4장 스트림 소개
스트림은 자바 8 API에 새로 추가된 기능이다. 스트림을 이용하면 선언형(즉, 데이터를 처리하는 임시 구현 코드 대신 질의로 표현할 수 있다)으로 컬렉션 데이터를 처리할 수 있다. 일단 스트림이 데이터 컬렉션 반복을 멋지게 처리하는 기능이라고 생각하자. 또한 스트림을 이용하면 멀티 스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
- 136p
다음은 기존 코드다
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish : menu) { // 누적자로 요소 필터링
if (dish.getCalories() < 400) {
lowCaloricDishes.add(dish);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() { // 익명 클래스로 요리 정렬
public int compare(Dish dish1, Dish dish2){
return Integer.compare(dish1.getCalories(), dish2.getCalories());
}
};
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish dish : lowCaloricDishes) {
lowCaloricDishesName.add(dish.getName()); // 정렬된 리스트를 처리하면서 요리 이름 선택
}
스트림을 이용한 코드
List<String> lowCaloricDishesName =
menu.stream()
.filter(d -> d.getCalories() < 400) // 400 칼로리 이하의 요리 선택
.sorted(comparing(Dish::getCalories)) // 칼로리로 요리 정렬
.map(Dish::getName) // 요리명 추출
.collect(tolist()); // 요리명을 리스트에 저장
- 137p
선언형으로 코드를 구현할 수 있다. 즉, 루프와 if 조건문 등의 제어 블록을 사용해서 어떻게 동작을 구현할지 지정할 필요 없이 '저칼로리의 요리만 선택하라' 같은 동작의 수행을 지정할 수 있다.
- 137p
다음과 같은 코드를 구현
Map<Dish.Type, List<Dish>> dishesByTpe =
menu.stream().collect(groupingBy(Dish::getType));
- 138p
스트림이란 정확히 뭘까? 스트림이란 '데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소'로 정의할 수 있다.
- 141p
컬렉션의 주제는 데이터고 스트림의 주제는 계산이다.
- 141p
데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이다. 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조다. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. ... 반면 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조다.
- 144p
dvd vs 인터넷 스트리밍, 생산자 중심 vs 소비자 중심
- 144p
스트림을 시간적으로 흩어진 값의 집합으로 간주할 수 있다. 반면 컬렉션은 특정 시간에 모든 것이 존재하는 공간(컴퓨터 메모리)에 흩어진 값으로 비유할 수 있다.
- 146p
컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 한다(예를 들면 for-each 등을 사용해서) 이를 외부 반복이라고 한다. 반면 스트림 라이브러리는 (반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는) 내부 반복을 사용한다.
- 146p
List<String> names = new ArrayList<>();
for (Dish dish:menu){ // 메뉴 리스트를 명시적으로 순차 반복한다.
names.add(dish.getName()); // 이름을 추출해서 리스트에 추가한다.
}
Iterator<Dish> iterator = menu.iterator();
while (iterator.hasNext()){ // 명시적 반복
Dish dish = iterator.next();
names.add(dish.getName());
}
- 146 ~ 147p
연결할 수 있는 스트림 연산을 중간 연산이라고 하며, 스트림을 닫는 연산을 최종 연산이라고 한다.
- 150p
보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다. 예를 들어 다음 파이프라인에서 forEach는 소스의 각 요리에 람다를 적용한 다음에 void를 반환하는 최종 연산이다. System.out.println을 forEach에 넘겨주면 menu에서 만든 스트림의 모든 요리를 출력한다.
- 151p
map.stream().forEach(System.out::println);
-152p
5장 스트림 활용
필터링
.filter(Dish::isVegetarian)
고유 요소 필터링
.distinct()
takeWhile과 dropWhile을 통해 효과적인 슬라이싱도 가능하다
요소 건너뛰기
.skip(2)
=> 처음 두 요리를 건너뛴 다음부터 연산 적용
- 158 ~ 161p
스트림은 함수를 인수로 받는 map 메서드를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
- 162p
List<String> words = Arrays.asList("Modern", "java", "in", "action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.toList();
System.out.println(words);
System.out.println(wordLengths);
- 162p
스트림 평면화
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
- 163p
List<String> uniqueChracters = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(tolist());
- 165p
flatMap 이용하기 예제
// 두개의 리스트 [1,2,3] 과 [3,4]가 주어졌을 때
// (1,3) (1,4) (2,3) (2,4) (3,3) (3,4)를 반환하기
List<Integer> number1 = Arrays.asList(1, 2, 3);
List<Integer> number2 = Arrays.asList(3, 4);
List<int[]> pairs = number1.stream()
.flatMap(i -> number2.stream()
.map(j -> new int[]{i, j}))
.toList();
- 166p
검색과 매칭
allMatch, anyMatch, noneMatch, findFirst, findAny
프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할 때 anyMatch 메서드를 이용한다.
if (menu.stream().anyMatch(Dish::isVegetarian)) {
system.out.println("")
}
anyMatch는 불리언을 반환하므로 최종 연산이다.
- 167p
요소 검색
findAny 메서드는 현재 스트림에서 임의의 요소를 반환한다.
다음과 같이 채식 요리를 선택하기
optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian);
.findAny()
.isPresent(dish -> System.out.println(dish.getName());
- 168p
findAny는 아무 요소도 반환하지 않을 수 있다. null은 쉽게 에러를 일으킬 수 있으므로 자바 8 라이브러리 설계자는 Optional<T>를 만들었다. ... 일단 Optional은 값이 존재하는지 확인하고 값이 없을 때 어떻게 처리할지 강제하는 기능을 제공한다.
- 168p
첫번째 요소 찾기
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
.map(n -> n * n)
.filter(n -> n%3 == 0)
.findFirst(); // 9
요소의 합
기존 for 방식
int sum = 0;
for (int x : numbers) {
sum +=x;
}
sum 변수의 초깃값 0
리스트의 모든 요소를 조합하는 연산 +
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
스트림이 하나의 값으로 줄어들 때까지 람다는 각 요소를 반복해서 조합한다.
- 170 ~ 171p
int sum = numbers.stream().reduce(0, Integer::sum);
Integer 클래스에서 두 숫자를 더하는 정적 sum 메서드를 제공하는 것 활용하기
- 172p
초깃값이 없을 때 Optional 객체로 감싼 결과를 반환한다.
Optional<Integer> max = numbers.stream().reduce(Integer::max);
기본형 특화 스트림
// 모든 칼로리를 integer 형식으로 추출한 다음에 intstream을 반환.
// intstream의 메서드를 통해 연산
int caclories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
boxed()를 이용해서 특화 스트림을 일반 스트림으로 변환할 수 있다.
- 183p
IntStream의 최댓값 요소 찾기
// IntStream의 최댓값 요소 찾기
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
int max = maxCalories.orElse(1);
세 수 표현하기
Stream<Object> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed() // Stream<Integer>로 변환
.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)})
);
pythagoreanTriples.limit(5)
.forEach(t -> System.out.println(Arrays.toString((int[]) t)));
피타고라스 a^2 + b^2 = c^2 를 만족하는 a,b,c 찾기
- 187p
함수로 무한 스트림 만들기
iterate
Stream.iterate(0, n-> n+2)
.limit(10)
.forEach(System.out::println);
iterate 메서드는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산할 수 있다.
- 191p
피보나치 수열 만들기
Stream.iterate(new int[]{0,1},
ints -> new int[] { ints[1], ints[0] + ints[1]})
.limit(10)
.map(t->t[0])
.forEach(System.out::println);
generate로 무한 스트림
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
- 191p
'Book' 카테고리의 다른 글
[독서 기록] 모던 자바 인 액션 7장 병렬 데이터 처리와 성능 (0) | 2023.01.09 |
---|---|
[독서 기록] 모던 자바 인 액션 6장 스트림으로 데이터 수집 (0) | 2023.01.08 |
[독서 기록] 모던 자바 인 액션 3장 람다 표현식 (1) | 2023.01.06 |
[독서 기록] 모던 자바 인 액션 2장 - 동작 파라미터화 코드 전달하기 (0) | 2023.01.04 |
[독서 기록] 모던 자바 인 액션 1장 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가? (0) | 2023.01.04 |