본문 바로가기
Book

[독서 기록] 모던 자바 인 액션 4장 스트림 소개, 5장 스트림 활용

by Renechoi 2023. 1. 8.

 

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 

반응형