자바 7까지는 메서드의 반환 타입으로  Collection, Set, List 같은 컬렉션 인터페이스,  혹은 Iterable이나 배열을 썻다.

이 중 가장 적합한 타입을 선택하기란 그다지 어렵지 않았다. 기본은 컬렉션 인터페이스다.

for-each 문에서만 쓰이거나 반환된 원소 시퀀스가 일부 Collection 메서드를 구현할 수 없을 때는 Iterable 인터페이스를 썼다. 반환 원소들이 기본 타입이거나 성능에 민감한 상황이라면 배열을 썼다.

 

그런데 자바 8이 스크림이라는 개념을 들고 오면서 이 선택이 아주 복잡한 일이 되어버렸다.

 

원소 시퀀스를 반환할 때는 당연히 스트림을 사용해야 한다는 이야기를 들어봤을지 모르겠지만, 아이템 45에서 이야기했듯이 스트림은 반복(iterable)을 지원하지 않는다.

 

https://win-bro.tistory.com/97

 

[Effec Java] Item.45 스트림은 주의해서 사용하라

스트림 API는 순차적이든 병렬적이든 다량의 데이터 처리 작업을 돕고자 자바 8에 추가되었다. 이 API가 제공하는 추상 개념 중 핵심은 두 가지다. 스트림(Stream)은 데이터 원소의 유한 혹은 무한

win-bro.tistory.com

 

따라서 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다. API를 스트림만 반환하도록 짜놓으면 반환된 스트림을 for-each로 반복하길 원하는 사용자는 당연히 불만을 토로할 것이다.

 

스트림에서 for-each로 반복을 할 수 없는 이유는 Stream이 Iterable을 확장(extends)하지 않아서다.

for(ProcessHandle ph : ProcessHandle.allProcesses()::iterator){
	// 프로세스 처리
}

아쉽게도 이 코드는 다음의 컴파일 오류를 낸다.

 

Test.java:6: error: method reference not expected here
for(ProcessHandle ph : ProcessHandle.allProcess()::iterator){
                       ^

 

이 오류를 바로잡으려면 메소드 참조를 매개변수화된 Iterable로 적절히 형변환해줘야 한다.

for(ProcessHandle ph : (Iterable<ProcessHandle>)
			ProcessHandle.allProcesses()::iterator)}{
		// 프로세스 처리                       
}

 

작동은 하지만 실전에 쓰기에는 난잡하고 직관성이 떨어진다. 여기서 어댑터 메서드를 사용하면 상황이 나아진다.

자바는 이런 메서드를 제공하지 않지만 다음 코드와 같이 쉽게 만들어낼 수 있다.

 

public static <E> Iterable<E> iterableOf(Stream<E> stream){
	return stream::iterator;
}

 어댑터를 사용하면 어던 스트림도 for-each문으로 반복할 수 있다.

 

for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())){
	// 프로세스 처리
}

 

아이템 45의 아나그램 프로그램에서 스트림 버전은 사전을 읽을 때 Files.lines 메서드를 이용했고, 반복 버전은 스캐너를 이용했다.

 

둘 중 파일을 읽는동안 발생하는 모든 예외를 알아서 처리해준다는 점에서 Files.lines쪽이 더 우수하다.

그래서 이상적으로는 반복버전에서도 Files.lines를 써야 했다.

이는 스트림만 반환하는 API가 반환한 값을 for-each로 반복하길 원하는 프로그래머가 감수해야 할 부분이다.

 

반대로 API가 Iterable만 반환하면 이를 스트림 파이프라인에서 처리하려는 프로그래머가 성을 낼 것이다.

자바는 이를 위한 어댑터도 제공하지 않지만 아래와 같이 구현할 수 있다.

 

public static <E> Stream<E> streamOf(Iterable<E> iterable){
	return StreamSupport.stream(iterable.spliterator(), flase);
}

 

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

 

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

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

Arrays 역시 Arrays.asList와 Stream.of 메서드로 손쉽게 반복과 스트림을 지원할 수 있다.

 

반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet같은 표준 컬렉션 구현체를 반환하는 게 최선일 수 있다. 하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안된다.

 

핵심 정리

원소 시퀀스를 반환하는 메서드를 작성할 때는, 이를 스트림으로 처리하기를 원하는 사용자와 반복으로 처리하길 원하는 사용자가 모두 있을 수 있음을 떠올리고, 양쪽을 다 만족시키려 노력하자.

컬렉션을 반환할 수 있다면 그렇게 하라.

반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하라.

컬렉션을 반환하는게 불가능하면 스트림과 Iterable 중 더 자연스러운 것을 반환하라.

만약 나중에 Stream 인터페이스가 Iterable을 지원하도록 자바가 수정된다면, 그때는 안심하고 스트림을 반환하면 될 것이다.
(스트림 처리와 반복 모두에 사용할 수 있으니)

+ Recent posts