Java

[Java] Collection

림 림 2021. 10. 5. 18:06
반응형

Oracle Java 11 API와 다음 문서를 참고하여 공부하였습니다.

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collection.html

 

Collection (Java SE 11 & JDK 11 )

Compares the specified object with this collection for equality. While the Collection interface adds no stipulations to the general contract for the Object.equals, programmers who implement the Collection interface "directly" (in other words, create a clas

docs.oracle.com

Index

        1. Class Hierarchy
          1. 인터페이스 계층 구조
          2. 클래스, 추상 클래스 포함 계층 구조
          3. java.lang.Iterable<T> 인터페이스
          4. java.lang.Collection<E> 인터페이스
          5. java.util.List<E> 인터페이스
          6. java.util.Queue<E> 인터페이스
          7. java.util.Set<E> 인터페이스
        2. Interface Collection<E> 의 특징
          1. Type Parameter E
          2. Collection 구현 클래스의 생성자 컨벤션
          3. Optional Operator
          4. equals() 메소드와 컬렉션 프레임워크 인터페이스의 멤버 메소드와의 관계
        3. Collection<E>의 멤버 메소드
          1. int size()
          2. boolean isEmpty()
          3. boolean contains(Object o)
          4. Iterator<E> iterator()
          5. Object[] toArray()
          6. <T> T[] toArray(T[] a)
          7. dafault <T> T[] toArray(IntFunction<T[]> generator)
          8. boolean add(E e)
          9. boolean remove(Object o)
          10. boolean containsAll(Collection<?> c)
          11. boolean addAll(Collection<? extends E> c)
          12. boolean removeAll(Collection<?> c)
          13. default boolean removeIf(Predicate<? suber E> filter)
          14. boolean retainAll(Collection<?> c)
          15. void clear()
          16. boolean equals(Object o)
          17. int hashCode()
          18. default Spliterator<E> spliterator()
          19. default Stream<E> stream()
          20. default Stream<E> parallelStream()

 

1. Class Hierarchy

1-1. 인터페이스 계층 구조

참고: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/package-tree.html

 

1-2. 클래스, 추상 클래스 포함 계층 구조

참고: https://en.wikipedia.org/wiki/Java_collections_framework

Collection 인터페이스의 계층 구조는 위의 그림과 같습니다.

 

1-3. java.lang.Iterable<T> 인터페이스

Collection 인터페이스는 java.lang의 Iterable<T> 인터페이스를 확장하고 있습니다. Iterable<T> 인터페이스는 최상위 인터페이스로, 반복자를 통해 T 타입의 원소를 순회할 수 있도록 하는 인터페이스입니다. Iterable<T> 인터페이스를 구현하는 클래스는 for-each 문으로 원소를 순회할 수 있습니다.

 

1-4. java.lang.Collection<E> 인터페이스

java.lang의 Iterable<T> 인터페이스를 확장하는 java.util의 Collection<E> 인터페이스는 자바 컬렉션 프레임워크의 최상위 인터페이스입니다. 자바 컬렉션 프레임워크란 원소(클래스)를 담는 그룹인 컬렉션 자료구조를 표현하는 클래스와 인터페이스들을 말합니다. 서브 인터페이스들로는 java.util.List<E>, java.util.Queue<E>, java.util.Set<E>가 있습니다. 컬렉션에 따라 중복을 허용하는지, 순서가 있는지에 대한 특징들을 가지고 있습니다.

 

1-5. java.util.List<E> 인터페이스

List는 순서가 있는 컬렉션입니다. List의 원소들은 저장된 순서에 따른 정수형의 인덱스로 접근할 수 있습니다. 

List는 원소의 중복을 허용합니다. 원소 e1과 e2에 대해, e1.equals(e2)가 true인 원소의 저장이 가능합니다. 또, 여러 null값이나 null을 나타내는 원소를 저장할 수 있습니다.

List 인터페이스를 구현하는 클래스들로는 다음과 같은 것들이 있습니다. 이 중에 저에게 익숙한 클래스들로는 ArrayList, LinkedList, Stack, Vector가 있습니다.

 

1-6. java.util.Queue<E> 인터페이스

Queue는 FIFO (first-in-first-out) 형태로 삽입과 삭제가 이루어지는 컬렉션입니다. 이 중에 Priority Queue는 예외입니다. 우선순위 큐는 큐에 저장된 순서가 아니라 제공되는 Comparator에 의해 식별된 우선순위에 따라 삭제가 이루어집니다.

Queue는 기본적인 컬렉션 메소드 외에 추가적인 삽입(Insert), 삭제(Remove), 검사(Examine) 작업을 제공합니다. 각각의 작업들은 두 가지 형태로 나뉘어집니다. 하나는 수행에 실패하면 예외를 발생시키는 것이고, 다른 하나는 null이나 false와 같은 특정 값을 리턴하는 방식입니다. 각각의 메소드는 아래와 같습니다.

Queue 인터페이스를 구현하는 클래스들로는 다음과 같은 것들이 있습니다.

 

1-7. java.util.Set<E> 인터페이스

Set은 수학에서의 집합을 추상화한 컬렉션입니다. 집합의 정의에서 알 수 있듯이, Set 컬렉션은 중복을 허용하지 않습니다. 따라서 Set을 구현하는 클래스의 생성자는 중복 원소가 없는 Set 클래스를 생성하도록 구현해야 합니다.

Set 인터페이스를 구현하는 클래스들로는 다음과 같은 것들이 있습니다.

 

지금까지 자바 컬렉션 프레임워크의 계층 구조와 간단한 구성 요소들을 살펴보았습니다. 이제 Collection 인터페이스에 대해 더 자세히 알아보겠습니다.

 

2. Interface Collection<E> 의 특징

2-1. Type Parameter E

타입 파라미터 E는 Collection의 원소의 타입을 나타냅니다.

 

2-2. Collection 구현 클래스의 생성자 컨벤션

Collection 인터페이스를 구현하는 클래스의 생성자는 2가지의 표준 생성자를 가져야 합니다.

  • 파라미터가 없는 기본 생성자:  원소가 없는 빈 구현 타입의 컬렉션 클래스를 리턴합니다.
  • 하나의 파리미터를 가지는 생성자: Collection 타입의 하나의 파라미터를 가지는 생성자입니다. 파라미터로 받은 Collection과 같은 원소들을 가지는 구현 타입의 컬렉션 클래스를 리턴합니다.

인터페이스는 생성자를 가질 수 없기 때문에, 이 규칙을 강제하는 방법은 없습니다. 그래도 이 규칙을 알고 지키는 것이 좋습니다.

 

2-3. Optional Operator

만약 Collection 구현 클래스가 특정 메소드를 구현하지 않을 것이라면, UnsupportedOperationException 예외를 발생시키는 메소드로 정의해야 합니다. 이런 메소드들은 Collection 인터페이스의 JavaDocs에 "optional operation"이라고 명세되어 있습니다.

예를 들어, Collection 인터페이스의 add() 메소드의 JavaDocs 첫 번째 줄을 보면 optional operation이라고 되어 있습니다.

그리고 Collection 인터페이스를 구현하는 java.util.AbstractCollection<E> 클래스에서는 add() 메소드를 구현하고 있지 않습니다. 따라서 다음과 같이 UnsupportedOperationException 예외를 발생시키고 있습니다.

 

2-4. equals() 메소드와 컬렉션 프레임워크 인터페이스의 멤버 메소드와의 관계

컬렉션 프레임워크 인터페이스의 많은 멤버 베소드들은 equals() 메소드와 의미적으로 밀접하게 연관되어 있습니다. 예를 들어, Collection 인터페이스의 contains(Object o) 메소드는 o.equals(e) 인 원소 e가 하나라도 있으면 true를 반환합니다. 

의미상으로 o.equals(e)인 원소가 있으면 true를 반환하지만, 실제로 모든 원소에 대해 o.equals(e)를 실행한다는 의미는 아닙니다. Collection 구현 클래스는 equals()를 실행하는 대신 다른 방법으로 비교하며 자유롭게 최적화를 할 수 있습니다.

 

3. Collection<E>의 멤버 메소드

3-1. int size()

원소의 개수를 int형으로 반환합니다. 만약 원소의 개수가 Integer.MAX_VALUE 값보다 많다면, Integer.MAX_VALUE 값을 리턴합니다.

 

3-2. boolean isEmpty()

컬렉션에 원소가 없으면 true를 반환합니다.

 

3-3. boolean contains(Object o)

컬렉션이 특정 원소를 포함하고 있으면 true를 반환합니다. 2-4 절에서 살펴봤듯이, o.euqals(e)가 true인 원소가 하나라도 있으면 true를 반환합니다.

특정 원소의 타입이 컬렉션에 맞지 않으면 ClassCastExecption 예외를 발생시킬 수 있습니다. 특정 원소가 null인데 컬렉션이 null 원소를 허용하지 않을 경우, NullPointerException 예외를 발생시킬 수 있습니다.

 

3-4. Iterator<E> iterator()

컬렉션의 반복자를 리턴합니다. 리턴되는 원소에 대한 순서를 보장하지 않습니다.

 

3-5. Object[] toArray()

컬렉션에 있는 모든 원소들을 배열로 반환합니다. 만약 컬렉션이 원소의 순서를 보장하는 컬렉션이라면, 그 순서대로 배열에 담아져서 리턴됩니다. 런타임에 배열의 원소의 타입은 Object입니다.

반환되는 배열에 해당하는 컬렉션의 참조가 없습니다. 따라서 toArray()를 호출한 클라이언트는 반환되는 배열을 자유롭게 수정해도 안전합니다.

 

3-6. <T> T[] toArray(T[] a)

앞서 본 Object toArray()와 비슷하지만 런타임에서 반환되는 배열의 타입이 매개변수로 받은 a의 타입이라는 점이 다릅니다. 

매개변수 a는 컬렉션의 원소가 저장될 배열입니다. 

 

3-7. dafault <T> T[] toArray(IntFunction<T[]> generator)

앞서 본 <T> T[] toArray(T[] a)와 비슷하지만 지정한 generator function을 통해 생성한 T[] 타입의 배열에 원소를 담아준다는 점이 다릅니다. 내부적으로 <T> T[] toArray(T[] a)를 호출한 결과를 반환하고 있습니다.

 

3-8. boolean add(E e)

컬렉션에 특정 원소를 추가합니다. 만약 add() 메소드를 실행하고 컬렉션의 구성이 변화했다면 즉, 새로운 원소가 추가되었다면 true를 반환합니다. 만약 컬렉션이 중복을 허용하지 않는데 이미 특정 원소와 같은 값의 원소를 포함하고 있다면, false를 반환합니다. 이런 과정을 통해 특정 원소를 추가하는 것에 성공했든 안 했든, 특정 원소가 컬렉션에 포함되어 있다는 것을 보장해줍니다.

add() 메소드를 지원하는 컬렉션은 컬렉션에 추가될 수 있는 원소에 대한 제한을 가지고 있을 수 있습니다. 예를 들어, 어떤 컬렉션은 null을 허용하지 않을 수도 있고, 어떤 컬렉션은 특정 타입의 원소만 추가될 수 있도록 할 수 있습니다. 컬렉션 구현 클래스들은 JavaDocs와 같은 documentation에 원소의 제한에 대해 명확하게 명세해야 합니다. 

만약 컬렉션이 매개변수로 받은 원소를 추가할 수 없다면, false를 리턴하는 대신 예외를 발생시켜야 합니다. 이렇게 하면 컬렉션에 항상 특정 타입의 원소만 포함되도록 보장할 수 있습니다.

 

3-9. boolean remove(Object o)

특정 원소가 컬렉션에 존재한다면 컬렉션에서 삭제합니다. 일반적으로, o.equals(e)가 true인 원소를 삭제합니다. 만약 컬렉션에서 원소를 삭제하는 것에 성공했다면, true를 반환합니다. add()와 마찬가지로 삭제에 실패했다면 예외를 발생시켜줍니다.

 

3-10. boolean containsAll(Collection<?> c)

컬렉션이 파라미터로 받은 특정 컬렉션의 모든 원소를 포함하고 있다면 true를 반환합니다.

 

3-11. boolean addAll(Collection<? extends E> c)

파라미터로 받은 특정 컬렉션에 있는 모든 원소들을 컬렉션에 추가합니다.

 

3-12. boolean removeAll(Collection<?> c)

파라미터로 받은 특정 컬렉션의 원소를 컬렉션에서 삭제합니다.

 

3-13. default boolean removeIf(Predicate<? suber E> filter)

파라미터로 주어진 Predicate를 만족하는 원소들을 컬렉션에서 삭제합니다. 

내부 로직을 살펴보면 먼저 java.util.Objects 클래스의 requireNonNull() 정적 메소드를 이용하고 있습니다. Objects.requireNonNull()의 코드는 다음과 같습니다.

T 타입의 객체를 받아 null이면 NullPointerException 예외를 발생시킵니다. 따라서 이 메소드를 실행하면 객체가 null인지 검사할 수 있습니다.

그리고 현재 컬렉션의 iterator() 메소드를 통해 Iterator<E> 객체를 얻습니다. 그리고 이 반복자를 통해 컬렉션의 원소에 차례로 접근합니다. 각각의 컬렉션 원소를 filter.test()의 매개변수로 전달합니다. Predicate<T>의 boolean test(T t) 멤버 메소드는 predicate를 평가해서 조건을 만족하면 true를, 아니라면 false를 반환합니다. 따라서 파라미터로 주어진 Predicate인 filter를 만족하는 컬렉션의 원소들은 삭제됩니다.

 

3-14. boolean retainAll(Collection<?> c)

파라미터로 받은 컬렉션에 포함되는 원소만 유지하고 나머지는 컬렉션에서 삭제합니다.

 

3-15. void clear()

컬렉션의 모든 원소들을 삭제합니다. clear() 메소드를 실행한 후에 컬렉션은 빈 상태가 됩니다.

 

3-16. boolean equals(Object o)

특정 객체와 컬렉션의 동등성(equality)를 비교합니다. 동일성(identity)와 동등성(equality)에 대한 설명은 지난 포스팅(2021.08.24 - [Java] - [Java] 동일성과 동등성(hashCode와 equals))을 참고하실 수 있습니다.

 

[Java] 동일성과 동등성(hashCode와 equals)

클래스의 객체를 비교할 때, 동일성을 비교할 것인지 동등성을 비교할 것인지를 명확하게 해야 할 때가 있습니다. 그렇다면 동일성과 동등성이란 무엇일까요? 동일성(Identity) 두 객체의 동일성

limdevbasic.tistory.com

만약 List, Queue, Set과 같은 Java API를 이용하는 것이 아니라 Collection 인터페이스를 직접 구현하게 된다면, 프로그래머는 Object.equals() 메소드를 오버라이딩 할지 안할지 신중하게 생각해봐야 합니다. Value 비교를 할 것인지, Reference 비교를 할 것인지에 따라 equals() 오버라이딩의 여부가 결정됩니다. 

✋ List와 Set의 equals()

List의 equals()는 비교하는 "List"와 같을 때 true를, Set의 equals()는 비교하는 "Set"과 같을 때 true를 반환하도록 규정되어 있습니다.
따라서 프로그래머가 직접 구현하는 컬렉션이 List와 Set을 구현하는 것이 아니라면, List나 Set과 비교했을 때 반드시 false를 반환해야 합니다. 

 

3-17. int hashCode()

컬렉션의 정수형 해시 코드 값을 반환합니다. 

Object.equals() 메소드를 오버라이딩한 컬렉션은 반드시 Object.hashCode()도 오버라이딩해야 합니다. 일반적으로 c1.equals(c2)는 c1.hashCode() == c2.hashCode()를 의미하기 때문입니다.

 

3-18. default Spliterator<E> spliterator()

Java 8부터 추가되었으며, Iterator<E>의 메소드를 오버라이딩 한 메소드입니다. 컬렉션의 Spliterator 객체를 반환합니다. 

일반적으로 더 효율적인 spliterator를 반환할 수 있는 서브클래스에서 오버라이딩되어야 합니다. 뒤에서 살펴볼 stream()과 parallelStream()의 laziness를 보존하기 위해, spliterator는 IMMUTABLE, CONCURRENT, 또는 late-binding 특성을 가져야 합니다. 

 

3-19. default Stream<E> stream()

Java 8부터 추가되었으며, 컬렉션의 sequential 스트림 객체를 반환합니다. spliterator() 메소드가 IMMUTABLE, CONCURRENT, 또는 late-binding인 spliterator를 반환할 수 없을 때 stream() 메소드가 오버라이드 되어야 합니다. 

 

3-20. default Stream<E> parallelStream()

Java 8부터 추가되었으며, 컬렉션의 possibly parallel한 스트림 객체를 반환합니다. spliterator() 메소드가 IMMUTABLE, CONCURRENT, 또는 late-binding인 spliterator를 반환할 수 없을 때 stream() 메소드가 오버라이드 되어야 합니다. 

반응형