남궁성 저자의 Java의 정석 3판 스터디
이전 글에서 Collections Framework 에 대해서 알아보았다.
이번에는 Collections Framework 의 클래스들을 알아보고자 한다.
다룰 클래스 ⬇️
- ArrayList
- LinkedList
- Stack, Queue
- Iterator, ListIterator, Enumeration
- Arrays
- Comparator, Comparable
- HashSet
- TreeSet
- HashMap, Hashtable
- TreeMap
- Properties
- Collections
ArrayList
List 인터페이스를 구현했고, 기존의 Vector 를 개선한 클래스.
💡 Vector 는 기존에 작성된 소스와의 호환성을 위해 남겨두고 있는 것이기 때문에
Vector 보다는 ArrayList 를 사용하자 !
ArrayList 는 Object 배열을 이용해서 데이터를 순차적으로 저장한다.
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable {
...
transient Object[] elementData; // Object 배열
...
}
간단하게 ArrayList 의 method 를 살펴보자.
많은 method 가 있지만 몇 개만 적어보려한다. 나머지는 쓰면서 찾아보자 🙂
// 생성자
ArrayList() // 크기가 10인 ArrayList 생성
ArrayList(Collection c) // 주어진 컬렉션이 저장된 ArrayList 생성
ArrayList(int initialCapacity) // 지정된 초기용량을 갖는 ArrayList 생성
// 메소드
boolean add(Object o) // ArrayList 의 마지막에 객체를 추가, 성공하면 true 반환
void add(int index, Object element) // 지정된 위치에 객체를 저장
void clear() // ArrayList 를 완전히 비움
void sort(Comparator c) // 지정된 정렬기준으로 ArrayList 정렬
Object set(int index, Object element) // 주어진 객체를 지정된 위치에 저장
add(index, element) 는 해당 위치에 객체를 끼워넣는 것이고,
set() 은 바꿔치기 !
ArrayList 는 배열 기반이다.
배열은 구조가 간단하고 데이터를 읽어노는데 걸리는 시간이 빠르다는 장점이 있지만 단점도 있다.
- 크기를 변경할 수 없다.
- 비순차적인 데이터의 추가 또는 삭제에 시간이 많이 걸린다.
이러한 단점을 보완하기 위해서 나온 것이 Linked List 라는 자료구조 이다.
LinkedList
- 순차적으로 요소를 추가/삭제하는 경우에는 ArrayList 가 LinkedList 보다 빠르다.
- 중간 데이터를 추가/삭제하는 경우에는 LinkedList 가 ArrayList 보다 빠르다.
ArrayList 는 배열 기반이기 때문에 중간 데이터를 추가하거나 삭제할 때
요소들을 재배치해서 추가공간을 확보하거나 빈 공간을 다 채워줘야 하기 때문에 느리다.
장단점을 잘 이해하고 적합한 것을 선택해서 사용하자 ‼️
LinkedList 역시 List 인터페이스를 구현했기 때문에 ArrayList 와 내부구현방법만 다를뿐
제공하는 메소드의 종류나 기능은 거의 같다.
컬렉션 | 읽기(접근시간) | 추가/삭제 | 비고 |
ArrayList | 빠르다 | 느리다 | 순차적인 추가삭제는 더 빠름 비효율적인 메모리 사용 |
LinkedList | 느리다 | 빠르다 | 데이터가 많을수록 접근성이 떨어짐 |
💡 장점을 살려서, 두 클래스를 조합해서 사용해보면 어떨까?
처음 데이터 저장 시 ArrayList 를 사용한 다음,
작업할 때는 LinkedList 로 데이터를 옮겨서 작업하면 ?
Stack, Queue
데이터를 순차적으로 추가하고 삭제하는 Stack 은
ArrayList 와 같은 배열기반의 컬렉션 클래스가 적합하고,
Queue 는 데이터를 꺼낼 때 항상 첫 번째 저장된 데이터를 삭제하므로
ArrayList 와 같은 배열기반의 컬렉션 클래스를 사용한다면 데이터를 꺼낼 때마다 빈 공간을 채우기 위해
데이터의 복사가 발생하게 되어 비효율적이다.
그래서 Queue 는 ArrayList 보다 LinkedList 로 구현하는 것이 더 적합하다 ‼️
Stack 의 method
boolean empty() // Stack 이 비어있는지 확인
Object peek() // Stack 가장 위에 저장된 객체 반환
Object pop() // Stack 가장 위에 있는 객체 꺼내서 반환 (삭제),
// Stack이 비어있을 때는 EmptyStackException 발생
Object push(Object item) // Stack 에 객체 저장
int search(Object o) // Stack 에서 객체를 찾아서 그 위치 반환, 못 찾으면 -1 반환
// 배열과 달리 위치는 1부터 시작
Queue 의 method
boolean add(Object o) // 객체를 Queue 에 추가
Object remove() // Queue 에서 객체를 꺼내 반환.
// 비어있을 때는 NoSuchElementException 발생
Object element() // 삭제 없이 요소를 읽어옴. Queue 가 비어있으면 NoSuchElementException 발생
boolean offer(Object o) // Queue 에 객체 저장.
Object poll() // Queue 에서 객체를 꺼내서 반환, 비어있으면 null 반환
Object peek() // 삭제 없이 요소를 읽어옴. Queue 가 비어있으면 null 을 반환
Java 에서 Stack 은 클래스로 구현해서 제공하고 있지만,
Queue 는 인터페이스로만 정의해놓았다.
Queue 인터페이스를 구현한 클래스들 중에서 하나를 선택해서 사용하자 ‼️
PriorityQueue
Queue 인터페이스의 구현체 중 하나.
저장한 순서에 상관없이 우선순위가 높은 것부터 꺼내게 된다.
null 을 저장하면 NullPointerException ‼️
저장공간으로 배열을 사용하고, 각 요소를 heap 자료구조 형태로 저장한다.
Deque (Double-Ended Queue)
한쪽 끝에만 추가/삭제할 수 있는 Queue 와 달리,
Deque 는 양쪽 끝에 추가/삭제가 가능하다.
Deque 의 조상은 Queue 이고 구현체로는 ArrayDeque 과 LinkedList 등이 있다.
Iterator, ListIterator, Enumeration
컬렉션에 저장된 요소를 접근하는 데 사용되는 인터페이스.
Enumeration 은 Iterator 의 구버전이고, ListIterator 는 Iterator 의 기능을 향상시킨 것이다.
Iterator
컬렉션 프레임워크에서는 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화했다.
그 기능을 가진 것이 바로 Iterator 인터페이스 !
Collection 인터페이스는 Iterator 를 반환하는 iterator() 를 정의하고 있다.
public interface Iterator() {
boolean hasNext(); // 읽어올 요소가 남아있는지 확인.
Object next(); // 다음 요소를 읽어옴. next() 호출 전에 hasNext()로 확인하자 !
void remove(); // next()로 읽어온 요소 삭제.
}
public interface Collection() {
...
public Iterator iterator();
...
}
ArrayList 에 저장된 요소를 출력하는 코드를 작성해보자.
// ArrayList 요소 출력
Collection c = new ArrayList();
Iterator it = c.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
Map 인터페이스의 경우 k,v 상으로 저장하고 있기 때문에 iterator() 를 직접 호출할 수는 없고,
keySet() 이나 entrySet() 으로 key 와 value 를 Set 형태로 얻어온 후에
다시 iterator() 를 호출해야 한다.
Map map = new HashMap();
Iterator it = map.entrySet().iterator();
여기서 유의해야 할 점은, List 클래스들은 저장순서를 유지하기 때문에
Iterator 를 이용해서 읽어온 결과도 저장순서와 동일하다.
하지만 Set 클래스들은 각 요소간의 순서가 유지되지 않기 때문에
Iterator 를 이용해서 저장된 요소들을 읽어봐도 처음에 저장된 순서와 같지 않다 ‼️
ListIterator, Enumeration
Enumeration 은 컬렉션 프레임워크가 만들어지기 이전에 사용하던 것 !
가능하면 Enumeration 대신 Iterator 를 사용하자.
ListIterator 는 Iterator 를 상속받아서 기능을 추가한 것이다.
Iterator 는 단방향으로만 이동할 수 있는데, ListIterator 는 양방향으로 이동이 가능하다 !
단 List 인터페이스를 구현한 컬렉션에만 사용 가능 ‼️
void add(Object o) // 컬렉션에 새로운 객체 추가
boolean hasNext() // 읽어올 다음 요소가 있는지 확인
boolean hasPrevious() // 읽어올 이전 요소가 있는지 확인
Object next() // 다음 요소 읽어옴
Object previous() // 이전 요소 읽어옴
int nextIndex() // 다음 요소의 index 반환
int previousIndex() // 이전 요소의 index 반환
void remove() // next() 또는 previous() 로 읽어온 요소 삭제
void set(Object o) // next() 또는 previous() 로 읽어온 요소를 지정된 객체로 변경
remove() 를 호출할 때는 반드시 next() 와 함께 사용해야 한다.
next() 호출 없이 remove() 를 호출하면 IllegalStateException
이 발생한다.
Iterator 를 상속받아 클래스를 구현하는데, remove()가 필요없는 상황이라고 가정하자.
필요없다고 하더라도 추상메서드기 때문에 body 를 만들어주어야 하는데,
단순히 선언만 하고 body 를 비우는 것보다
UnsupportedOperationException
을 던지는 것이 더 좋다 !
++추가예정++