컬렉션 계층 구조에서 헤매지 않기

컬렉션 프레임워크는 인터페이스와 클래스의 여러 계층 구조로 나뉩니다. 가장 먼저 이해해야 할 것은 컬렉션 인터페이스 계층 구조입니다.

The Collection Interface Hierarchy

컬렉션 인터페이스 계층 구조

일부 인터페이스가 생략되었는데, 이는 나중에 설명하겠습니다.

 

Iterable 인터페이스

이 계층 구조의 첫 번째 인터페이스는 Iterable 인터페이스이며, 사실 컬렉션 프레임워크의 일부가 아닙니다. 그럼에도 불구하고 여기서 언급할 가치가 있는 이유는 Collection 인터페이스의 상위 인터페이스이므로 이 계층 구조의 모든 인터페이스의 상위 인터페이스이기 때문입니다.

Iterable 인터페이스는 Java SE 5(2004)에 추가되었습니다. Iterable을 구현하는 객체는 반복할 수 있는 객체입니다. 이 인터페이스는 코드의 for each 패턴과 함께 Java SE 5에 추가되었습니다.

Collection의 요소를 반복하는 이 방식에 이미 익숙하실 것입니다:

Collection<String> collection = ...; 
 
for (String element: collection) {
    // do someting with element
}

이 패턴을 사용해 컬렉션이나 배열을 반복할 수 있다는 것은 이미 알고 계실 것입니다. 실제로 Iterable의 모든 인스턴스가 여기에 사용될 수 있다는 것이 밝혀졌습니다.

Iterable 인터페이스를 구현하는 방법은 매우 간단합니다. 다음에 볼 수 있는 다른 인터페이스인 Iterator의 인스턴스를 제공하기만 하면 됩니다.

 

컬렉션 인터페이스를 사용하여 컨테이너에 요소 저장하기

다른 모든 인터페이스는 컨테이너에 요소를 저장하는 것에 관한 것입니다.

ListSet 두 인터페이스는 모두 공통된 동작을 공유하며, 이는 Collection 인터페이스에 의해 모델링됩니다. Collection 인터페이스는 엘리먼트 컨테이너에 대한 여러 연산을 모델링합니다. 기술적인 세부 사항으로 들어가기 전에, Collection으로 할 수 있는 작업은 다음과 같습니다:

  • 요소를 추가하거나 제거합니다;
  • 주어진 요소가 있는지 테스트합니다;
  • 포함된 요소의 개수 또는 이 컬렉션이 비어 있는지 묻습니다.;
  • 이 콘텐츠를 삭제합니다.

Collection은 요소의 집합이므로, Collection 인터페이스에 정의된 집합 연산도 있습니다:

  • 다른 집합에 집합이 포함되는지 테스트합니다.;
  • union;
  • intersection(교차);
  • complement(보완).

마지막으로, Collection 인터페이스는 요소에 액세스하는 다양한 방법을 모델링합니다:

  • iterator를 사용하여 컬렉션의 요소를 반복할 수 있습니다;
  • 이러한 요소에 병렬이 가능한 스트림을 만들 수 있습니다.

물론 이러한 모든 연산은 ListSet에서도 사용할 수 있습니다. 그렇다면 일반 Collection의 인스턴스와 Set 또는 List의 인스턴스의 차이점은 무엇일까요?

 

List로 컬렉션 확장하기

엘리먼트의 List와 엘리먼트의 Collection의 차이점은, List는 엘리먼트가 추가된 순서를 기억한다는 점입니다.

첫 번째 결과는 목록의 요소를 반복하면 가장 먼저 추가되는 요소가 첫 번째 요소가 된다는 것입니다. 그런 다음 모든 요소가 표시될 때까지 두 번째 요소가 표시되는 식으로 반복됩니다. 따라서 요소를 반복하는 순서는 항상 동일하며, 이러한 요소가 추가된 순서에 따라 고정됩니다. 일반 Collection이나 Set에는 이 보장이 적용되지 않습니다.

컬렉션 프레임워크에서 제공하는 Set의 일부 구현이 항상 같은 순서로 요소를 반복하는 것으로 나타났습니다. 이는 우발적인 효과이므로 코드에서 이 동작에 의존해서는 안 됩니다.

첫 번째 결과만큼 명확하지는 않지만 두 번째 결과는 목록의 요소에 인덱스가 있다는 것입니다. Collection에서 첫 번째 요소를 쿼리하는 것은 의미가 없습니다. 목록은 첫 번째 요소를 기억하기 때문에 List에서 첫 번째 요소를 쿼리하는 것은 의미가 있습니다.

이러한 인덱스는 어떻게 처리되나요? 다시 한 번 말씀드리지만, 이것은 구현의 책임입니다. 인터페이스의 첫 번째 역할은 동작을 지정하는 것이지 구현이 이를 어떻게 달성해야 하는지 알려주는 것이 아닙니다.

보시다시피, List 인터페이스는 Collection 인터페이스에 새로운 작업을 추가합니다. 목록의 요소에는 인덱스가 있으므로 해당 인덱스로 다음과 같은 작업을 수행할 수 있습니다.

  • 특정 인덱스에서 요소 가져오기 또는 삭제하기
  • 특정 위치에 요소 삽입 또는 요소 바꾸기
  • 두 인덱스 사이의 요소 범위를 가져옵니다.

 

Set으로 컬렉션 확장하기

엘리먼트의 Set과 엘리먼트의 Collection의 차이점은 Set에는 중복을 가질 수 없다는 것입니다. Collection에는 동일한 클래스의 인스턴스를 여러 개 가질 수 있으며, 심지어 같은 인스턴스를 두 번 이상 가질 수도 있습니다. Set에서는 허용되지 않습니다. 이를 어떻게 적용하는지는 구현자의 책임이며, 이 튜토리얼의 뒷부분에서 확인할 수 있습니다.

이 동작의 결과 중 하나는 Set에 요소를 추가하는 데 실패할 수 있다는 것입니다.

그렇다면 중복을 방지하고 어떤 요소에 인덱스가 있는 컨테이너를 가질 수 있을까요? 대답은 그렇게 간단하지 않습니다. 컬렉션 프레임워크는 항상 같은 순서로 요소를 반복하는 Set의 구현을 제공하지만, 이러한 요소에는 인덱스가 없으므로 이 클래스는 List를 구현하지 않습니다.

이러한 동작의 차이로 인해 Set 인터페이스에서 새로운 작업이 수행되지는 않습니다.

 

정렬된 Set과 탐색 가능한 Set으로 Set의 요소 정렬하기

Set 인터페이스에는 두 가지 확장이 있습니다: SortedSetNavigableSet입니다.

SortedSet 인터페이스는 오름차순으로 정렬된 요소를 유지합니다. 다시 한번 말하지만, 이것이 어떻게 적용되는지는 나중에 보게 되겠지만 구현의 책임입니다.

정렬을 하려면 SortedSet이 요소를 비교해야 합니다. 이를 어떻게 수행할 수 있을까요? 이를 위해 Java 언어에 정의된 두 가지 표준 메커니즘이 있습니다.

요소가 Comparable이더라도 SortedSet을 구축할 때 Comparator를 제공할 수 있습니다. 이는 compareTo() 메서드에서 구현된 순서와 다른 순서로 요소를 정렬해야 하는 경우에 유용할 수 있습니다.

Sorting과 Ordering의 차이점은 무엇인가요? List는 요소를 추가한 순서대로 유지하고, SortedSet은 정렬된 상태로 유지합니다. 요소를 Sorting한다는 것은 주어진 비교 로직의 의미에서 Set을 탐색하는 동안 가장 먼저 얻는 요소가 가장 낮은 요소가 된다는 것을 의미합니다. 요소를 Ordering 한다는것은 목록에 요소를 추가한 순서가 이 목록의 수명 내내 유지된다는 의미입니다. 따라서 목록을 탐색하는 동안 가장 먼저 얻는 요소는 목록에 추가된 첫 번째 요소입니다.

SortedSetSet에 여러 연산을 추가합니다. 다음은 SortedSet으로 수행할 수 있는 작업입니다.

  • 집합의 가장 작은 요소와 가장 큰 요소를 얻을 수 있습니다.
  • 주어진 요소보다 작거나 큰 모든 요소의 headSettailSet을 추출할 수 있습니다.

SortedSet의 요소를 반복하면 가장 작은 요소부터 가장 큰 요소까지 만들어집니다.

NavigableSetSortedSet의 동작을 변경하지 않습니다. 내림차순으로 요소를 반복할 수 있는 기능 등 SortedSet에 몇 가지 유용한 연산이 추가되었습니다. 이에 대한 자세한 내용은 나중에 살펴보겠습니다.