1. 컬렉션 프레임웍(Collections framework)

데이터 군을 저장하는 클래스들을 표준화한 설계를 뜻한다. Java API문서에는 컬렉션 프레임웍을 '데이터 군을 다루고 표현하기 위한 단일화된 구조'라고 정의하고 있다. 또한 인터페이스와 다형성을 이용한 객체지향적 설계를 통해 표준화되어 있기 떄문에 사용법을 익히기에도 편리하고 재사용성이 높은 코드를 작성할 수 있다는 장점이 있다.

1.1 컬렉션 프레림웍의 핵심 인터페이스

컬렉션 프레임웍에서는 컬렉션데이터 그룹을 크게 3가지 타입이 존재한다고 인식하고 각 컬렉션을 다루는데 필요한 기능을 가진 3개의 인터페이스를 정의하였다.

  • List : 순서가 있는 데이터의 집합, 데이터의 중복을 허용한다.
    예) 대기자 명단
    구현 클래스 : ArrayList, LinkedList, Stack, Vector등
  • Set : 순서를 유지하지 않는 데이터의 집합, 데이터의 중복을 허용하지 않는다.
    예) 양의 정수집합, 소수의 집합
    구현 클래스 : HashSet, TreeSet등
  • Map : 키(Key)와 값(Value)의 쌍(pair)으로 이루어진 데이터의 집합, 순서는 유지되지 않으며
    키는 중복을 허용하지 않고, 값은 중복을 허용한다.
    예) 우편번호, 지역번호(전화번호)
    구현 클래스 : HashMap, TreeMap, Hashtable, Propertiese등

Vector, Stack, Hashtable, Properties와 같은 클래스들은 컬렉션 프레임웍이 만들어지기 이전부터 존재하던 것이기 때문에 컬렉션 프레임웍의 명명법을 따르지 않는다. Vector나 Hashtable과 같은 기존의 컬렉션 클래스들은 호환을 위해, 설계를 변경해서 남겨두었지만 가능하면 사용하지 않는 것이 좋다. 그 대신 새로 추가된 ArrayList와 HashMap을 사용하자.

Collection 인터페이스
List와 Set의 조상인 Collection인터페이스에는 다음과 같은 메서드들이 정의되어 있다.

Collection인터페이스는 컬렉션 클래스에 저장된 데이터를 읽고, 추가하고 삭제하는 등 컬렉션을 다루는데 가장 기본적인 메서드들을 정의하고 있다. 위의 반환타입이 boolean인 메서드들은 작업을 성공하거나 사실이면 true를, 그렇지 않으면 false를 반환한다.
그리고 JavaAPI문서를 보면, 표에 사용된 'Object'가 아닌 'E'로 표기되어 있는데, E는 특정 타입을 의미하는 것으로 JDK1.5부터 추가된 지네릭스(Generics)에 의한 표기이다. 아직 지네릭스를 배우지 않았기 떄문에 이해를 돕기 위한 의도로 'E'대신 'Object'로 표기 했다. 'E'외에도 'I'나 'K', 'V'를 사용하는 경우도 있는데 모두 Object타입이라고 이해하자.

LIst인터페이스
List인터페이스는 중복을 허용하면서 저장순서가 유지되는 컬렉션을 구현하는데 사용된다.

List인터페이스에 정의된 메서드는 다음과 같다. Collection인터페이스로부터 상속받은 것들은 제외하였다.

Set인터페이스
Set인터페이스는 중복을 허용하지 않고 저장순서가 유지되지 않는 컬렉션 클래스를 구현하는데 사용된다. Set인터페이스를 구현한 클래스로는 HashSet, TreeSet등이 있다.

Map인터페이스
Map인터페이스는 키(Key)와 값(value)을 하나의 쌍으로 묶어서 저장하는 컬렉션 클래스를 구현하는데 사용된다.키는 중복될 수 없지만 값은 중복을 허용한다. 기존에 저장된 데이터와 중복된 키와 값을 저장하면 기존의 값은 없어지고 마지막에 저장된 값이 남게 된다. Map인터페이스를 구현한 클래스로는 Hashtable, HashMap, LickedHashMap, SortedMap, TreeMap등이 있다.
Map이란 개념은 어떤 두 값을 연결한다는 의미에서 붙여진 이름이다.


values()에서는 반환타입이 collection이고, keySet()에서는 반환타입이 Set인 것에 주목하자.
Map인터페이스에서 값(value)은 중복을 허용하기 때문에 Collection타입으로 반환하고, 키(Key)는 중복을 허용하지 않기 떄문에 Set타입으로 반환한다.

Map.Entry인터페이스
Map.Entry인터페이스는 Map인터페이스의 내부 인터페이스이다.내부 클래스와 같이 인터페이스도 인터페이스 안에 인터페이스를 정의하는 내부 인터페이스(innner interface)를 정의하는 것도 가능하다.
Map에 저장되는 key-value쌍을 다루기 위해 내부적으로 Entry인터페이스를 정의해 놓았다. 이것은 보다 객체지향적으로 설계하도록 유도하기 위한 것으로 Map인터페이스를 구현하는 클래스에서는 Map.Entry 인터페이스도 함께 구현해야한다

public interface Map {
	...
    public static interface Entry {
    	Object getKey();
        Object getValue();
        Object setValue(Object value);
        boolean equals(Object o);
        int hashCode();
        ...
    }
}

## 1.2 ArrayList
ArrayList는 List인터페이스를 구현하기 때문에 데이터의 저장순서가 유지되고 중복을 허용한다는 특징을 갖는다. ArrayList는 기존의 Vector를 개선한 것으로 Vector와 구현원리와 기능적인 측면에서 동일하다고 할 수 있다. 호환성 떄문에 계속 남겨두고 있을 뿐이기 때문에, Vector보다는 ArrayList를 사용하자.

ArrayList는 Object배열을 이용해서 데이터를 순차적으로 저장한다. 배열에 더 이상 저장할 공간이 없으면 보다 큰 새로운 배열을 생성해서 기존의 배열에 저장된 내용을 새로운 배열로 복사한 다음에 저장된다.

public class ArrayList extneds AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {
	...
    transient Objectp[] elementData; //Object배열
    ...
}

transient는 직렬화(serialization)와 관련된 제어자이다.
위의 코드는 ArrayList의 소스코드 일부인데 ArrayList는 elementData라는 이름의 Object배열을 멤버변수로 선언하고 있다는 것을 알수 있다. 선언된 배열의 타입이 모든 객체의 최고조상인 Object이기 때문에 모든 종류의 객체를 담을 수 있다.

ArrayList나 Vector 같이 배열을 이용한 자료구조는 데이터를 읽어오고 저장하는 데는 효율이 좋지만, 용량을 변경해야할 때는 새로운 배열을 생성한 후 기존의 배열로부터 새로 생성된 배열로 데이터를 복사해야 하기 떄문에 상당히 효율이 떨어진다는 단점을 가지고 있다. 그래서 처음에 인스턴스를 생성할 때, 저장할 데이터의 개수를 잘 고려하여 충분한 용량의 인스턴스를 생성하는 것이 좋다.

1.3 LinkedList

배열은 가장 기본적인 형태의 자료구조로 구조가 간단하며 사용하기가 쉽고 데이터를 읽어오는데 걸리는 시간(접근시간, access time)이 가장 빠르다는 장점을 가지고 있지만 다음과 같은 단점도 가지고 있다

  • 크기를 변경할 수 없다.
    크기를 변경할수 없으므로 새로운 배열을 생성해서 데이터를 복사해야 한다.
    실행속도를 향상시키기 위해서는 충분히 큰 크기의 배열을 생성해야 하므로 메모리가 낭비된다.
  • 비순차적인 데이터의 추가 또는 삭제에 시간이 많이 걸린다.
    차례대로 데이터를 추가하고 마지막에서부터 데이터를 삭제하는 것은 빠르지만,
    배열의 중간에 데이터를 추가하려면, 빈자리를 만들기 위해 다른 데이터들을 복사해서 이동해야 한다.

이러한 배열의 단점을 보안하기 위해서 링크드 리스트(linked list)는 자료구조가 고안 되었다.
배열은 모든 데이터가 연속적으로 존재하지만 링크드 리스트는 불연속적으로 존재하는 데이터를 서로 연결(link)한 형태로 구성되어 있다.

class Node {
	Node next;		//다음 요소의 주소를 저장
    Object obj;		//데이터를 저장
}

링크드 리스트에서의 데이터 삭제는 간단하다. 삭제하고자 하는 요소의 이전요소가 삭제하고자 하는 요소의 다음 요소를 참조하도록 변경하기만 하면 된다. 단 하나의 참조만 변경하면 삭제가 이루어지는 것이다. 배열처럼 데이터를 이동하기 위해 복사하는 과정이 없기 때문에 처리속도가 매우 빠르다.

링크드 리스트는 이동방향이 단방향이기 때문에 다음 요소에 대한 접근은 쉽지만 이전요소에 대한 접근은 어렵다. 이 점을 보완한 것이 더블 링크드 리스트(이중 연결리스트, doubly linked list)이다.

더블 링크드 리스트는 단순히 링크드 리스트에 참조변수를 하나 더 추가하여 다음 요소에 대한 참조뿐 아니라 이전 요소에 대한 참조가 가능하도록 했을 뿐, 그 외에는 링크드 리스트와 같다.
더블 링크드 리스트는 링크드 리스드보다 각 요소에 대한 접근과 이동이 쉽기 떄문에 링크드 리스트보다 더 많이 사용된다.

class Node {
	Node next;		//다음 요소의 주소를 저장
    Node previous   //이전 요소의 주소를 저장
    object obj;	    //데이터를 저장
}

더블 써큘러 링크드 리스트(이중 원형 연결리스트, doubly circular linked list)인데, 단순히 더블 링크드 리스트의 첫 번째 요소와, 마지막 요소를 연결시킨 것이다. 이렇게 하면, 마지막요소의 다음요소가 첫번째 요소가 되고, 첫 번째 요소의 이전 요소가 마지막 요소가 된다. 마치 Tv의 마지막 채널에서 채널을 증가시키면 첫 번째 채널로 이동하고 첫 번째 채널에서 채널을 감소시키면 마지막 채널로 이동하는 것과 같다.

실제로 LinkedList클래스는 이름과 달리 '링크드 리스트'가 아닌 '더블 링크드 리스트'로 구현되어 있는데, 이는 링크드 리스트의 단점인 낮은 접근성(accessability)을 높이기 위한 것이다.

순차적으로 추가/삭제하는 경우에는 ArrayList가 LinkedList보다 빠르다.
순자적으로 삭제한다는 것은 마지막 데이터부터 역순으로 삭제해 나간다는 것을 의미하며, ArrayList는 마지막 데이터부터 삭제할 경우 각 요소들의 재배치가 필요하지 않기 때문에 상당히 빠르다.
단지 마지막 요소의 값을 null로만 바꾸면 되니까.

중간 데이터를 추가/삭제하는 경우에는 LinkedList가 ArrayList보다 빠르다.
중간 요소를 추가 삭제하는 경우, LinkedList는 각 요소간의 연결만 변경해주면 되기 때문에 처리속도가 상당히 빠르다. 반면에 ArrayList는 각 요소들을 재배치하여 추가할 공간을 확보하거나 빈 공간을 채워야하기 때문에 처리속도가 늦다.

배열의 경우 만일 인덱스가 n인 요소의 값을 얻어 오고자 한다면 단순히 아래와 같은 수식을 계산함으로써 해결된다.
인덱스가 n인 데이터의 주소 = 배열의 주소 + n * 데이터 타입의 크기
배열은 각 요소들이 연속적으로 메모리상에 존재하기 때문에 이처럼 간단한 계산만으로 원하는 요소의 주소를 얻어서 저장된 데이터를 곧바로 읽어올 수 있지만 LinkedList는 불연속적으로 위치한 각 요소들이 서로 연결된 것이라 처음부터 n번째 데이터까지 차례대로 따라가야만 원하는 값을 얻을 수 있다.

그래서 LinkedList는 저쟁해야하는 데이터의 개수가 많아질수록 데이터를 읽어 오는 시간, 즉 접근시간(access time)이 길어진다는 단점이 있다.

다루고자 하는 데이터의 개수가 변하지 않는 경우라면, ArrayList가 최상의 선택이 되겠지만, 데이터 개수의 변경이 잦다면 LInkedList를 사용하는 것이 더 나은 선택이 될 것이다.
두 클래스의 장점을 이용해서 두 클래스를 조합해서 사용하는 방법도 생각해 볼 수 있다.
처음에 작업하기 전에 데이터를 저장할 때는 ArrayList를 사용한 다음, 작업할 때는 LinkedList로 데이터를 옮겨서 작업하면 좋은 효율을 얻을 수 있을 것이다.

ArrayList al = new ArrayList(1000000);
for(int i = 0; i < 100000; i++) {
	al.add(i + "");
}
LinkedList ll = new LinkedList(al);
for(int i = 0; i < 1000; i++) {
	ll.add(500, "x");

컬렉션 프레임웍에 속한 대부분의 컬렉션 클래스들은 이처럼 서로 변환이 가능한 생성자를 제공하므로 이를 이용하면 간단히 다른 컬렉션 클래스로 데이터를 옮길 수 있다.

1.4 Stack과 Queue

스텍은 마지막에 저장한 데이터를 가장 먼저 꺼내게 되는 LIFO(Last In First Out)구조로 되어있고, 큐는 처음에 저장한 데이터를 가장 먼저 꺼내게 되는 FIFO(First In First Out)구조로 되어 있다.
스텍은 양 옆과 바닥이 막혀있어서 한 방향으로만 뺼 수 있고, 큐는 양 옆만 막혀있고 위 아래로 뚫려 있어서 한 방향으로는 넣고 한 방향으로는 빼는 파이프와 같은 구조로 되어 있다.

예를 들어 스택에 0,1,2의 순서로 데이터를 넣었다면 스텍은 2,1,0순으로 꺼내게 된다.
즉 넣은 순서와 뒤집어 진다.

큐에 0,1,2의 순서로 데이터를 넣었다면 꺼내는 순서 역시 0,1,2의 순서가 된다.

스택의 구현을 위해서 순차적으로 데이터를 추가하고 삭제하는 ArrayList와 같은 배열기반의 컬렉션클래스가 적합하지만, 큐는 데이터를 꺼낼 때 마다 항상 첫 번째 저장된 데이터를 삭제하므로, ArrayList와 같은 배열기반의 컬렉션클래스를 사용한다면 데이터를 꺼낼 때마다 빈 공간을 채우기 위해 데이터 복사가 일어남으로 비효율 적이다. 그래서 큐는 ArrayList보다 데이터의 추가/삭제가 쉬운 LInkedList로 구현하는 것이 더 적합하다.

스택과 큐의 활용
지금까지 스택과 큐의 개념과 구현에 대해서 알아보았는데 이제는 스택과 큐를 어떻게 활용할 것인가에 대해 살펴보자.

스택의 활용 예 - 수식계산, 수식괄호검사, 워드프로세서의 undo/redo, 웹브라우저의 뒤로/앞으로
큐의 활용 예 - 최근사용문서, 인쇄작업 대기목록, 버퍼(buffer)

PriorityQueue
Queue인터페이스의 구현체 중의 하나로, 저장한 순서에 관계없이 우선순위(priority)가 높은 것부터 꺼내게 된다는 특징이 있다. 그리고 null은 저장할 수 없다. null을 저장하면 NullPointerException이 발생한다.
PriorityQueue는 저장공간으로 배열을 사용하며, 각 요소를 '힙(heap)'이라는 자료구조의 형태로 저장한다.힙은 잠시 우헤 배울 이진 트리의 한 종류로 가장 큰 값이나 가장 작은 값을 빠르게 찾을 수 있다는 특징이 있다.

우선순위는 숫자가 작을수록 높은 것이므로 1이 가장 먼저 출력된다.

Deque(Double - Ended Queue)
Queue의 변형으로, 한 쪽 끝으로만 추가/삭제할 수 있는 Queue와 달리, Deque(덱, 또는 다큐라고 읽음)은 양쪽 끝에 추가/삭제가 가능하다. Deque의 조상은 Queue이며, 구현체로는 ArrayDeque과 LinkedList등이 있다.

1.5 lterator, Listlterator, Enumeration

Iterator, ListIterator, Enumeration은 모두 컬렉션에 저장된 요소를 접근하는데 사용되는 인터페이스이다. Enimeration은 Iterator의 구버전이며, ListIterator는 Iterator의 기능을 향상 시킨 것이다.

Iterator
컬렉션 프레임웍에서는 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화하였다. 컬렉션에 저장된 각 요소에 접근하는 기능을 가진 Iterator인터페이스를 정의하고, Collection인터페이스에는 'Iterator(Iterator를 구현한 클래스의 인스턴스)를 반환하는 iterator()를 정의하고 있다.

iterator()는 Collection인터페이스에 정의된 메서드이므로 Collection인터페이스의 자손인 List와 set에도 포함되어 있다. 그래서 List나 Set인터페이스를 구현하는 컬렉션은 iterator()가 각 컬렉션의 특징에 알맞게 작성되어 있다. 컬렉션 클래스에 대해 iterator()를 호출하여 Iterator를 얻은 다음 반복문, 주로 while문을 사용해서 컬렉션 클래스의 요소드를 읽어 올 수 있다.
ArrayList에 저장된 요소들을 출력하기 위한 코드는 다음과 같이 작성할 수 있다.

Collection c = new ArrayList();		//다음 컬렉션으로 변경시 이 부분만 고치면 된다.
Iterator it = c.iterator();
while(it.hasNext()) {
	System.out.println(it.next());
}

Map인터페이스를 구현한 컬렉션 클래스는 키(Key)와 값(value)을 쌍(pair)으로 저장하고 있기 때무네 iterator()를 직접 호출 할 수 없고, 그 대신 ketSet()이나 entrySet()과 같은 메서드를 통해서 키와 값을 각각 따로 Set의 형태로 얻어 온 후에 다시 iterator()를 호출해야 Iterator를 얻을 수 있다.

Map map = new HashMap();
	...
Iterator it = map.entrySet().iterator();

Iterator it = map.entrySet().iterator();는 아래의 두 문장을 하나로 합친 것이라고 이해하면 된다.

Set eSet = map.entrySet();
Iterator it = eSet.iterator();
  1. map.entrySet()의 실행결과가 Set이므로
    Iterator it = map.entrySet().iterator(); -----> Iterator it = Set인스턴스.iterator();

  2. map.entrySet()를 통해 얻은 Set인스턴스의 iterator()를 호출해서 Iterator인스턴스를 얻는다.
    Iterator it = Set인스턴스.iterator(); ----> Iterator it = Iterator인스턴스;

  3. 마지막으로 Iterator인스턴스의 참조가 it에 저장된다.

ListIterator와 Enumeration
Enumeration은 컬렉션 프레임웍이 만들어지기 이전에 사용하던 것으로 Iterator의 구버전이라고 생각하면 된다. 이전 버전으로 작성된 소스와의 호환을 위해서 남겨두고 있을 뿐이므로 가능하면 Enumeration 대신 Iterator를 사용하자.

ListIterator는 Iterator를 상속받아서 기능을 추가한 것으로, 컬렉션의 요소에 접근할때 Iterator는 단방향으로만 이동할 수 있는데 반해 ListIterator는 양방향으로의 이동이 가능하다. 다만 ArrayList나 LinkedList와 같이 List인터페이스를 구현한 컬렉션에만 사용할 수 있다.

Enumeration : Iterator의 구버전
ListIterator : Iterator에 양방향 조회기능추가(List를 구현한 경우만 사용가능)

Enumeration과 Iterator는 메서드이름만 다를 뿐 기능은 같고, ListIterator는 Iterator에 이전방향으로의 접근기능을 추가한 것일 뿐이라는 것을 알 수 있다.

Iterator인터페이스를 구현하는 클래스에서 remove()는 선택적인 기능이므로 구현하지 않아도 된다. 그렇다 하더라도 인터페이스로부터 상속받은 메서드는 추상메서드라 메서드의 몸통(body)을 반드시 만들어 주어야 하므로 다음과 같이 처리한다.

public void remove() {
	throw new UnsupportedOperationException();
}

단순히 public void remove() {};와 같이 구현하는 것 보다 이처럼 예외를 던져서 구현되지 않은 기능이라는 것을 메서드를 호출하는 쪽에 알리는 것이 좋다.

Iterator의 remove()는 단독으로 쓰일 수 없고, next()와 같이 써야한다. 특정위치의 요소를 삭제하는 것이 아니라 읽어 온 것을 삭제한다. next()의 호출 없이 remove()를 호출하면, IllegalStateException이 발생한다.

'마이크로소프트 아웃룩)과 같읕 email클라이언트에서 메일서버에 있는 메일을 가져올 때 서버에 있는 메일을 읽어만 올 것인지(copy), 메일을 가져오면서 서버에서 삭제할 것(move)인지를 선택 할 수 있다. 이와 같은 기능을 구현하고자 할 떄 쓸 목적으로 remove()를 정의해 놓은 것이다.

단순히 서버에서 읽어오기만 할 때는 next()를 사용하면 되고, 읽어 온 메일을 서버에 남기지 않고 지울 때는 next()와 함께 remove()를 사용하면 이와 같은 기능을 구현할 수 있다.

1.6 Arrays

Arrays클래스에는 배열을 다루는데 유용한 메서드가 정의되이 있다. Arrays에 정의된 메서드는 모두 static메서드이다.

배열의 복사 - copyOf(), copyOfRange()
copyOf()는 배열 전체를, copyOfRange()는 배열의 일부를 복사해서 새로운 배열을 만들어 반환한다.
늘 그렇듯이 copyOfRange()에 지정된 범위의 끝은 포함하지 않는다.

배열 채우기 - fill(), setAll()
fill()은 배열의 모든 요소를 지정된 값으로 채운다. setAll()은 배열을 채우는데 사용할 함수형 인터페이스를 매개변수로 받는다. 이 메서드를 호출할 때는 함수형 인터페이스를 구현한 객체를 매개변수로 지정하던가 아니면 람다식을 지정해야 한다.

배열의 정렬과 검색 - sort(), binarySearch()
sort()는 배열을 정렬할 때, 그리고 배열에 저장된 요소를 검색할 때는 binarySearch()를 사용한다.
binarySearch()는 배열에서 지정된 값이 저장된 위치(index)를 찾아서 반환하는데, 반드시 배열이 정렬된 상태이어야 올바른 결과를 얻는다. 그리고 만일 검색한 값과 일치하는 요소들이 여러 개 있다면, 이 중에서 어떤 것의 위치가 반환될지는 알 수 없다.

배열의 첫 번째 요소부터 순서대로 하나씩 검색하는 것을 '순차 검색(linear search)'라고 하는데, 이 검색 방법은 배열이 정렬되어 있을 필요는 없지만 배열의 요소를 하나씩 비교하기 때문에 시간이 많이 걸린다.

반면에 이진 검색(binary search)은 배열의 검색할 범위를 반복적으로 절반씩 줄여가면서 검색하기 때문에 검색속도가 상당히 빠르다. 배열의 길이가 10배 늘어나도 검색 횟수는 3~4회 밖에 늘어나지 않으므로 큰 배열의 검색에 유리하다. 단, 배열이 정렬되어 있는 경우에만 사용할 수 있다.

배열의 비교와 출력 - equals(),toString()
toString()배열의 모든 요소를 문자열로 편하게 출력할 수 있다. toString()은 일차원 배열에만 사용할 수 있으므로, 다차원 배열에는 deepTo String()을 사용해야 한다.

equal()는 두 배열에 저장된 모든 요소를 비교해서 같으면 true, 다르면 false를 반환한다. equals()도 일차원 배열에만 사용가능하므로, 다차원 배열의 비교에는 deepEquals()를 사용해야 한다.

위와 같이 2차원 String배열을 equals()로 비교하면 배열에 저장된 내용이 같은데도 false를 결과로 얻는다. 다차원 배열은 '배열의 배열'의 형태로 구성하기 때문에 equals()로 비교하면, 문자열을 비교하는 것이 아니라 '배열에 저장된 배열의 주소'를 비교하게 된다. 서로 다른 배열은 항상 주소가 다르므로 false를 결과로 얻는다.

배열을 List로 변환 - asList(Object...a)
asList()는 배열을 List에 담아서 반환한다. 매개변수의 타입이 가변인수라서 배열 생성없이 저장할 요소들만 나열하는 것도 가능하다.

한가지 주의할 점은 asList()가 반환한 List의 크기를 변경할 수 없다는 것이다. 즉, 추가 또는 삭제가 불가능하다. 저장된 내용은 변경가능하다. 만일 크기를 변경할 수 있는 List가 필요하다면 다음과 같이 하면 된다.

List list = new ArrayList(Arrays.asList(1,2,3,4,5));

parallelXXX(), spliterator(), stream()
이 외에도 'parallel'로 시작하는 이름의 메서드들이 있는데, 이 메서드들은 보다 빠른 결과를 얻기 위해 여러 쓰레드가 작업을 나누어 처리하도록 한다. spliterator()는 여러 쓰레드가 처리할 수 있게 하나의 작업을 여러 작업으로 나누는 Spliterator를 반환하며, stream()은 컬렉션을 스트림으로 변환한다. 이 메서드들은 람다와 스트림과 관련된 내용이다.

1.7 Comparator와 Comparable

이전 예제에서 Array.sort()를 호출만 하면 컴퓨터가 알아서 배열을 정렬하는 것처럼 보이지만, 사실은 Charater클래스의 Comparable의 구현에 의해 정렬되었던 것이다. Comparator와 Comparable은 모두 인터페이스로 컬렉션을 정렬하는데 필요한 메서드를 정의하고 있으며, Comparable을 구현하고 있는 클래스들은 같은 타입의 인스턴스끼리 서로 비교할 수 있는 클래스들, 주로 Integer와 같은 wrapper클래스와 String, Data, File과 같은 것들이며 기본적으로 오름차순, 즉 작은 값에서부터 큰 값의 순으로 정렬되도록 구현되어 있다. 그래서 Comparable을 구현한 클래스는 정렬이 가능하다는 것을 의미한다.

  • Comparable : 기본 정렬기준을 구현하는데 사용
  • Comparator : 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때 사용

1.8 HashSet

HashSet은 Set인터페이스를 구현한 가장 대표적인 컬렉션이며, Set인터페이스의 특징대로 HashSet은 중복된 요소를 저장하지 않는다. HashSet에 새로운 요소를 추가할 때는 add메서드나 addAll메서드를 사용하는데, 만일 Hashset에 이밈 저장되에 있는 요소와 중복된 요소를 추가하고자 한다면 이 메서드들은 false를 반환함으로써 중복된 요소이기 때문에 추가에 실패했다는 것을 알린다.

이러면 HashSet의 특징을 이용하면, 컬렉션 내의 중복 요소들을 쉽게 제거할 수 있다.

ArrayList와 같이 List인터페이스를 구현한 컬렉션과 달리 HashSet은 저장순서를 유지하지 않으므로 저장순서를 유지하고자 한다면 LinkedHashSet을 사용해야 한다.

HashSet은 내부적으로 HashMap을 이용해서 만들어졌으며, HashSet이란 이름은 해싱(hashing)을 이용해서 구현했기 때문에 붙여진 이름이다. 해싱(hashing)에 대한 자세한 내용은 hashMap에서 설명한다.

impoert java.util.*;
class HashSetEx3 {
	HashSet set = new HashSet();

    set.add("abc");
    set.add("abc");
    set.add(new Person("David", 10));
    set.add(new Person("David", 10))

    System.out.println(set);
    }
}

class Person {
	String name;
    int age;

    Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }

    public String toString() {
    	return name + " : " + age:
    }
}

실행결과
[abc, David : 10, David : 10]

name과 age가 같으면 같은 사람으로 인식하도록 하려는 의도로 작성하였다. 하지만 실행결과를 보면 두 인스턴스의 name과 age의 값이 같음에도 불구하고 서로 다른것으로 인식하여 'David : 10'이 두번 출력되었다.
클래스의 작성의도대로 이 두 인스턴스를 같은 것으로 인식하게 하려면 어떻게 해야 하는 걸까?

impoert java.util.*;
class HashSetEx3 {
	HashSet set = new HashSet();

    set.add("abc");
    set.add("abc");
    set.add(new Person("David", 10));
    set.add(new Person("David", 10))

    System.out.println(set);
    }
}

class Person2 {
	String name;
    int age;

    Person2(String name, int age) {
    	this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj) {
    	if(obj instanceof Person2) {
        	Person2 tmp = (Person2)obj;
            return name.equals(tmp.name) && age == tmp.age;
        }

        return false;
    }

    public int hashCode() {
    	return(name+age).hashCode();
    }

    public String toString() {
    	return name + " : " + age;
    }
}

실행결과 
[abc, David : 10]

HashSet의 add메서드는 새로운 요소를 추가하기 전에 기존에 저장된 요소와 같은 것인지 판별하기 위해 추가하려는 요소의 equal()와 hashCode()를 호출하기 때문에 equals()와 hashCode()를 목적에 맞게 오버라이딩 해야 한다.

그래서 String클래스에서 같은 내용의 문자열에 대한 equals()의 호출결과가 true를 반환하도록 equals()를 오버라이딩 했다. 그리고 hashCode()는 String클래스의 hashCode()를 이용해서 구현했다.

String클래스의 hashCode()는 잘 구현되어 있기 때문에 이를 활용하면 간단히 처리 할 수 있다.

public int hashCode() {
	return(name+age).hashCode();
}

위의 코드를 JDK1.8부터 추가된 java.util.Objects클래스의 hash()를 이용해서 작성하면 아래와 같다.이 메서드의 괄호 안에 클래스의 인스턴스 변수들을 넣으면 된다. 이전의 코드와 별반 다르지 않지만, 가능하면 아래의 코드를 쓰자.

public int hashCode() {
	return Object.hash(name, age);	//int hash(Object...value)
}

오버라이딩을 통해 작성된 hashCode()는 다음의 세 가지 조건을 만족시켜야 한다.

    1. 실행중인 애플리케이션 내의 동일한 객체에 대해서 여러번 hashCode()를 호출해도 동일한 int값을 반환해야 한다. 하지만, 실행시마다 동일한 int값을 반환할 필요는 없다. (단 equals메서드의 구현에 사용된 멤버변수의 값이 바뀌지 않았다고 가정한다.)

      예를들어 Perston2클래스의 equals메서드에 사용된 멤버변수 name과 age의 값이 바뀌지 않는 한, 하나의 Person2인스턴스에 대해 hashCode()를 여러 번 호출했을 때 항상 같은 int값을 얻어야 한다.

    1. equals메서드를 이용한 비교에 의해서 true를 얻은 두 객체에 대해 각각 hashCode()를 호출해서 얻은 결과는 반드시 같아야 한다.
      인스턴스 p1과 p2에 대해서 equals메서드를 이용한 비교의 결과인 변수 b의 값이 true라면, hashCode1과 hashCode2의 값은 같아야 한다는 뜻이다.
Person2 p1 = new Person2("David", 10);
Person2 p2 = new Person2("David", 10);

boolean b= p1.equals(p2);

int hashCode1 = p1.hashCode();
int hashCode2 = p2.hashCode();
    1. equals메서드를 호출했을 때 false를 반환하는 두 객체는 hashCode()를 호출에 대해 같은 int값을 반환하는 경우가 있어도 괜찮지만, 해싱(hashing)을 사용하는 컬렉션의 성능을 향상시키기 위해서 다른 int값을 반환하는 것이 좋다.

1.9 TreeSet

TreeSet은 이진 검색 트리(binary search tree)라는 자료구조의 형태로 데이터를 저장하는 컬렉션 클래스이다. 이진 검색 트리는 정렬, 검색, 범위검색(range search)에 높은 성능을 보이는 자료구조이며 TreeSet은 이진 검색 트리의 성능을 향상시킨 '레드-블랙 트리(Red-Black tree)'로 구현되어 있다.

그리고 Set인터페이스를 구현했으므로 중복된 데이터의 저장을 허용하지 않으며 정렬된 위치에 저장하므로 저장순서를 유지하지도 않는다
이진 트리(binary tree)는 링크드리스트처럼 여러 개의 노드(node)가 서로 연결된 구조로, 각 노드에 최대 2개의 노드를 연결할 수 있으며 '루트(root)'라고 불리는 하나의 노드에서부터 시작해서 계속 확장해 나갈 수 있다.

위 아래로 연결된 두 노드를 '부모 - 자식관계'에 있다고 하며 위의 노드를 부모 노드, 아래의 노드를 자식 노드라 한다. 부모 - 자식관계는 상대적인 것이며 하나의 부모 노드는 최대 두 개의 자식 노드와 연결될 수 있다.
아래의 그림에서 A는 B와 C의 부모 노드이고, B와 C는 A의 자식 노드이다.

이진 트리의 노드를 코드로 표현하면 다음과 같다.

class TreeNode {
	TreeNode left;		//왼쪽 자식노드
    Object element;		//객체를 저장하기 위한 참조변수
    TreeNode right;		//오른쪽 자식 노드 
}

이진 검색 트리는 부모노드의 왼쪾에는 부모노드의 값보다 작은 값의 자식노드를 오른쪽에는 큰 값의 자식노드를 저장하는 이진 트리이다.

이진 검색 트리(binary search tree)는

  • 모든 노드는 최대 두 개의 자식노드를 가질 수 있다.
  • 왼쪽 자식노드의 값은 부모노드의 값보다 작고 오른쪽자식노드의 값은 부모노드의 값보다 커야 한다.
  • 노드의 추가 삭제에 시간이 걸린다(순차적으로 저장하지 않으므로)
  • 검색(범위검색)과 정렬에 유리하다
  • 중복된 값을 저장하지 못한다.

import java.util.*;

class TreeSetExl1 {
	public static void main(String[] args) {
    	TreeSet set = new TreeSet();

        String from = "b";
        String to = "d";

        set.add("abc");		set.add("alien");		set.add("bat");
        set.add("car");		set.add("Car");			set.add("disc");
        set.add("dance");   set.add("dzzzz");		set.add("dzzzz");
        set.add("elephant");set.add("elevator");	set.add("fan");

        System.out.println(set);
        System.out.println("range search : from " + from + " to " + to);
        System.out.println("result1 : " + set.subSet(from,to));
        System.out.println("result2 : " + set.subSet(from, to + "zzz"));
    }
}

실행결과 
[car, abc, alien, bat, car, dzzzz, dance, disc, dzzzz, elephant, elevator, fan, flower]
range search : from b to d
result 1 : [bat, car]
result 2 : [bat, car, dzzzz, dance, disc]

subSet()을 이용해서 범위검색(racnge search)할 때 시작하는 범위는 포함되지만 끝 범위는 포함되지 않으므로 ruslt1에는 c로 시작하는 단어까지만 검색결과에 포함되어 있다.
만일 끝 범위인 d로 시작하는 단어까지 포함시키고자 한다면, 아래와 같이 끝 범위에 'zzz'와 같은 문자열을 붙이면 된다.

System.out.println("result2 : " + se.subSet(from, to + "zzz"));

d로 시작하는 단어 중에서 'dzzz'다음에 오는 단어는 없을 것이기 때문에 d로 시작하는 모든 단어들이 포함 될 것이다.

결과를 보면 'abc'보다 'Car'가 앞에 있고 'dZZZZ'가 'dance'보다 앞에 정렬되어 있는 것을 알 수 있다. 대문자가 소문자보다 우선하기 때문에 대소문자가 섞여 있는 경우 의도한 것과는 다른 범위검색결과를 얻을 수 있다.
그래서 가능하면 대문자 또는 소문자로 통일해서 저장하는 것이 좋다.

반드시 대소문자가 섞여 있어야 하거나 다른 방식으로 정렬해야 하는 경우 Comparator를 이용하면 된다.

문자열의 경우 정렬순서는 문자의 코드값이 기준이 되므로, 오름차순 정렬의 경우 코드값의 크기가 작은 순서에서 큰 순서, 즉 공백, 숫자, 대문자, 소문자 순으로 정렬되고 내림차순의 경우 그 반대가 된다.

1.10 HashMap과 Hashtable

Hashtable과 HashMap의 관계는 Vector와 ArrayList의 관계와 같아서 Hashtable보다는 새로운 버전인 HashMap을 사용할 것을 권한다. 여기서는 HashMap대해서만 설명하도록 하겠다.

HashMap은 Map을 구현했으므로 앞에서 살펴본 Map의 특징, 키(key)와 값(value)을 묶어서 하나의 데이터(entry)로 저장한다는 특징을 갖는다. 그리고 해싱(hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는데 있어서 뛰어난 성능을 보인다.

HashMap이 데이터를 어떻게 저장하는지 확인하기 위해 실제소스의 일부를 발췌하였다.

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {
	transient Entry[] table;
		...
    static class Entry implments Map.Entry {
    	final object key;
        object value;
        	...
    }
}

HashMap은 Entry라는 내부 클래스를 정의하고, 다시 Entry타입의 배열을 선언하고 있다. 키(key)와 값(value)은 별개의 값이 아니라 서로 관련된 값이기 때문에 각각의 배열로 선언하기 보다는 하나의 클래스로 정의해서 하나의 배열로 다루는 것이 데이터의 무결성(integrity)적인 측면에서 더 바람직하기 때문이다.

HashMap은 키와 값을 각각 Object타입으로 저장한다. 즉(Object, Object)의 형태로 저장하기 때문에 어떠한 객체도 저장할 수 있지만 키는 주로 String을 대문자 또는 소문자로 통일해서 사용하곤 한다.

키(key) : 컬렉션 내의 키(key)중에서 유일해야 한다.
값(value) : 키(key)와 달리 데이터의 중복을 허용한다.

import java.util.*;

class HashMapEx1 {
	public static void main(String[] args) {
    	HashMap map = new HashMap();
        map.put("myId", "1234");
        map.put("asdf", "1111");
        map.put("asdf", "1234");

        scanner s = new Sanner(System.in);

        while(true) {
        	System.out.println("id와 password를 입력해주세요.");
            System.oit.print("id :");
            String id = s.nextLine().trim;
            System.out.printIn();

            if(!map.containsKey(id)) {
            	System.out.println("입력하신 id는 존재하지 않습니다." + "다시 입력해주세요. ");
                continue;
            }

            if(!(map.get(id)).equals(password)) {
            	System.out.println("비밀번호가 일치하지 않습니다. 다시 입력해주세요.");
            } else {
            	System.out.println("id와 비밀번호가 일치합니다.");
                break;
            }
        }
    }
}

실행결과
c:\jdk1.8\work\ch11>java HashMapEx1
id와 password를 입력해주세요.
id : asdf
password : 1111

비밀번호가 일치하지 않습니다. 다시 입력해주세요
id와 password를 입력해주세요.
id : asdf
password : 1234

id와 비밀번호가 일치합니다

c:\jdk1.8\work\ch11>

HashMap을 생성하고 사용자ID와 비밀번호를 키와 값의 쌍(pair)으로 저장한 다음, 입력된 사용자 ID를 키로 HashMap에서 검색해서 얻은 값(비밀번호)을 입력된 비밀번호와 비교하는 예제이다.

싱과 해시함수
해싱이란 해시함수(hash function)를 이용해서 데이터를 해시테이블(hash table)에 저장하고 검색하는 기법을 말한다. 해시함수는 데이터가 저장되어 있는 곳을 알려주기 때문에 다량의 데이터 중에서도 원하는 데이터를 빠르게 찾을 수 있다.

해싱을 구현한 컬렉션 클래스로는 HashSet, HashMap, Hashtable등이 있다.
Hashtable은 컬렉션 프레임웍이 도입되면서 HashMap으로 대체되었으나 이전 소스와의 호환성 문제로 남겨두고 있다. 가능하면 Hashtable 대신 HashMap을 사용하도록 하자.

해싱에서 사용하는 자료구조는 다음과 같이 배열과 링크드 리스트의 조합으로 되어있다.
저장할 데이터의 기를 해시함수에 넣으면 배열의 한 요소를 얻게 되고, 다시 그 곳에 연결되어 있는 링크 리스스에 저장하게 된다.

    1. 검색하고자 하는 값의 키로 해시함수를 호출한다.
    1. 해시함수의 계산 결과(해시코드)로 해당 값이 저장되어 있는 링크드 리스트를 찾는다.
    1. 링크드 리스트에서 검색한 키와 일치하는 데이터를 찾는다.

배열의 인덱스가 n인 요소의 주소 = 배열의 시작주소 + type의 size * n

하나의 링크드 리스트(서럽)에 최소한의 데이터만 저장되려면, 저장될 데이터의 크기를 고려해서 HashMap의 크기를 적절하게 지정해주어야 하고, 해시함수가 서로 다른 키(주민번호)에 대해서 중복된 해시코드(서랍위치)의 반환을 최소화해ㅑ 한다. 그래야 HashMap에서 빠른 검색시간을 얻을 수 있다.

그래서 해싱을 구현하는 과정에서 제일 중요한 것은 해싱함수의 알고리즘이며, 이 예에서 사용된 해시함수의 알고리즘은 주어진 키(주민번호)의 첫 번째 문자를 뽑아서 정수로 반환하기만 하면 되므로 아래와 같이 코드로 표현할 수 있다.

int hashFunction(String key) {
	return Integer.parseInt(Key.substring(0,1);
}

알고리즘이 간단한 만큼 성능은 좋지 않아서 서로 다른 키에 대해서 중복된 해시코드를 반환하는 경우가 많다.

실제로는 HashMap과 같이 해싱을 구현한 컬렉션 클래스에서 Object클래스에 정의된 hashCode()를 해시함수로 사용한다. Object클래스에 정의된 hashCode()는 객체의 주소를 이용하는 알고리즘으로 해시코드를 만들어 내기 때문에 모든 객체에 대해 hashCode()를 호출한 결과가 서로 유일한 훌륭한 방법이다.

String클래스의 경우 Object로부터 상속받은 hashCode()를 오버라이딩해서 문자열의 내용으로 해시코드를 만들어 낸다. 그래서 서로 다른 String인스턴스일지라도 같은 내용의 문자열을 가졌다면 hashCode()를 호출하면 같은 해시코드를 얻는다.

HashSet에서 이미 설명했던 것과 같이 서로 다른 두 객체에 대해 equals()로 비교한 결과가 true인 동시에 hashCode()의 반환값이 같아야 같은 객체로 인식한다. HashMap에서도 같은 방법으로 객체를 구별하며, 이미 존재하는 키에 대한 값을 저장하면 기존의 값을 새로운 값으로 덮어쓴다.

그래서 새로운 클래스를 정의할 때 equals()를 재정의오버라이딩해야 한다면 hashCode()도 같이 재정의해서 equals()의 결과가 true인 두 객체의 해시코드hashCode()의 결과값이 항상 같도록 해주어야 한다.

그렇지 않으면 HashMap과 같이 해싱을 구현한 컬렉션 클래스에서는 equal()의 호출결과가 true지만 해시코드가 다른 두 객체를 서로 다른 것으로 인식하고 따로 저장할 것이다.

1.11 TreeMap

TreeMap은 이름에서 알 수 있듯이 이진 검색트리의 형태로 키와 값의 쌍으로 이루어진 데이터를 저장한다. 그래서 검색과 정렬에 적합한 컬렉션 클래스이다.
한가지 설명할 것이 있다면 HashMap과 TreeMap의 검색성능에 관한 것인데, 검색에 관한 대부분의 경우에서 HashMap이 TreeMap보다 더 뛰어나므로 HashMap을 사용하는 것이 좋다. 다만 범위검색이나 정렬이 필요한 경우에는 TreeMap을 사용하자.

1.12 Properties

Properties는 HashMap의 구버전인 Hashtable을 상속받아 구현한 것으로, Hashtable은 키와 값을(Object, Object)의 형태로 저장하는데 비해 Properties는(String, String)의 형태로 저장하는 보다 단순화된 컬렉션클래스이다.

주로 애플리케이션의 환경설정과 관련된 속성(property)을 저장하는데 사용되며 데이터를 파일로부터 읽고 쓰는 편리한 기능을 제공한다. 그래서 간단한 입출력은 Properties를 활용하면 몇줄의 코드로 쉽게 해결 될 수있다.

Properties의 기본적인 메서드를 이용해서 저장하고 읽어오고 출력하는 방법을 보여주는 간단한 예제이다.
데이트를 저장하는데 사용되는 setProperty()는 단순히 Hashtable의 put 메서드를 호출할 뿐이다. 그리고 setProperty()는 기존에 같은 키로 저장된 값이 있는 경우 그 값을 Object타입으로 반환하며, 그렇지 않을 때는 null을 반환한다.

getProperty()는 Properties에 저장된 값을 읽어오는 일을 하는데, 만일 읽어오려는 키가 존재하지 않으면 지정된 기본값(defaultValue)을 반환한다.

String getProperty(String key)
String getProperty(String key, String defaultValue)

Properties는 Hashtable을 상속받아 구현한 것이라 Map의 특성상 저장순서를 유지하지않기 때문에 예제의 결과에 출력된 순서가 저장순서와는 무관하다는 것을 확인하자.

Properties는 컬렉션프레임웍 이전의 구버전이므로 Iterator가 아닌 Enumeration을 사용한다.
그리고 list메서드를 이용하면 Properties에 저장된 모든 데이터를 화면 또는 파일에 편리하게 출력할 수 있다.

void list(PrintStream out)
void list(PrintWriter out)

1.13 Collections

Arrays가 배열과 관련된 메서드를 제공하는 것 처럼, Collections은 컬렉션과 관련된 메서드를 제공한다.

컬렉션의 동기화
멀티 쓰레드(multi - thread) 프로그래밍에서는 하나의 객체를 여러 쓰레드가 동시에 접근할 수 있기 때문에 데이터의 일관성(consistency)을 유지하기 위해서는 공유되는 객체에 동기화(synchronization)이 필요하다. Vector와 Hashtable과 같은 구버전(JDK1.2 이전)의 클래스들은 자체적으로 동기화 처리가 되어 있는데, 멀티쓰레드 프로그래밍이 아닌 경우에는 불필요한 기능이 되어 성능을 떨어트리는 요인이 된다.

그래서 새로 추가된 ArrayList와 HashMap과 같은 컬렉션은 동기화를 자체적으로 처리하지 않고 필요한 경우에만 java.util.Collections클래스의 동기화 메서드를 이용해서 동기화처리가 가능하도록 변경하였다.
Collections클래스에는 다음과 같은 동기화 메서드를 제공하고 있으므로, 동기화가 필요할 때 해당하는 것을 사용하면 된다.

static Collection synchronizedCollection(Collection c)
static List       synchronizedList(List list)
static Set        synchronizedSet(Set s)
static Map		  synchronizedMap(Map m)
static SortedSet  synchronizedSortedSet(SortedSet s)
static SortedMap  synchronizedSortedMap(SortedMap m)

이들을 사용하는 방법은 다음과 같다.

List syncList = Collections.synchronizedList(new ArrayList(...));

변경불가 컬렉션 만들기
컬렉션에 저장된 데이터를 보호하기 위해서 컬렉션을 변경할 수 없게, 즉 읽기전용으로 만들어야 할 때가 있다. 주로 멀티 쓰레드 프로그래밍에서 여러 쓰레드가 하나의 컬렉션을 공유하다보면 데이터가 손상될 수 있는데, 이를 방지하려면 아래의 메서드들을 이용하자.

static Collection     unmodifiableCollection(Collection c)
static List 	      unmodifiableList(List list)
static Set 		      unmodifiableSet(Set s)
static Map 		      unmodifiableMap(Map m)
static NavigableSet   unmodifiableNavigableSet(NavigableSet s)
static SortedSet      unmodifiableSortedSet(SortedSet s)
satatic NavigableMap  unmodifiableNavigableMap(NavigableMap m)
static SortedMap	  unmodifiableSortedMap(SortedMap m)

싱글톤 컬렉션 만들기
단 하나의 객체만을 저장하는 컬렉션을 만들고 싶을 경우가 있다. 이럴 때는 아래의 메서드를 사용하면 된다.

static List singletonList(Object o)
static Set singleton(Object o)		//singletonSet이 아님
static Map singletonMap(Object key, Object value)

매개변수로 저장할 요소를 지정하면, 해당 요소를 저장하는 컬렉션을 반환한다. 그리고 반환된 컬렉션은 변경할 수 없다.

한 종류의 객체만 저장하는 컬렉션 만들기
컬렉션에 모든 종류의 객체를 저장할 수 있다는 것은 장점이기도하고 단점이기도 하다.
대부분의 경우 한 종류의 객체를 저장하며, 컬렉션에 지정된 종류의 객체만 저장할 수 있도록 제한하고 싶을 때 아래의 메서드를 사용한다.

static Collection   	checkedCollection(Collection c, class type)
static List 	    	checkedList(List list, Class type)
static Set 		   	  	checkedSet(Set s, Class type)
static Map 		    	checkedMap(Map m, Class keyType, class valueType)
static Queue	    	checkedQueue(Queue queue, Class type)
static NavigableSet 	checkedNavigableSet(NavigableSet s, Class type)
static SortedSet    	checkedSortedSet(SortedSet s, Class type)
statia NavigableMap 	checkedNavigableMap(NavigableMap m, Class KeyType, Class valueType)
static SortedMap    	checkedSortedMap(SortedMap m, Class keyType, Class valueType)

사용방법은 다음과 같이 두 번째 매개변수에 저장할 객체의 클래스를 지정하면 된다.

List list = new ArrayList();
List CheckedList = checkedList(list, String.class); //String만 저장가능
checkedList.add("abc)";								//OK.
checkedList.add(new Integer(3));					//에러. ClassCastexception발생

컬렉션에 저장할 요소의 타입을 제한하는 것은 다음 장에서 배울 지네릭스(generics)로 간단히 처리할 수 있는데도 이런 메서드들을 제공하는 이유는 호환성 때문이다.

지네릭스는 JDK1.5부터 도입된 기능이므로 JDK1.5이전에 작성된 코드를 사용할 때는 이 메서드들이 필요 할 수 있다.

1.14 컬렉션 클래스 정리 & 요약

profile
하루하루 최선을

0개의 댓글