원소 시퀀스, 즉 일련의 원소를 반환하는 방법은 여러 가지다. 기본은 컬렉션 인터페이스다. for-each
문에서만 쓰어기나 반환된 원소 시퀀스가 일부 Collection
메서드를 구현할 수 없을 때는 Iterable
인터페이스를 썼다. 반환 원소들이 기본 타입이거나 성능에 민감한 상황이라면 배열을 썼다. 그런데 자바 8에서 스트림이라는 개념이 들어오면서 복잡해졌다.
스트림은 반복을 지원하지 않는다. 따라서 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다. 사실 Stream
인터페이스는 Iterable
인터페이스가 정의한 추상 메서드를 전부 포함할 뿐만 아니라, Iterable
인터페이스가 정의한 방식대로 동작한다. 그럼에도 for-each
로 스트림을 반복할 수 없는 이유는 Stream
이 Iterable
을 확장하지 않아서다. 이 문제를 해결할 깔끔한 우회로가 존재하지 않는다. 자바 타입 추론의 한계로 코드가 지저분해진다. 그나마 어댑터 메서드를 사용하면 상황이 나아진다.
공개 API
를 작성할 때는 스트림 파이프라인을 사용하는 사람과 반복문에서 쓰려는 사람 모두를 배려해야 한다. 사용자 대부분이 한 방식만 사용할거라는 근거가 없다. Collection
인터페이스는 Iterable
의 하위 타입이고 stream
메서드도 제공하니 반복과 스트림을 동시에 지원한다. 따라서 원소 시퀀스를 반환하는 공개 API
의 반환 타입에는 Collection
이나 그 하위 타입을 쓰는 게 일반적으로 최선이다. 반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList
나 HashSet
같은 표준 컬렉션 구현체를 반환하는 것도 좋다. 하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안 된다.
반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현하는 방안도 검토해보면 좋다. AbstractCollection
을 활용해서 Collection
전용 구현체를 작성할 때는 Iterable
용 메서드 외에 contain
과 size
메서드만 더 구현하면 된다. 만약 contain
과 size
를 구현하는 게 불가능할 때는 컬렉션보다는 스트림이나 Iterable
을 반환하는 편이 낫다.
핵심은 원소 시퀀스를 반환하는 메서드를 작성할 때는, 이를 스트림으로 처리하기를 원하는 사요앚와 반복으로 처리하길 원하는 사용자가 모두 있을 수 있음을 떠올리고, 양쪽을 모두 만족시키려 노력해야 한다는 것이다. 컬렉션을 반환할 수 있으면 반환하고, 그렇지 않으면 전용 컬렉션을 구현할지 고민해봐야 한다. 컬렉션으로 반환하는 게 불가능하면 스트림과 Iterable
중 더 자연스러운 것을 반환해야 한다.