filter 메서드는 predicate(불리언을 반환하는 함수)를 인수로 받아서 일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
number.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이싱
아래 코드의 경우 filter는 전체 스트림을 반복하지만 takewhile 연산을 활용하면 반복작업을 중단할 수 있다
List<Dish> sliceMenu1 = specialMenu.stream() .talkWhile(dish -> dish.getCalories() < 320) .collect(toList()); //seasonal fruit, prawns
Takewhile 과는 반대로 프레디케이트가 거짓이 되는 지점까지 발견되는 요소를 버린다
limit(n) : 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환
skip(n): 처음 n개 요소를 제외한 스트림을 반환하는 메서드
map(): 함수를 인수로 받으며, 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
["Hello", "World"] 리스트가 있을때 ["H", "e", "l", "o", "W", "r", "d"] 고유문자로만 이루어진 리스트를 반환하려면?
word.stream()
.map(word -> word.split(""))
.disctinct()
.collect(toList());
map 이 String[] 을 반환한다는 문제로 우리가 원하는
Stream <String>
에 도달하지 못한다.
words.stream()
.map(word -> word.split("")) //각 단어를 개별 문자열로 반환
.map(Arrays::stream) //각 배열을 별도의 스트림으로 생성
.distinct()
.collect(toList));
배열대신 문자열 스트림을 만들기 위해 문자열을 받아 스트림을 만드는 메서드인 Arrays.stream()은
List<Stream<String>>
를 반환한다는 문제
위의 두가지 경우 모두 flatMap 을 사용하면 해결가능하다
flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑
각각의 스트림을 모아 하나의 스트림으로 연결
anyMatch()
allMatch()
<-> noneMatch()
이들은 모두 전체연산을 수행하지 않았더라도 원하는 요소를 찾았으면 즉시 결과를 반환하는 쇼트 서킷기법을 사용한다
findAny()
현재 스트림에서 임의의 요소를 반환
findFirst()
현재 스트림에서 첫번재 요소를 찾을 때 사용
모든 스트림 요소를 반복적으로 처리해서 결과를 도출하는 작업을 수행할 수 있다.
함수형 프로그래밍 언어에서는 종이를 계속해서 접는것과 비슷하다고하여 폴드(fold)라 부른다.
reduce()
를 활용하면 애플리케이션의 반복된 패턴을 추상화할 수 있다
reduce 사용 전
int sum = 0; for ( int x : numbers ) { sum += x; }
reduce 사용 후
int sum = numbers.stream().reduce(0, (a,b) -> a + b);
reduce + 메서드 참조로 더 간결하게 구현이 가능하다
int sum = numbers.stream().reduce(0, Integer::sum);
스트림연산: 상태 없음과 상태 있음
filter, map등은 상태를 저장하지 않는 상태 없는 연산(stateless operation)이다.
반면 reduce와 같은 연산은 값을 계산하는데 필요한 상태를 저장한다. 또한 sorted, distinct 등의 메서드는 새로운 스트림을 반환하기에 앞서 스트림의 모든 요소를 버퍼에 저장해야 한다. 이런 메서드를 상태 있는 연산(stateful operation)이라고 한다.
😃 스터디에서 상태 없음과 상태있음에 대해서 토론을 나누었다. 뒷 챕터에서 배울 거지만 내부상태를 가지지 않아야 병렬 처리에 유리하며, 내부상태를 가지는 연산은 내부 상태를 바꾸지 않는다는 전제하에 병렬처리에 사용되어야한다고 했다.
즉, 우리는 병렬 처리의 지점에서 스트림연산이 각각 상태가 있는지 없는지에 대해 아는 것이 중요하다라고 이야기가 나왔다.
자바 8에서는 스트림 API 숫자 스트림을 효율적으로 처리할 수 있도록 세 가지 기본형 특화 스트림을 제공한다. IntStream, DoubleStream, LongStream 이 그것이며 자주 사용되는 숫자 관련 리듀싱 연산 수행 메서드(sum, max, min 등)를 제공하고, 박싱 비용을 피할 수 있게 해주어 효율성과 관련이 있다.
int calories=menu.stream().map(Dish::getCalories).reduce(0,Integer::sum)
이 코드는 Integer::sum 메서드 레퍼런스를 사용하고 이로 인해 int 값들이 Integer 객체로 박싱되어 합산 결과를 반환한다.
이는 우리가 원했던 결과가 아니다.
아래와 같이 스트림을 숫자스트림(특화스트림)으로 매핑하는 mapToInt, mapToDouble,mapToLong 세가지 메서드를 활용하자.
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
mapToInt 의 결과는
IntStream
이지,Stream<Integer>
이 아니다
IntStream intStream=menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream=intStream.boxed();
숫자 스트림에서 특정 범위의 숫자를 생성할 때에는 range와 rangeClosed 메서드를 사용하면 된다. range메서든 시작값과 종료값이 결과에 포함되지 않고 rangeClosed는 둘 다 포함한 범위를 생성한다.
Stream<String> stream = Stream.of("Modern", "Java", "In", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty();
자바 9에서는 null 이 될 수 있는 개체를 스트림으로 만들 수 있는 새로운 메서드가 추가되었다.
Stream<String> homeValueStream
= Stream.ofNullable(System.getProperty("home"));
Stream<String> values =
Stream.of("config", "home", "user")
.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
null 이 될 수 있는 객체를 포함하는 스트림값을 flatMap 과 함께 사용하는 상황에서 유리하다.
왜일까? map을 사용하면, null 객체 역시 스트림에 포함된다. flatMap을 사용하면, null 객체에 대해서는 해당 객체가 무시되고 평면화된 스트림에 포함되지 않는다
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API(비블록 I/O)도 스트림 API를 활용할 수 있다.
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
//
}
##5.8.5 함수로 무한 스트림만들기
iterate 메서드는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임업이 생산할 수 있다.(언바운드 스트림)
// 불가
IntStream.iterate(0, n -> n + 4)
.filter(n -> n < 100)
.forEach(System.out::println);
// 가능
IntStream.iterate(0, n -> n + 4)
.talkWhile(n -> n < 100)
.forEach(System.out::println);
iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않으며, Supplier<T>
를 인수로 받아서 새로운 값을 생산한다.
Supplier<T>
란?
Java8에서는 @FuncionalInterface 어노테이션을 가진 다양한 함수형 인터페이스를 제공하는데 그 중 하나이다. T get() 이라는 메소드 하나만 추상화 되어있고 T의 하위 타입 인스턴스를 반환할 수 있다.추가로 자세한 내용은 나중에 정리해야겠다.
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);