5.1 필터링
5.1.1 프레디케이트로 필터링
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", false, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
List<Dish> vegetableList = menu.stream()
.filter(Dish::isVegetarian)
.toList();
![](https://blog.kakaocdn.net/dn/wXYoO/btslOByWB3s/swEEkaqTT5wVNnDm8XKj10/img.png)
5.1.2 고유 요소 필터링
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
![](https://blog.kakaocdn.net/dn/bRqW1s/btslS7ccRZy/E6J6WdEKqKhY6evdCow9c1/img.png)
5.2 스트림 슬라이싱
5.2.1 프레디케이트를 이용한 슬라이싱
자바 9 에서 효율적으로 슬라이싱할 수 있도록 takeWhile과 dropWhile 메서드를 추가했다.
takeWhile 활용
List<Dish> filteredMenu = menu.stream()
.filter(dish -> dish.getCalories() < 320)
.toList();
filter 함수를 써도 정상적으로 동작은 하지만, 모든 요소를 다 돌아보게 된다.
List<Dish> slicedMenu1 = menu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.toList();
이렇게 정렬되어있는 원소로 takeWhile을 사용하면, 더 이상 그 조건을 만족하는 원소가 없다면 바로 중지하게 된다. 따라서, 모든 원소를 방문하지 않아도 되기 때문에 더 효율적이다.
dropWhile 활용
List<Dish> slicedMenu2 = menu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.toList();
320이하의 모든 dish를 버리는 코드다.
5.2.2 스트림 축소
List<Dish> dishes = specialMenu.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3)
.toList();
![](https://blog.kakaocdn.net/dn/cb51mn/btslMp6BZxD/fkJ6o55ePdXot0NatHxzz1/img.png)
5.2.3 요소 건너띄기
List<Dish> dishes2 = specialMenu.stream()
.filter(dish -> dish.getCalories() > 300)
.skip(2)
.toList();
![](https://blog.kakaocdn.net/dn/nqeVb/btslNlJCgdP/CTs2KRf7QdsKfTUKfiYE6k/img.png)
퀴즈 5.1
List<Dish> dishes = menu.stream()
.filter(dish -> dish.getType() == Dish.Type.MEAT)
.limit(2)
.toList();
5.3 매핑
5.3.1 스트림의 각 요소에 함수 적용하기
List<String> dishNames = menu.stream()
.map(Dish::getName)
.toList();
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> workLengths = words.stream()
.map(String::length)
.toList();
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.toList();
5.3.2 스트림 평면화
Stream의 Stream(Stream<Stream>)형태를 Stream으로 1차원처럼 만들어주는 것을 스트림 평면화라 한다. 아래 예제를 보자.
words.stream()
.map(word -> word.split(""))
.distinct()
.toList();
![](https://blog.kakaocdn.net/dn/da369x/btslRHSUWWj/Wi6vllSRpW1112YLtfAXUK/img.png)
우리는 모든 알파벳을 하나씩 얻고 싶은데, 그럴 수 없다.
map과 Arrays.stream 활용
Arrays.Stream
을 배열값을 스트림으로 묶을 수 있다.
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.toList();
근데, 결과적으로는 String 배열을 하나하나 돌면서 Stream으로 만들어주는 것이기 때문에 List<Stream>
이 되어버린다.
flatMap사용
words.stream()
.map(word -> word.split(""))
.flatmap(Arrays::stream)
.distinct()
.toList();
flatMap
은 각 배열을 스트림이 아니라 스트림의 컨텐츠로 매핑한다. 즉, List<Stream>
으로 나올 결과를 하나의 Stream
안에 담아버린다.
![](https://blog.kakaocdn.net/dn/bgJNOI/btslLE3ZpDD/KfCUQOE8TryD3KaFWQyMK1/img.png)
퀴즈 5.2
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(i -> i * i)
.toList();
List<Integer> arr1 = Arrays.asList(1, 2, 3);
List<Integer> arr2 = Arrays.asList(3, 4);
List<int[]> pairs = arr1.stream()
.flatMap(i -> arr2.stream()
.map(j -> new int[]{i, j}))
.toList();
pairs.forEach(l -> System.out.println(l[0] + ", " + l[1]));
List<int[]> pairs2 = arr1.stream()
.flatMap(i -> arr2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j}))
.toList();
pairs2.forEach(l -> System.out.println(l[0] + ", " + l[1]));
5.4 검색과 매칭
5.4.1 프레디케이트가 적어도 한 요소와 일치하는지 확인
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch
는 Stream
이 아닌 불리언을 반환하므로 최종연산이다.
5.4.2 프레데케이트가 모든 요소와 일치하는지 검사
allMatch
boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
noneMatch
boolean isHealthy2 = menu.stream()
.noneMatch(dish -> dish.getCalories() >= 1000);
쇼트서킷
Java
의 Stream
의 일부 연산들도 쇼트서킷을 지원한다. allMatch
, nonMatch
, findFirst
, findAny
, limit
등이 대표적이다.
5.4.3 요소 검색
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny();
5.4.4 첫 번째 요소 찾기
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisionByThree = integers.stream()
.map(i -> i * i)
.filter(i -> i % 3 == 0)
.findFirst();
findAny와 findFirst의 차이는?
findAny
는 병렬환경에서 제일 첫 번째 원소를 보장하지 않는다. 말 그대로 처음에 만나는 원소를 반환하게 된다.
5.5 리듀싱
5.5.1 요소의 합
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
![](https://blog.kakaocdn.net/dn/dzUxBj/btslSpRJBdL/lVltXRTZh0MNqImxsGjca0/img.png)
초기값 없는 reduce
초기값을 받지 않도록 오버로딩된 reduce
메서드도 있다. 다만, 이 reduce
메서드는 Optional
을 반환한다. 왜냐하면 Stream
이 비어있는 경우엔 초기값을 지정하지 않았기 때문에 합계를 반환할 수 없기 때문이다.
Optional<Integer> sum2 = numbers.stream().reduce((a, b) -> a + b);
5.5.2 최댓값과 최솟값
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
![](https://blog.kakaocdn.net/dn/dOFJjk/btslRoMQpds/CZF6ujOX5eCetadkpKUgAK/img.png)
퀴즈 5.3
Optional<Integer> countOfDishes = menu.stream()
.map(dish -> 1)
.reduce(Integer::sum);
리듀스 메서드의 장점과 병렬화
스트림 연산: 상태 없음과 상태 있음
map
, filter
등은 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림을 보낸다. 따라서 보통 상태가 없는, 즉 내부 상태를 갖지 않는 연산(stateless operation)이다.(반드시 그런건 아님!)
하지만 reduce, sum, max같은 연산은 모두 누적할 내부 상태가 필요하다. 스트림에서 처리하는 요소 수와 관계없이 내부 상태의 크기는 한정(bounded)되어 있다.
반면, sorted나 distinct같은 연산은 filter, map처럼 스트림을 입력으로 받아 다른 스트림을 출력하는 것 처럼 볼 수도 있지만, 사실은 과거의 이력을 알고 있어야한다. 여기서 과거의 이력은 스트림의 크기에 따라 가변적이며, 모든 요소가 버퍼에 추가되어 있어야한다. 이러한 연산을 내부 상태를 갖는 연산(stateful operation)이라 한다.![](https://blog.kakaocdn.net/dn/bHkChr/btslLBl8xLT/YHNe3UNgg8Eq705bq7aGS1/img.png)
5.6 실전 연습
5.6.1 거래자와 트랜잭션
5.6.2 실전 연습 정답
// 1번
List<Transaction> transactions1 = transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparingInt(Transaction::getYear))
.toList();
transactions1.forEach(System.out::println);
// 2번
List<String> cities = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getCity)
.distinct()
.toList();
cities.forEach(System.out::println);
// 3번
List<Trader> traders1 = transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.toList();
traders1.forEach(System.out::println);
// 4번
List<String> names = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getName)
.distinct()
.sorted()
.toList();
names.forEach(System.out::println);
// 5번
boolean res = transactions.stream()
.map(Transaction::getTrader)
.anyMatch(trader -> trader.getCity().equals("Milan"));
System.out.println("res = " + res);
// 6번
List<Transaction> cambridge = transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge")).toList();
cambridge.forEach(System.out::println);
// 7번
Optional<Integer> maxValue = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
maxValue.ifPresent(System.out::println);
// 8번
Optional<Integer> minValue = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::min);
minValue.ifPresent(System.out::println);
5.7 숫자랑 스트림
5.7.1 기본형 특화 스트림
박싱/언박싱 문제를 해결하기 위해 기본형 특화 스트림을 제공한다. IntStream
, DoubleStream
, LongStream
총 3개 제공한다.
숫자 스트림으로 매핑
일반 Stream
을 특화 스트림으로 매핑할 때에는 mapToInt
, mapToDouble
, mapToLong
이 세 가지 메서드를 주로 사용한다. map
과 동일한 작업을 하는데 결과물이 특화스트림이다.
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
객체 스트림으로 복원
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
5.7.2 숫자 범위
range
, rangeClosed
라는 두 메서드를 제공하는데, 특정 숫자 범위의 Stream
을 만든다.
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
5.7.3 숫자 스트림 활용: 피타고라스 수
어렵다,,, 다시보자!!
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(1, 100)
.filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}))
.toList();
5.8 스트림 만들기
5.8.1 값으로 스트림 만들기
Stream<String> stream = Stream.of("Modern ", "Java ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
5.8.2 null이 될 수 있는 객체로 스트림 만들기
Java8의 Stream
은 원래 null을 넣을 수 없었다. 하지만, 때로는 null이 될 수 있는 객체를 넣어야할 때가 있다. Java9에서 nullable한 Stream
을 만들 수 있게 되었다.(null이면 빈 stream)
Stream<String> homeValueStream = Stream.ofNullable(System.getProperty("home"));
5.8.3 배열로 스트림 만들기
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
5.8.4 파일로 스트림 만들기
참고로, Stream
은 AutoCloseable
인터페이스를 구현하므로 try 블록 내의 자원은 알아서 반환한다.
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 함수로 무한 스트림 만들기
Stream.iterate
와 Stream.generate
로 무한 스트림을 만들 수 있다. 무한 스트림은 보통 limit
과 함께 사용하여 무한하게 만복하는 걸 막는다.
iterate 메서드
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
이렇게 무한 스트림을 만들 수도 있는데, iterate는 Java9부터 Predicate를 받을 수 있게 지원한다.
아래 코드를 보자.
Stream.iterate(0, n -> n < 100, n -> n + 4)
.forEach(System.out::println);
Stream.iterate(0, n -> n + 4)
.takeWhile(n -> n < 100)
.forEach(System.out::println);
/* filter은 shortcircuit을 지원하지 않기 때문에무한하게 돈다!! */
// Stream.iterate(0, n -> n + 4)
// .filter(n -> n < 100)
// .forEach(System.out::println);
filter은 shortcircuit을 지원하지 않는 다는 것에 주의하자!!
generate 메서드
generate
메서드는 iterate
와 달리 연속된 값을 생성하는 것이 아니다!
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
퀴즈 5.4
Stream.iterate(new int[]{0, 1}, (int[] arr) -> new int[]{arr[1], arr[0] + arr[1]})
.limit(20)
.forEach(t -> System.out.println("(" + t[0] + ", " + t[1] + ")"));
5.9 마치며
Uploaded by N2T
(23.05.31 23:37)에 작성된 글 입니다.