자바-20(Stream-2)
학습할것
동작 순서
성능 향상
스트림 재사용
지연 처리(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();
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);
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);
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]
지연 처리(Lazy Invocation)
- 스트림에서 최종 결과는 최종 작업이 이루어질 때 계산된다.
- 아래 코드는 호출횟수를 카운트하는 코드이다.
private long cnt;
private void wasCalled(){
cnt++;
}
- 다음코드에서 리스트의 요소가 3개이기 때문에 총 세 번 호출되어 결과가 3이 출력될것이라고 예상된다.
- 하지만 출력값은 0이다.
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
Null-safe 스트림 생성하기
- NullPointerException은 개발시 흔히 발생하는 예외이다.
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"]
- 이제 Null로 테스트를 해보겠습니다.
- 아래와 같이 리스트에 null이 있다면 런타임에러가 발생한다.
- 외부에서 인자로 받은 리스트로 작업을 하는 경우에 일어날 수 있는 상황이다.
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);
줄여쓰기(Simplified)
- 스트림 사용시 다음과 같은 겨웅에 같은 내용을 좀 더 간결하게 줄여쓸수있다.
- IntelliJ를 사용하면 다음과 같은 경우에 줄여쓸 것을 제안해준다.
- 많이 사용되는 것만 추렸다.
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()
- 하지만 스트림이 비어서 값을 계산할 수 없을 떄의 동작은 다르다.
- 전자는 Optional 객체를 리턴하지만, 후자는 NullPointerException이 발생할 가능성이있다.
collect(Collectors.maxBy()) // optional
Stream.max() // NPE 발생가능
참고
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
- NullPointerException은 개발시 흔히 발생하는 예외이다.
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"]
- 이제 Null로 테스트를 해보겠습니다.
- 아래와 같이 리스트에 null이 있다면 런타임에러가 발생한다.
- 외부에서 인자로 받은 리스트로 작업을 하는 경우에 일어날 수 있는 상황이다.
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);
줄여쓰기(Simplified)
- 스트림 사용시 다음과 같은 겨웅에 같은 내용을 좀 더 간결하게 줄여쓸수있다.
- IntelliJ를 사용하면 다음과 같은 경우에 줄여쓸 것을 제안해준다.
- 많이 사용되는 것만 추렸다.
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()
- 하지만 스트림이 비어서 값을 계산할 수 없을 떄의 동작은 다르다.
- 전자는 Optional 객체를 리턴하지만, 후자는 NullPointerException이 발생할 가능성이있다.
collect(Collectors.maxBy()) // optional
Stream.max() // NPE 발생가능
참고
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/
Author And Source
이 문제에 관하여(자바-20(Stream-2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@yyong3519/자바-20Stream-2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)