[Java] 컬렉션 프레임워크

JD_S·2022년 11월 28일
0

Java

목록 보기
19/21

컬렉션 프레임워크의 개념

컬렉션 프레임워크(collection framework)란?

자바에서 컬렉션 프레임워크란 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스의 집합을 의미한다. 즉, 데이터를 저장하는 자료 구조와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현해 놓은 것이다. 이러한 컬렉션 프레임워크는 자바의 인터페이스를 사용하여 구현된다.

컬렉션 프레임워크 주요 인터페이스

컬렉션 프레임워크에서는 데이터를 저장하는 자료 구조에 따라 다음과 같은 핵심이 되는 주요 인터페이스를 정의하고 있다.

  • List인터페이스
  • Set인터페이스
  • Map인터페이스

이 중에서 ListSet인터페이스는 모두 Collection인터페이스를 상속받지만, 구조상의 차이로 인해 Map인터페이스는 별도로 정의한다. 따라서 List인터페이스와 Set인터페이스의 공통된 부분을 Collection인터페이스에서 정의하고 있다.

주요 인터페이스 간의 상속 관계

자바에서 컬렉션 프레임워크를 구성하고 있는 인터페이스 간의 상속 관계는 다음 그림과 같다.

위 그림에서 <E><K, V>라는 것은 컬렉션 프레임워크를 구성하는 모든 클래스가 제네릭으로 표현되어 있음을 알려준다. 제네릭의 개념은 제네릭 챕터에서 확인할 수 있다.

주요 인터페이스의 간략한 특징

자바에서 컬렉션 프레임워크를 구성하고 있는 주요 인터페이스의 간략한 특징은 다음과 같다.

컬렉션 클래스

컬렉션 프레임워크에 속하는 인터페이스를 구현한 클래스를 컬렉션 클래스라고 한다. 컬렉션 프레임워크의 모든 컬렉션 클래스는 ListSet, Map 인터페이스 중 하나의 인터페이스를 구현하고 있다. 또한, 클래스 이름에도 구현한 인터페이스의 이름이 포함되므로 바로 구분할 수 있다.

VectorHashtable과 같은 컬렉션 클래스는 예전부터 사용해 왔으므로, 기존 코드와의 호환을 위해 아직도 남아 있다. 하지만 기존에 사용하던 컬렉션 클래스를 사용하는 것보다는 새로 추가된 ArrayListHashMap클래스를 사용하는 것이 성능 면에서도 더 나은 결과를 얻을 수 있다.

아래 코드는 ArrayList클래스를 이용하여 리스트를 생성하고 조작하는 코드이다.

import java.util.*;

public class Collection01 {
  public static void main(String[] args) {
    //리스트 생성
    ArrayList<String> arrList = new ArrayList<String>();
    
    //리스트에 요소의 저장
    arrList.add("넷");
    arrList.add("둘");
    arrList.add("셋");
    arrList.add("하나");
    
    //리스트 요소의 출력
    for (int i = 0; i < arrList.size(); i++) {
      System.out.println(arrList.get(i));
    }
  }
}

결과
넷
둘
셋
하나

Collection 인터페이스

ListSet인터페이스의 많은 공통된 부분을 Collection인터페이스에서 정의하고, 두 인터페이스는 그것을 상속받는다. 따라서 Collection인터페이스는 컬렉션을 다루는데 가장 기본적인 동작들을 정의하고, 그것을 메서드로 제공하고 있다.

Collection인터페이스에서 제공하는 주요 메서드는 다음과 같다.

List 컬렉션 클래스

List 컬렉션 클래스

List인터페이스를 구현한 모든 List컬렉션 클래스는 다음과 같은 특징을 가진다.

  • 요소의 저장 순서가 유지된다.
  • 같은 요소의 중복 저장을 허용한다.

대표적인 List컬렉션 클래스에 속하는 클래스는 다음과 같다.

  • ArrayList<E>
  • LinkedList<E>
  • Vector<E>
  • Stack<E>

ArrayList<E> 클래스

ArrayList클래스는 가장 많이 사용되는 컬렉션 클래스 중 하나이다. JDK 1.2부터 제공된 ArrayList클래스는 내부적으로 배열을 이용하여 요소를 저장한다. ArrayList클래스는 배열을 이용하기 때문에 인덱스를 이용해 배열 요소에 빠르게 접근할 수 있다. 하지만 배열의 크기를 변경할 수 없는 인스턴스이므로, 크기를 늘리기 위해서는 새로운 배열을 생성하고 기존의 요소들을 옮겨야 하는 복잡한 과정을 거쳐야 한다. 물론 이 과정은 자동으로 수행되지만, 요소의 추가 및 삭제 작업에 걸리는 시간이 매우 길어지는 단점을 가지게 된다.

아래 코드는 여러 ArrayList메서드를 이용하여 리스트를 생성하고 조작하는 코드이다.

ArrayList<Integer> arrList = new ArrayList<Integer>();

//add()메서드를 이용한 요소의 저장
arrList.add(40);
arrList.add(20);
arrList.add(30);
arrList.add(10);

//for문과 get()메서드를 이용한 요소의 출력
for (int i = 0; i < arrList.size(); i++) {
  System.out.print(arrList.get(i) + " ");
}

System.out.print("\n");

//remove()메서드를 이용한 요소의 제거
arrList.remove(1);

//Enhanced for문과 get()메서드를 이용한 요소의 출력
for (int e : arrList) {
  System.out.print(e + " ");
}

System.out.print("\n");

//Collections.sort()메서드를 이용한 요소의 정렬
Collections.sort(arrList);

//iterator()메서드와 get()메서드를 이용한 요소의 출력
Iterator<Integer> iter = arrList.iterator();
while (iter.hasNext()) {
  System.out.print(iter.next() + " ");
}

System.out.print("\n");

//set()메서드를 이용한 요소의 변경
arrList.set(0, 20);

for (int e : arrList) {
  System.out.print(e + " ");
}

System.out.print("\n");

//size()메소드를 이용한 요소의 총 개수
System.out.println("리스트의 크기 : " + arrList.size());

결과
40 20 30 10 
40 30 10 
10 30 40 
20 30 40 
리스트의 크기 : 3

위 코드처럼 컬렉션 클래스의 요소를 출력하는 방법에는 for문과 enhanced for문, iterator()메서드를 이용한 방법 등 다양한 방법을 사용할 수 있다.

자바의 Collections클래스는 JDK 1.2부터 제공되는 컬렉션에서 동작하거나 컬렉션을 반환하는 클래스 메서드만으로 구성된 클래스이다.

자바의 Collection은 인터페이스이며, Collections는 클래스임을 주의해야 한다.

LinkedList<E> 클래스

LinkedList클래스는 ArrayList클래스가 배열을 이용하여 요소를 저장함으로써 발생하는 단점을 극복하기 위해 고안되었다. JDK 1.2부터 제공된 LinkedList클래스는 내부적으로 연결 리스트를 이용하여 요소를 저장한다. 배열은 저장된 요소가 순차적으로 저장된다. 하지만 연결 리스트는 저장된 요소가 비순차적으로 분포되며, 이러한 요소들 사이를 링크로 연결하여 구성한다. 다음 요소를 가리키는 참조만을 가지는 연결 리스트를 단일 연결 리스트라고 한다.

이러한 단일 연결 리스트는 요소의 저장과 삭제 작업이 다음 요소를 가리키는 참조만 변경하면 되므로, 아주 빠르게 처리될 수 있다. 하지만 단일 연결 리스트는 현재 요소에서 이전 요소로 접근하기가 매우 어렵다. 따라서 이전 요소를 가리키는 참조도 가지는 이중 연결 리스트가 좀 더 많이 사용된다.

LinkedList클래스도 위와 같은 이중 연결 리스트를 내부적으로 구현한 것이다. 또한, LinkedList클래스 역시 List인터페이스를 구현하므로, ArrayList클래스와 사용할 수 있는 메서드가 거의 같다.

아래 코드는 여러 LinkedList메서드를 이용하여 리스트를 생성하고 조작하는 코드이다.

LinkedList<String> lnkList = new LinkedList<String>();

//add()메서드를 이용한 요소의 저장
lnkList.add("넷");
lnkList.add("둘");
lnkList.add("셋");
lnkList.add("하나");

//for문과 get()메서드를 이용한 요소의 출력
for (int i = 0; i < lnkList.size(); i++) {
  System.out.print(lnkList.get(i) + " ");
}

System.out.println();

//remove()메서드를 이용한 요소의 제거
lnkList.remove(1);

//Enhanced for문과 get()메서드를 이용한 요소의 출력
for (String e : lnkList) {
  System.out.print(e + " ");
}

System.out.println();

//set()메서드를 이용한 요소의 변경
lnkList.set(2, "둘");

for (String e : lnkList) {
  System.out.print(e + " ");
}

System.out.println();

//size()메서드를 이용한 요소의 총 개수
System.out.println("리스트의 크기 : " + lnkList.size());

결과
넷 둘 셋 하나 
넷 셋 하나 
넷 셋 둘 
리스트의 크기 : 3

위 코드를 살펴보면 앞선 코드와 연결 리스트를 생성하는 한 줄의 코드만이 다른 것을 확인할 수 있다. 이처럼 ArrayListLinkedList의 차이는 사용 방법이 아닌, 내부적으로 요소를 저장하는 방법에 있다.

Vector<E> 클래스

Vector클래스는 JDK 1.0부터 사용해 온 ArrayList클래스와 같은 동작을 수행하는 클래스이다. 현재의 Vector클래스는 ArrayList클래스와 마찬가지로 List인터페이스를 상속받는다. 따라서 Vector클래스에서 사용할 수 있는 메서드는 ArrayList클래스에서 사용할 수 있는 메서드와 거의 같다. 하지만 현재에는 기존 코드와의 호환성을 위해서만 남아있으므로, Vector클래스보다는 ArrayList클래스를 사용하는 것이 좋다.

List 인터페이스 메서드

List인터페이스는 Collection인터페이스를 상속받으므로, Collection인터페이스에서 정의한 메서드도 모두 사용할 수 있다. List인터페이스에서 제공하는 주요 메서드는 다음과 같다.

Stack과 Queue

Stack<E> 클래스

Stack클래스는 List컬렉션 클래스의 Vector클래스를 상속받아, 전형적인 스택 메모리 구조의 클래스를 제공한다. 스택 메모리 구조는 선형 메모리 공간에 데이터를 저장하면서 후입선출(LIFO)의 시멘틱을 따르는 자료 구조이다. 즉, 가장 나중에 저장된(push) 데이터가 가장 먼저 인출(pop)되는 구조이다.

Stack클래스는 스택 메모리 구조를 표현하기 위해, Vector클래스의 메서드를 5개만 상속받아 사용한다.

더욱 복잡하고 빠른 스택을 구현하고 싶다면 Deque인터페이스를 구현한 ArrayDeque클래스를 사용하면 된다.

Deque<Integer> st = new ArrayDeque<Integer>();

아래 코드는 여러 Stack메서드를 이용하여 스택 메모리 구조를 구현한 코드이다.

Stack<Integer> st = new Stack<Integer>(); //스택의 생성
//Deque<Integer> st = new ArrayDeque<Integer>();

//push()메서드를 이용한 요소의 저장
st.push(4);
st.push(2);
st.push(3);
st.push(1);

//peek()메서드를 이용한 요소의 반환
System.out.println(st.peek());
System.out.println(st);

//pop()메서드를 이용한 요소의 반환 및 제거
System.out.println(st.pop());
System.out.println(st);

//search()메서드를 이용한 요소의 위치 검색
System.out.println(st.search(4));
System.out.println(st.search(3));

결과
1
[4, 2, 3, 1]
1
[4, 2, 3]
3
1

단, ArrayDeque클래스는 Stack클래스와는 달리 search()메서드는 지원하지 않는다.

Queue<E> 인터페이스

클래스로 구현된 스택과는 달리 자바에서 큐 메모리 구조는 별도의 인터페이스 형태로 제공된다. 이러한 Queue인터페이스를 상속받는 하위 인터페이스는 다음과 같다.

  • Deque<E>
  • BlockingDeque<E>
  • BlockingQueue<E>
  • TransferQueue<E>

따라서 Queue인터페이스를 직간접적으로 구현한 클래스는 상당히 많다. 그중에서도 Deque인터페이스를 구현한 LinkedList클래스가 큐 메모리 구조를 구현하는데 가장 많이 사용된다. 큐 메모리 구조는 선형 메모리 공간에 데이터를 저장하면서 선입선출(FIFO)의 시멘틱을 따르는 자료 구조이다. 즉, 가장 먼저 저장된(push) 데이터가 가장 먼저 인출(pop)되는 구조이다.

Queue인터페이스는 큐 메모리 구조를 표현하기 위해, 다음과 같은 Collection인터페이스 메서드만을 상속받아 사용한다.

더욱 복잡하고 빠른 큐를 구현하고 싶다면 Deque인터페이스를 구현한 ArrayDeque클래스를 사용하면 된다.

Deque<Integer> qu = new ArrayDeque<Integer>();

아래 코드는 여러 LinkedList메서드를 이용하여 큐 메모리 구조를 구현한 코드이다.

LinkedList<String> qu = new LinkedList<String>(); //큐의 생성
//Deque<String> qu = new ArrayDeque<String>();

//add()메서드를 이용한 요소의 저장
qu.add("넷");
qu.add("둘");
qu.add("셋");
qu.add("하나");

//peek()메서드를 이용한 요소의 반환
System.out.println(qu.peek());
System.out.println(qu);

//poll()메서드를 이용한 요소의 반환 및 제거
System.out.println(qu.poll());
System.out.println(qu);

//remove()메서드를 이용한 요소의 제거
qu.remove("하나");
System.out.println(qu);

결과
넷
[,,, 하나][,, 하나]
[,]

Java SE 6부터 지원되는 ArrayDeque클래스는 스택과 큐 메모리 구조를 모두 구현하는데 가장 적합한 클래스이다.

Set 컬렉션 클래스

Set 컬렉션 클래스

Set인터페이스를 구현한 모든 Set컬렉션 클래스는 다음과 같은 특징을 가진다.

  • 요소의 저장 순서를 유지하지 않는다.
  • 같은 요소의 중복 저장을 허용하지 않는다.

대표적인 Set컬렉션 클래스에 속하는 클래스는 다음과 같다.

  • HashSet<E>
  • TreeSet<E>

HashSet<E> 클래스

HashSet클래스는 Set컬렉션 클래스에서 가장 많이 사용되는 클래스 중 하나이다. JDK 1.2부터 제공된 HashSet클래스는 해시 알고리즘을 사용하여 검색 속도가 매우 빠르다. 이러한 HashSet클래스는 내부적으로 HashMap인스턴스를 이용하여 요소를 저장한다. HashSet클래스는 Set인터페이스를 구현하므로, 요소를 순서에 상관없이 저장하고 중복된 값은 저장하지 않는다. 만약 요소의 저장 순서를 유지해야 한다면 JDK 1.4부터 제공하는 LinkedHashSet클래스를 사용하면 된다.

아래 코드는 여러 HashSet메서드를 이용하여 집합을 생성하고 조작하는 코드이다.

HashSet<String> hs01 = new HashSet<String>();
HashSet<String> hs02 = new HashSet<String>();

//add()메서드를 이용한 요소의 저장
hs01.add("홍길동");
hs01.add("이순신");
System.out.println(hs01.add("임꺽정"));
System.out.println(hs01.add("임꺽정")); //중복된 요소의 저장

//Enhanced for문과 get() 메서드를 이용한 요소의 출력
for (String e : hs01) {
  System.out.print(e + " ");
}

System.out.println();

//add()메서드를 이용한 요소의 저장
hs02.add("임꺽정");
hs02.add("홍길동");
hs02.add("이순신");

//iterator()메서드를 이용한 요소의 출력
Iterator<String> iter02 = hs02.iterator();
while (iter02.hasNext()) {
  System.out.print(iter02.next() + " ");
}

System.out.println();

//size()메서드를 이용한 요소의 총 개수
System.out.println("집합의 크기 : " + hs02.size());

Collection인터페이스에서는 Iterator인터페이스를 구현한 클래스의 인스턴스를 반환하는 iterator()메서드를 정의하여 각 요소에 접근하도록 하고 있다.

위 코드에서 요소의 저장 순서를 바꿔도 저장되는 순서에는 영향을 미치지 않는 것을 확인할 수 있다. 또한, add()메서드를 사용하여 해당 HashSet에 이미 존재하는 요소를 추가하려고 하면, 해당 요소를 저장하지 않고 false를 반환하는 것을 볼 수 있다.

이때 해당 HashSet에 이미 존재하는 요소인지를 파악하기 위해서는 내부적으로 다음과 같은 과정을 거치게 된다.

  • 해당 요소에서 hashCode()메서드를 호출하여 반환된 해시값으로 검색할 범위를 결정한다.
  • 해당 범위 내의 요소들을 equals()메서드로 비교한다.

따라서 HashSet에서 add()메서드를 사용하여 중복 없이 새로운 요소를 추가하기 위해서는 hashCoed()equals()메서드를 상황에 맞게 오버라이딩해야 한다.

해시 알고리즘

해시 알고리즘이란 해시 함수를 사용하여 데이터를 해시 테이블에 저장하고, 다시 그것을 검색하는 알고리즘이다.

자바에서 해시 알고리즘을 이용한 자료 구조는 위의 그림과 같이 배열과 연결 리스트로 구현된다. 저장할 데이터의 키값을 해시 함수에 넣어 반환되는 값으로 배열의 인덱스를 구한다. 그리고서 해당 인덱스에 저장된 연결 리스트에 데이터를 저장하게 된다.

예를 들어, 정수형 데이터를 길이가 10인 배열에 저장한다고 한다면 1,000,002를 검색하는 방법은 다음과 같을 수 있다. 1,000,002를 10으로 나눈 나머지가 2이므로 배열의 세 번째 요소에 연결된 연결 리스트에서 검색을 시작한다. 매우 간략화한 예제이지만 이렇게 해서 알고리즘을 이용하면 매우 빠르게 검색 작업을 수행할 수 있다.

TreeSet<E> 클래스

TreeSet클래스는 데이터가 정렬된 상태로 저장되는 이진 탐색 트리의 형태로 요소를 저장한다. 이진 탐색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠르다. JDK 1.2부터 제공되는 TreeSet클래스는 NavigableSet인터페이스를 기존의 이진 탐색 트리의 성능을 향상시킨 레드-블랙 트리로 구현한다. TreeSet클래스는 Set인터페이스를 구현하므로, 요소를 순서에 상관없이 저장하고 중복된 값은 저장하지 않는다.

이진 탐색 트리 : 왼쪽 자식은 부모보다 작고, 오른쪽 자식은 부모보다 큰 이진 트리

레드-블랙 트리 : 이진 탐색 트리가 편향되지 않게 삽입과 삭제를 개선한 이진 탐색 트리

아래 코드는 여러 TreeSet메서드를 이용하여 집합을 생성하고 조작하는 코드이다.

TreeSet<Integer> ts = new TreeSet<Integer>();

//add()메서드를 이용한 요소의 저장
ts.add(30);
ts.add(40);
ts.add(20);
ts.add(10);

//Enhanced for문과 get()메서드를 이용한 요소의 출력
for (int e : ts) {
  System.out.print(e + " ");
}

System.out.println();

//remove()메서드를 이용한 요소의 제거
ts.remove(40);

//iterator()메서드를 이용한 요소의 출력
Iterator<Integer> iter = ts.iterator();
while (iter.hasNext()) {
  System.out.print(iter.next() + " ");
}

System.out.println();

//size()메서드를 이용한 요소의 총 개수
System.out.println("이진 검색 트리의 크기 : " + ts.size());

//subSet()메서드를 이용한 부분 집합의 출력
System.out.println(ts.subSet(10, 20)); //(1)
System.out.println(ts.subSet(10, true, 20, true)); //(2)

결과
10 20 30 40 
10 20 30 
이진 검색 트리의 크기 : 3
[10]
[10, 20]

위 코드처럼 TreeSet인스턴스에 저장되는 요소들은 모두 정렬된 상태로 저장된다. 또한, 위 코드에서 사용된 subSet()메서드는 TreeSet인스턴스에 저장되는 요소가 모두 정렬된 상태이기에 동작이 가능한 해당 트리의 부분 집합만을 보여주는 메서드이다.

(1)번 라인에서 사용된 subSet()메서드는 첫 번째 매개변수로 전달된 값에 해당하는 요소부터 시작하여 두 번째 매개변수로 전달된 값에 해당하는 요소의 바로 직전 요소까지를 반환한다.
(2)번 라인에서 사용된 subSet()메서드는 두 번째와 네 번째 매개변수로 각각 첫 번째와 세 번째 매개변수로 전달된 값에 해당하는 요소를 포함할 것인지 아닌지를 명시할 수 있다.
즉, (2)번 라인에서 네 번째 매개변수를 false로 변경하면 20을 포함하지 않게 되므로, (1)번 라인과 같은 결과를 출력할 것이다.

원형
1. public NavigableSet<E> subSet(E fromElement, E toElement)
2. public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)

Set 인터페이스 메서드

Set인터페이스는 Collection인터페이스를 상속받으므로, Collection인터페이스에서 정의한 메서드도 모두 사용할 수 있다. Set인터페이스에서 제공하는 주요 메서드는 다음과 같다.

Map 컬렉션 클래스

Map 컬렉션 클래스

Map인터페이스는 Collection인터페이스와는 다른 저장 방식을 가진다. Map인터페이스를 구현한 Map컬렉션 클래스들은 키와 값을 하나의 쌍으로 저장하는 방식(key-value 방식)을 사용한다. 여기서 키란 실질적인 값을 찾기 위한 이름의 역할을 한다.

Map인터페이스를 구현한 모든 Map컬렉션 클래스는 다음과 같은 특징을 가진다.

  • 요소의 저장 순서를 유지하지 않는다.
  • 키는 중복을 허용하지 않지만, 값의 중복은 허용한다.

대표적인 Map컬렉션 클래스에 속하는 클래스는 다음과 같다.

  • HashMap<K, V>
  • Hashtable<K, V>
  • TreeMap<K, V>

HashMap<K, V> 클래스

HashMap클래스는 Map컬렉션 클래스에서 가장 많이 사용되는 클래스 중 하나이다. JDK 1.2부터 제공된 HashMap클래스는 해시 알고리즘을 사용하여 검색 속도가 매우 빠르다. HashMap클래스는 Map인터페이스를 구현하므로, 중복된 키로는 값을 저장할 수 없다. 하지만 같은 값을 다른 키로 저장하는 것은 가능하다.

아래 코드는 여러 HashMap메서드를 이용하여 맵을 생성하고 조작하는 코드이다.

HashMap<String, Integer> hm = new HashMap<String, Integer>();

//put()메서드를 이용한 요소의 저장
hm.put("삼십", 30);
hm.put("십", 10);
hm.put("사십", 40);
hm.put("이십", 20);

//Enhanced for문과 get()메서드를 이용한 요소의 출력
System.out.println("맵에 저장된 키들의 집합 : " + hm.keySet());
for (String key : hm.keySet()) {
  System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}

System.out.println();

//remove()메서드를 이용한 요소의 제거
hm.remove("사십");

//iterator()메서드와 get()메서드를 이용한 요소의 출력
Iterator<String> keys = hm.keySet().iterator();
while (keys.hasNext()) {
  String key = keys.next();
  System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}

System.out.println();

//replace()메서드를 이용한 요소의 수정
hm.replace("이십", 200);

for (String key : hm.keySet()) {
  System.out.println(String.format("키 : %s, 값 : %s", key, hm.get(key)));
}

System.out.println();

//size()메서드를 이용한 요소의 총 개수
System.out.println("맵의 크기 : " + hm.size());

결과
맵에 저장된 키들의 집합 : [이십, 삼십, 사십,]: 이십,: 20: 삼십,: 30: 사십,: 40:,: 10: 이십,: 20: 삼십,: 30:,: 10: 이십,: 200: 삼십,: 30:,: 10

맵의 크기 : 3

위 코드에서 사용된 keySet()메서드는 해당 맵에 포함된 모든 키 값들을 하나의 집합(Set)으로 반환해 준다.

위 코드에서 사용한 for문과 JDK 1.5부터 추가된 Enhanced for문이다. 이 반복문은 배열과 컬렉션 프레임워크에서 해당 인스턴스에 저장된 모든 요소를 순회해야할 경우에 자주 사용된다.

Hashtable<K, V> 클래스

Hashtable클래스는 JDK 1.0부터 사용해 온 HashMap클래스와 같은 동작을 하는 클래스이다. 현재의 Hashtable클래스는 HashMap클래스와 마찬가지로 Map인터페이스를 상속받는다. 따라서 Hashtable클래스에서 사용할 수 있는 메서드는 HashMap클래스에서 사용할 수 있는 메서드와 거의 같다. 하지만 현재에는 기존 코드와의 호환성을 위해서만 남아있으므로, Hashtable클래스보다는 HashMap클래스를 사용하는 것이 좋다.

TreeMap<K, V> 클래스

TreeMap클래스는 키와 값을 한 쌍으로 하는 데이터를 이진 탐색 트리의 형태로 저장한다. 이진 탐색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠르다. JDK 1.2부터 제공된 TreeMap클래스는 NavigableMap인터페이스를 기존의 이진 탐색 트리의 성능을 향상시킨 레드-블랙 트리로 구현한다. TreeMap클래스는 Map인터페이스를 구현하므로, 중복된 키로는 값을 저장할 수 없다. 하지만 같은 값을 다른 키로 저장하는 것은 가능하다.

아래 코드는 여러 TreeMap메서드를 이용하여 맵을 생성하고 조작하는 코드이다.

TreeMap<Integer, String> tm = new TreeMap<Integer, String>();

//put()메서드를 이용한 요소의 저장
tm.put(30, "삼십");
tm.put(10, "십");
tm.put(40, "사십");
tm.put(20, "이십");

//Enhanced for문과 get()메서드를 이용한 요소의 출력
System.out.println("맵에 저장된 키들의 집합 : " + tm.keySet());
for (Integer key : tm.keySet()) {
  System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}

System.out.println();

//remove()메서드를 이용한 요소의 제거
tm.remove(40);

//iterator()메서드와 get()메서드를 이용한 요소의 출력
Iterator<Integer> keys = tm.keySet().iterator();
while (keys.hasNext()) {
  Integer key = keys.next();
  System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}

System.out.println();

//replace()메서드를 이용한 요소의 수정
tm.replace(20, "twenty");

for (Integer key : tm.keySet()) {
  System.out.println(String.format("키 : %s, 값 : %s", key, tm.get(key)));
}

System.out.println();

//size()메서드를 이용한 요소의 총 개수
System.out.println("맵의 크기 : " + tm.size());

결과
맵에 저장된 키들의 집합 : [10, 20, 30, 40]: 10,: 십
키 : 20,: 이십
키 : 30,: 삼십
키 : 40,: 사십

키 : 10,: 십
키 : 20,: 이십
키 : 30,: 삼십

키 : 10,: 십
키 : 20,: twenty
키 : 30,: 삼십

맵의 크기 : 3

HashMap<K, V> 메서드

HashMap<K,V>클래스에서 제공하는 주요 메서드는 다음과 같다.

자바 공식 문서에서는 키와 값으로 구성되는 데이터를 매핑(mapping) 또는 엔트리(entry)라고 기술하고 있다.

TreeMap<K, V> 메서드

TreeMap<K, V>클래스에서 제공하는 주요 메서드는 다음과 같다.

Map.Entry인터페이스는 Map인터페이스의 내부 인터페이스로 맵에 저장되는 엔트리의 조작을 위한 메서드가 정의되어 있다.

Iterator

Iterator<E> 인터페이스

자바의 컬렉션 프레임워크는 컬렉션에 저장된 요소를 읽어오는 방법을 Iterator인터페이스로 표준화하고 있다. Collection인터페이스에서는 Iterator인터페이스를 구현한 클래스의 인스턴스를 반환하는 iterator()메서드를 정의하여 각 요소에 접근하도록 하고 있다. 따라서 Collection인터페이스를 상속받는 ListSet인터페이스에서도 iterator()메서드를 사용할 수 있다.

아래 코드는 연결 리스트를 반복자(iterator)를 사용하여 순회하는 코드이다.

LinkedList<Integer> InkList = new LinkedList<Integer>();

InkList.add(4);
InkList.add(2);
InkList.add(3);
InkList.add(1);

Iterator<Integer> iter = InkList.iterator();

while (iter.hasNext()) {
  System.out.print(iter.next() + " ");
}

결과
4 2 3 1

Iterator인터페이스는 다음과 같은 메서드를 사용하여 컬렉션의 각 요소에 접근할 수 있다.

하지만 현재 자바에서는 될 수 있으면 JDK 1.5부터 추가된 Enhanced for문을 사용하도록 권장하고 있다. Enhanced for문을 사용하면 같은 성능을 유지하면서도 코드의 명확성을 확보하고 발생할 수 있는 버그를 예방해 준다. 하지만 요소의 선택적 제거나 대체 등을 수행하기 위한 경우에는 반복자를 사용해야만 한다.

Enumeration<E> 인터페이스

Enumeration인터페이스는 JDK 1.0부터 사용해 온 Iterator인터페이스와 같은 동작을 하는 인터페이스이다. Enumeration인터페이스는 hasMoreElements()nextElement()메서드를 사용하여 Iterator와 같은 작업을 수행한다. 하지만 현재에는 기존 코드와의 호환성을 위해서만 남아있으므로, Enumeration인터페이스보다는 Iterator인터페이스를 사용하는 것이 좋다.

ListIterator<E> 인터페이스

ListIterator인터페이스는 Iterator인터페이스를 상속받아 여러 기능을 추가한 인터페이스이다. Iterator인터페이스는 컬렉션의 요소에 접근할 때 한 방향으로만 이동할 수 있다. 하지만 JDK 1.2부터 제공된 ListIterator인터페이스는 컬렉션 요소의 대체, 추가 그리고 인덱스 검색 등을 위한 작업에서 양방향으로 이동하는 것을 지원한다. 단, ListIterator인터페이스는 List인터페이스를 구현한 List컬렉션 클래스에서만 listIterator()메서드를 통해 사용할 수 있다.

아래 코드는 리스트 반복자를 사용하여 리스트의 모든 요소를 각각 순방향과 역방향으로 출력하는 코드이다.

LinkedList<Integer> InkList = new LinkedList<Integer>();

InkList.add(4);
InkList.add(2);
InkList.add(3);
InkList.add(1);

ListIterator<Integer> iter = InkList.listIterator();
while (iter.hasNext()) {
  System.out.print(iter.next() + " ");
}

while (iter.hasPrevious()) {
  System.out.print(iter.previous() + " ");
}

결과
4 2 3 1
1 3 2 4

ListIterator인터페이스는 다음과 같은 메서드를 사용하여 컬렉션의 각 요소에 접근할 수 있다.

Comparable

Comparable<T> 인터페이스

Comparable인터페이스는 객체를 정렬하는 데 사용되는 메서드인 compareTo()메서드를 정의하고 있다. 자바에서 같은 타입의 인스턴스를 서로 비교해야만 하는 클래스들은 모두 Comparable인터페이스를 구현하고 있다. 따라서 Boolean을 제외한 래퍼 클래스나 String, Time, Date와 같은 클래스의 인스턴스는 모두 정렬 가능하다. 이때 기본 정렬 순서는 작은 값에서 큰 값으로 정렬되는 오름차순이 된다.

아래 코드는 인스턴스의 비교를 위해 사용자 정의 클래스인 Car클래스가 Comparable인터페이스를 구현하는 코드이다.

class Car implements Comparable<Car> {
    private String modelName;
    private int modelYear;
    private String color;
 
    Car(String mn, int my, String c) {
        this.modelName = mn;
        this.modelYear = my;
        this.color = c;
    }

    public String getModel() {
        return this.modelYear + "식 " + this.modelName + " " + this.color;
    }

    public int compareTo(Car obj) {
        if (this.modelYear == obj.modelYear) {
            return 0;
        } else if(this.modelYear < obj.modelYear) {
            return -1;
        } else {
            return 1;
        }
    }
}

public class Comparable01 {
    public static void main(String[] args) {
        Car car01 = new Car("아반떼", 2016, "노란색");
        Car car02 = new Car("소나타", 2010, "흰색");

        System.out.println(car01.compareTo(car02));
    }
}

Comparable인터페이스는 다음과 같은 메서드를 사용하여 객체를 정렬한다.

Comparator<T> 인터페이스

Comparator인터페이스는 Comparable인터페이스와 같이 객체를 정렬하는 데 사용되는 인터페이스이다. Comparable인터페이스를 구현한 클래스는 기본적으로 오름차순으로 정렬된다. 반면에 Comparator인터페이스는 내림차순이나 아니면 다른 기준으로 정렬하고 싶을 때 사용할 수 있다. 즉, Comparator인터페이스를 구현하면 오름차순 이외의 기준으로도 정렬할 수 있게 되는 것이다. 이때 Comparator인터페이스를 구현한 클래스에서는 compare()메서드를 재정의하여 사용하게 된다.

아래 코드는 요소를 내림차순으로 정렬하여 저장하는 TreeSet인스턴스를 생성하기 위해 Comparator인터페이스를 구현하는 코드이다.

import java.util.*;
 
class DescendingOrder implements Comparator<Integer> {
    public int compare(Integer o1, Integer o2) {
        if(o1 instanceof Comparable && o2 instanceof Comparable) {
            Integer c1 = (Integer)o1;
            Integer c2 = (Integer)o2;
            return c2.compareTo(c1);
        }
        return -1;
    }
}
 
public class Comparable02 {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<Integer>(new DescendingOrder());
 
        ts.add(30);
        ts.add(40);
        ts.add(20);
        ts.add(10);

        Iterator<Integer> iter = ts.iterator();
        while(iter.hasNext()) {
            System.out.println(iter.next());
        }
    }
}

결과
40
30
20
10

Comparator인터페이스는 다음과 같은 메서드를 사용하여 객체를 정렬한다.

Reference

profile
Whatever does not destroy me makes me stronger.

0개의 댓글