동작 순서
성능 향상
스트림 재사용
지연 처리(Lazy Invocation)
Null-safe 스트림 생성하기
줄여쓰기(Simplified)
List<String> list = Arrays.asList("l","l","java");
Stream<String> stringStream = list.stream();
stringStream.filter(el -> {
System.out.println("filter() was called");
return el.contains("a");
})
.map(el -> {
System.out.println("map() was called");
return el.toUpperCase();
})
.findFirst();
output
filter() was called
filter() was called
filter() was called
map() was called
여기서 스트림이 동작하는 순서를 알아낼 수 있다.
모든 요소가 첫 번째 중간 연산을 수행하고 남은 결과가 다음 연산으로 넘어가는 것이 아니라, 한 요소가 모든 파이프라인을 거쳐서 결과를 만들어내고, 다음 요소로 넘어가는 순서이다.
좀 더 자세히 살펴보면, "l"은 문자열 "a"를 포함하고 있지 않기 때문에 다음 요소로 넘어간다.
이 때 "filter() was called" 가 출력된다.
다음 요소인"l" 역시 문자열 "a"를 포함하고 있지 않기 때문에 다음 요소로 넘어간다. 이 때 "filter() was called" 가 출력된다.
마지막 요소인 "java"는 "a"를 포함하고 있기 때문에 다음 연산으로 넘어갈수 습니다.
마지막 연산인 findFirst
는 첫번쩨 요소만을 반환하는 연산이다. 따라서 최종결과인 "JAVA"이고 다음연산은 수행할 필요가 없어 종료된다.
위와 같은 과정을 통해서 수행된다.
List<String> list = Arrays.asList("Eric","Elena","java");
List<String> stringList = list.stream()
.map(el->{
System.out.println("map() was called");
return el.substring(0,3);
})
.skip(2)
.collect(Collectors.toList());
System.out.println(stringList);
output
map() was called
map() was called
map() was called
[jav]
첫 번째 요소인 "Eric"은 먼저 문자열을 잘라내고, 다음 skip
메서드 때문에 스킵된다.
다음 요소인 "Elena"도 마찬가지로 문자열을 잘라낸 후 스킵된다.
마지막 요소인 "Java"만 문자열을 잘라내어 "Jav"가 된 후 스킵되지 않고 결과에 포함된다.
여기서 map() 메서드는 총 3번 호출된다.
여기서 메서드 순서를 바꾸면 어떻게 될까?
먼저 skip
메서드가 먼저 실행되도록 해보자
List<String> list = Arrays.asList("Eric","Elena","java");
List<String> stringList = list.stream()
.skip(2)
.map(el->{
System.out.println("map() was called");
return el.substring(0,3);
})
.collect(Collectors.toList());
System.out.println(stringList);
output
map() was called
[jav]
map
메서드는 한 번 박에 호출되지 않는다.filter
, distinct
, skip
메서드가 존재한다 Stream<String> stringStream =
Stream.of("Eric","Elena","Java")
.filter(name->name.contains("a"));
Optional<String> firstElement = stringStream.findFirst();
Optional<String> anyElement = stringStream.findAny();
System.out.println(firstElement);
System.out.println(anyElement);
output
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:469)
위 코드에서 findFirst
메서드를 실행하면서 스트림이 닫히기 대문에 findAny
메서드를 호출 하는 시간 런타임 예외가 발생한다.
컴일러가 캐치할 수 없기 때문에 Stream 이 닫힌 후에 사용되지 않는지 주의해야 한다.
위 코드를 아래 코드처럼 변경할 수 있다.
데이터를 List에 저장하고 필요할 때마다 스트림을 생성해서 사용한다
List<String> names= Stream.of("Eric","Elena","Java")
.filter(name->name.contains("a"))
.collect(Collectors.toList());
Optional<String> firstElement = names.stream().findFirst();
Optional<String> anyElement = names.stream().findAny();
System.out.println(firstElement);
System.out.println(anyElement);
output
Optional[Elena]
Optional[Elena]
private long cnt;
private void wasCalled(){
cnt++;
}
List<String> names= Arrays.asList("Eric","Elena","Java");
cnt = 0;
Stream<String> stringStream = names.stream()
.filter(a->{
wasCalled();
return a.contains("a");
});
System.out.println(cnt); // 0
collect
메서드를 호출한 결과 3이 출력된다. List<String> names= Arrays.asList("Eric","Elena","Java");
cnt = 0;
names.stream()
.filter(a->{
wasCalled();
return a.contains("a");
})
.collect(Collectors.toList());
System.out.println(cnt); // 3
Optional
을 이용해서 null에 안전한(null-safe) 스트림을 생성해보자 public static <T> Stream<T> collectionToStream(Collection<T> collection){
return Optional
.ofNullable(collection)
.map(Collection::stream)
.orElseGet(Stream::empty);
}
위 코드는 인자로 받은 컬렉션 객체를 이용해 optional 객체를 만들고 스트림을 생성후 리턴하는 메서드이다.
그리고 만약 컬렉션이 비어있는 경우라면 빈 스트림을 리턴한다.
제네릭을 이용해 어떤 타입이든 받을수있다.
List<Integer> integerList = Arrays.asList(1,2,3);
List<String> stringList = Arrays.asList("a","b","c");
Stream<Integer> integerStream = collectionToStream(integerList); // [1,2,3]
Stream<String> stringStream = collectionToStream(stringList); // ["a","b","c"]
List<String> nullList= null;
nullList.stream()
.filter(str->str.contains("a"))
.map(String::length)
.forEach(System.out::println);
List<String> nullList= null;
Stream<String> stringStream = collectionToStream(nullList);
stringStream
.filter(str->str.contains("a"))
.map(String::length)
.forEach(System.out::println);
collection.stream().forEach()
→ collection.forEach()
collection.stream().toArray()
→ collection.toArray()
Arrays.asList().stream()
→ Arrays.stream() or Stream.of()
Collections.emptyList().stream()
→ Stream.empty()
stream.filter().findFirst().isPresent()
→ stream.anyMatch()
stream.collect(counting())
→ stream.count()
stream.collect(maxBy())
→ stream.max()
stream.collect(mapping())
→ stream.map().collect()
stream.collect(reducing())
→ stream.reduce()
stream.collect(summingInt())
→ stream.mapToInt().sum()
stream.map(x -> {...; return x;})
→ stream.peek(x -> ...)
!stream.anyMatch()
→ stream.noneMatch()
!stream.anyMatch(x -> !(...))
→ stream.allMatch()
stream.map().anyMatch(Boolean::booleanValue)
→ stream.anyMatch()
IntStream.range(expr1, expr2).mapToObj(x -> array[x])
→ Arrays.stream(array, expr1, expr2)
Collection.nCopies(count, ...)
→ Stream.generate().limit(count)
stream.sorted(comparator).findFirst()
→ Stream.min(comparator)
하지만 주의할점이 있다.
특정 케이스에서 조금 다르게 동작할 수 있다.
예를 들면 다음의 경우 stream
을 생략할수있지만,
collection.stream().forEach()
-> collection.forEach()
// not synchronized
Collections.synchronizedList(...).stream().forEach()
//synchronized
Collections.synchronizedList(...).forEach()
collect
를 생략하고 바로 max
메서드를 호출하는 경우이다.stream.collect(MaxBy())
-> stream.max()
collect(Collectors.maxBy()) // optional
Stream.max() // NPE 발생가능
[참고링크]https://futurecreator.github.io/2018/08/26/java-8-streams-advanced/