2023. 3. 5. 11:08ㆍJava
내용
필터링, 슬라이싱, 매칭
검색, 매칭, 리듀싱
특정 범위의 숫자와 같은 숫자 스트림 사용하기
다중 소스로부터 스트림 만들기
무한 스트림
// 외부 반복
List<Dish> vegetrianDishes = new ArrayList<>();
for(Dish d : menu) {
if(d.isVegetarian())
vegetrianDishes.add(d);
}
// 내부 반복
List<Dish> vegetrianDishes = menu.stream()
.filter(Dish::isVegetarian)
.collect(Collectors.toList());
1. 필터링
1. 프레디케이트로 필터링
filter 메서드는 프레디케이트를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetrianDishes = menu.stream()
.filter(Dish::isVegetarian) // 채식 요리인지 확인하는 메서드 참조
.collect(Collectors.toList());
2. 고유 요소 필터링
스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다.
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i%2 == 0)
.distinct()
.forEach(System.out::println);
2. 스트림 슬라이싱
1. 프레디케이트를 이용한 슬라이싱
자바9는 스트림의 요소를 효과적으로 선택할 수 있도록 takeWhile, dropWhile 두 가지 새로운 메서드를 지원한다.
- TAKEWHILE활용 - 코드를 보면 스페셜 메뉴는 이미 칼로리 별로 정렬이 되있는 상태다. 리스트가 이미 정렬되있다는 사실을 이용해 320 칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단할 수 있다. 작은 리스트에서는 이와 같은 동작이 별거 아닌 것처럼 보일 수 있지만 아주 많은 요소를 포함하는 큰 스트림에서는 상당한 차이가 될 수 있다. takeWhile을 이용하면 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이스할 수 있다.
List<Dish> specialMenu = Arrays.asList(
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER));
List<Dish> filterMenu = specialMenu.stream()
.filter(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());
List<Dish> sliceMenu = specialMenu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());
// season fruit, prawns 반환
- DROPWHILE 활용 - 나머지 요소를 선택하려면 dropWhile을 이용해 이 작업을 완료할 수 있다. dropWhile은 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다. 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 요소를 모두 반환한다. dropWhile은 무한한 남은 요소를 가진 무한 스트림에서도 동작한다.
List<Dish> sliceMenu2 = specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());
// rice, chicken, frenchi fries 반환
2. 스트림 축소
스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원한다. 스트림이 정렬되어 있으면 최대 요소 n개를 반환한다. 정렬되지 않은 스트림에도 limit을 사용할 수 있다. 정렬되어 있지 않다면 limit의 결과도 정렬되지 않은 상태로 반환한다.
List<Dish> filterMenu = specialMenu.stream()
.filter(dish -> dish.getCalories() > 320)
.limit(3) // 3개 반환
.collect(Collectors.toList());
// rice, chicken, french fries
3. 요소 건너뛰기
스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
List<Dish> filterMenu = specialMenu.stream()
.filter(dish -> dish.getCalories() < 320)
.skip(2)
.collect(Collectors.toList());
// 빈 스트림
3. 매핑
스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능을 제공한다.
1. 스트림의 각 요소에 함수 적용하기
스트림은 함수를 인수로 받는 map 메서드를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
List<String> dishNames = menu.stream()
.map(Dish::getName) // getName은 String을 반환하기때문에,
// map 메서드의 출력 스트림은 Strema<String> 형식을 갖는다.
.collect(Collectors.toList());
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length) // String.length는 Stream<Integer> 형식 반환
.collect(Collectors.toList());
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(Collectors.toList());
// map 메서드 연결로 Integer을 뽑아낸다.
2. 스트림 평면화
["Hello", "Wolrd"] -> ["H", "e", "l", "o", "W", "r", "d"] 로 만들어보자.
List<String> words = Arrays.asList("Hello", "World");
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
// Stream<String[]>
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream) // List<Stream<String>>
.distinct()
.collect(toList());
List<String> uniqueCharacters = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream) // Stream<String>
.distinct()
.collect(toList());
flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에
모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다.
4. 검색과 매칭
1. 프레디케이트가 적어도 한 요소와 일치하는지 확인
if(menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.pritnln("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch 적어도 한 요소와 일치하는지 검사한다.
2. 프레디케이트가 모든 요소와 일치하는지 검사
boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
allMatch 모든 요소가 일치하는지 검사한다.
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
noneMatch 일치하는 요소가 없는지 확인한다.
3. 요소 검색
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian) // Stream<Dish>
.findAny();
findAny 현재 스트림에서 임의의 요소를 반환한다.
4. 첫 번째 요소 찾기
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst(); // 9
findAny와 findFirst는 병렬성 때문에 사용하는 것이다. 병렬 실행에서는 첫 번째 요소를 찾기 어렵다. 따라서 요소의 반환 순서가 상관없다면 제약이 적은 findAny를 사용하는게 좋다!
5. 리듀싱
모든 스트림 요소를 처리해서 값으로 도출하는 연산을 리듀싱 연산이라고 한다. 함수형 프로그래밍에서는 이 과정이 마치 종이(스트림)를 작은 조각이 될 때까지 반복해서 접는 것과 비슷하다는 의미로 폴드라고 부른다.
1. 요소의 합
int sum = 0;
for(int x : numbers)
sum += x;
// 이 코드에서는 초깃값 0, 리스트의 요소를 조합하는 연산 + 총 2개를 사용했다.
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce는 두개의 인수를 갖는다.
- 초깃값 0
- 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
int product = numbers.stream().reduce(1, (a, b) -> a * b);
// 곱셈도 적용 가능하다.
int sum = numbers.stream().reduce(0, Integer::sum);
// Integer가 지원하는 sum을 이용하면 더 편하게 구현이 가능해진다.
2. 최댓값과 최솟값
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 최댓값 출력
Optional<Integer> min = numbers.stream().reduce(Integer::min);
// 최솟값 출력
6. 숫자형 스트림
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
// 이 코드에는 박싱 비용이 숨어있다.
1. 기본형 특화 스트림
int calories = menu.stream()
.mapToInt(Dish::getCalories) // IntStream
.sum();
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // Stream<Integer> -> IntStream
Stream<Integer> stream = intStream.boxed(); // IntStream -> Stream<Integer>
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
int max = maxCalories.orElse(1);
이 외에도 DoubleStream, LongStream을 제공한다.
2. 숫자 범위
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0); // 2, 4, 6 ... 100
System.out.println(evenNumbers.count()); // 50
rangeClosed(int a, int b)는 a, b의 범위를 포함하지만, range(int a, int b)는 a,b의 범위를 포함하지 않는다.
3. 활용 : 피타고라스 수
Stream<int[]> pythoagoreanTriples = IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)})
);
pythoagoreanTriples.limit(5)
.forEach(item -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
// 3, 4, 5
// 5, 12, 13
// 6, 8, 10
// 7, 24, 25
// 8, 15, 17
// 위 코드 개선? -> 정수를 무조건 두번 곱해 검증하는 방식이다.
Stream<double[]> pythoagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}
.filter(t -> t[2] % 1 == 0) // 반드시 정수여야 한다.
);
8. 스트림 만들기
1. 값으로 스트림 만들기
Stream<String> stream = Steram.of("Modern ", "Java ", "In ", "Action");
// 문자열로 된 Stream 생성
stream.map(String::toUpperCase)
.forEach(System.out::println);
// MODERN JAVA IN ACTION
Stream<String> emptyStream = Stream.empty(); // 빈 String Stream 생성
2. null이 될 수 있는 객체로 스트림 만들기
** 자바 9에서 새롭게 추가된 메서드다.
String homeValue = System.getProperty("home");
Stream<String> homeValueStream = homeValue == null ? Stream.empty() : Stream.of(homeValue);
// 자바 9 이전의 프로퍼티 스트림 객체 생성 코드
Stream<String> values = Stream.of("config", "home", "user")
.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
// 자바 9 이후의 스트림 객체 생성 코드
Stream<String> values = Stream.of("config", "home", "user")
.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
// 프로퍼티의 생성 코드
3. 배열로 스트림 만들기
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers) // IntStream을 반환한다.
.sum();
// sum = 141
Arrays.stream 은 배열을 정수로 받는다.
4. 파일로 스트림 만들기
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"),
Charset.defaultCharset())) { // AutoCloseable을 구현해야만 함.
long uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch(IOException e) {
...
}
5. 함수로 무한 스트림 만들기
iterate 메서드
Stream.iterate(0, n -> n+2)
.limit(10)
.forEach(System.out::println);
// 끝이 없이 연산하기때문에 무한 스트림이다. limit을 걸어 제한을 둬야한다.
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
.limit(10)
.forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
// (0, 1), (1, 1), (1, 2) ...
// 피보나치 수열을 만들수도 있다.
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
// 0, 1, 1, 2, 3 ...
// map을 만들어 앞 글자로 일반적인 피보나치 수열을 만들수도 있다.
Stream.iterate(0, n -> n + 4)
.takeWhile(n -> n < 100)
.forEach(System.out::println);
// takeWhile을 이용해 조건까지 연산을 시킬수도 있다.
generate 메서드
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
** generate 메서드는 가변상태 객체다. 병렬 환경에 안좋기때문에, 굳이 병렬 환경에 적용해야한다면 iterate를 쓰는것이 좋다.
'Java' 카테고리의 다른 글
[모던 자바 인 액션] chap09. 리팩터링, 테스팅, 디버깅 (0) | 2023.03.21 |
---|---|
[모던 자바 인 액션] chap06. 스트림으로 데이터 수집 (0) | 2023.03.07 |
[모던 자바 인 액션] chap04. 스트림이란? (0) | 2023.03.01 |
[모던 자바 인 액션] chap03. 람다 표현식(2) (0) | 2023.02.24 |
[모던 자바 인 액션] chap03. 람다 표현식(1) (0) | 2023.02.21 |