꾸준한 스터디
article thumbnail

자바 7까지 일련의 원소를 반환하는 메서드의 반환 타입

  • 기본적으로 Collection 인터페이스 (Collection, List, Set...)
  • for-each 문에서만 쓰이거나 일부 Collection을 구현할 수 없을때는 Iterable 인터페이스
  • 반환된 원소들이 기본타입이거나 성능에 민감한 상황이라면 배열

 

자바 8이후 반복(iteration)을 지원하지 않는 Stream이 등장하며 반환 타입 선택이 복잡해졌다.

스트립과 반복을 알맞게 조합해야 좋은 코드가 나오는데 API를 스트림만 반환하도록 짜 놓으면 반환된 스트림을 for-each로 반복하길 원하는 사용자가 생겨날 수 있다.

(Stream 인터페이스는 Iterable 인터페이스가 정의한 추상 메서드를 전부 포함할 뿐 아니라, Iterable 인터페이스가 정의한 방식대로 동작한다. 하지만 Iterable을 확장(extend) 하지 않아 for-each로 스트림을 반복할 수 없다.)

 

iterator 메서드에 메서드 참조를 건내면 for-each를 사용할 수 있을까?

컴파일에러로 메서드 참조를 매개변수화된 Iterable로 타입 형변환이 필요하다.

작동은 하지만 실전에 쓰기에는 너무 난잡하고 직관성이 떨어진다.

Stream을 Iterable로 중개해주는 어댑터 메서드를 만들어서 사용하면 상황이 나아진다.

 

반대로 API가 Iterable만 반환하면 이를 스트림 파이프라인에서 처리하려는 사용자를 위한 어댑터도 손쉽게 구현할 수 있다.

 

객체 시퀀스를 반환하는 메서드를 작성한다면, 이 메서드가 오직 스트림 파이프라인에서만 쓰인다면 스트림을 반환하게 하고 반환된 객체들이 반복문에서만 쓰인다면 Iterable을 반환하도록 한다.

하지만 공개 API를 작성할 때는 스트림 파이프라인을 사용하는 사람과 반복문에서 쓰려는 사람 모두를 배려해야한다.

Collection 인터페이스는 Iterable의 하위 타입이고 stream 메서드도 제공하니 반복과 스트림을 동시에 지원한다.

따라서 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로 최선이다.

 

 

크기가 큰 컬렉션을 메모리에 올린다는 것은 부담이 큰 작업이니 커스텀 컬렉션을 고려해볼 필요가 있다.

주어진 집합의 멱집합(한 집합의 모든 부분집합을 원소로 하는 집합)을 반환하는 경우 AbstractList를 이용하면 훌륭한 전용 컬렉션을 손쉽게 구현할 수 있다.

{a, b, c}의 멱집합 : {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}

  • 비결은 멱집합을 구성하는 각 원소의 인덱스를 비트벡터로 사용하는 것이다. 
    인덱스의 n번째 비트값은 멱집합의 해당 원소가 원래 집합의 n번째 원소를 포함하는지 여부를 알려준다.
    따라서 0부터 2n-1까지의 이진수와 원소 n개인 집합의 멱집합과 자연스럽게 매핑된다.
  • 원소수가 30을 넘으면 PowerSet.of가 예외를 던지는데 Collection의 size 메서드가 int 값을 반환하므로 PowerSet.of 반환되는 시퀀스의 최대 길이는 Integer.MAX_VALUE 또는 231-1로 제한되기 때문

 

AbstractCollection을 활용해서 Collection 구현체를 작성할 때는 Iterable용 메서드 외에 contains와 size만 더 구현하면 된다. 반복이 시작되기 전 시퀀스의 내용을 확정할수 없는 등의 사유료 contains와 size를 구현할 수 없는경우 Collection 보다는 Stream이나 Iterable을 반환하는 편이 낫다.

 

 

입력 리스트의 연속적인 부분 리스트를 모두 반환하는 메서드를 작성한다고 했을 때 필요한 부분리스트를 만들어 표준 컬렉션에 담는 코드는 단 3줄이면 충분하다.

하지만 이 컬렉션은 입력 리스트 크기의 거듭제곱만큼 메모리를 차지한다.

기하급수적으로 늘어나는 멱집합보다 낫지만 역시 좋은 방법은 아니다.

스트림으로 입력 리스트의 모든 부분을 구현하기는 그리 어렵지않다.

 

첫 번째 원소를 포함하는 부분리스트를 prefix라 하고

  • (a, b, c)의 프리픽스는 (a), (a, b), (a, b, c)

마지막 원소를 포함하는 부분리스트를 suffix라 한다.

  • (a, b, c)의 서픽스는 (a, b, c), (b, c), (c)

Stream.concat 메서드는 반환되는 스트림에 빈 리스트를 추가하며, flatMap 메서드는 모든 프리픽스의 모든 서픽스로구성된 하나의 스트림을 만든다. 마지막으로 프리픽스들과 서픽스드르이 스트림은 IntStream.range 와 IntStream.rangeClose가 반환하는 연속된 정숫값들을 매핑해 만들었다.

 

핵심 정리 

  • 원소 시퀀스를 반환하는 메서드를 작성할 때는, 이를 스트림으로 처리하기를 원하는 사용자오 ㅏ반복으로 처리하길 원하는 사용자가 모두 있을 수 있으므로 양쪽 다 만족시키려 노력하자
  • 컬렉션을 반환할 수 있다면 그렇게 하라
  • 반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하라. 그렇지 않으면 앞서 멱집합 예처럼 커스텀 컬렉션을 구현할지 고민하라
  • 컬렉션을 반환하는게 불가능하면 스트림과 Iterable 중 더 자연스러운 것을 반환하라
  • 나중에 Stream 인터페이스가 Iterable을 지원하도록 자바가 수정되면 그 때는 안심하고 스트림을 반환하면 된다.
profile

꾸준한 스터디

@StudyRecord

포스팅이 유익하셨다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!