Java8 In Action

[Part2] Java8 In action - Chapter5 - 1

신나게개발썰 2022. 7. 29. 09:15
반응형

해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.

좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!

5.1 필터링과 슬라이싱

Entity

Dish.java

package Part2.Chapter5.Chapter5_5_1.entity;

public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type) {    
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return this.name;
    }

    public int getCalories() {
        return this.calories;
    }

    public Type getType() {
        return this.type;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    @Override
    public String toString() {
        return this.name;
    }

    public enum Type {
        MEAT, FISH, OTHER
    }
}

Main

package Part2.Chapter5.Chapter5_5_1;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import Part2.Chapter5.Chapter5_5_1.entity.Dish;

/*
 * 5.1 필터링과 슬라이싱 
 */
public class Main_5_1 {

    public static void main(String[] args) {
        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", true, 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));

        /*
         * 5.1.1 프레디케이트로 필터링
         * 
         * filter 메서드는 프레디케이트(불린을 반환하는 함수)를 인수로 받아서 프레디케이트와 일치하는 모든 요소를
         * 포함하는 스트림을 반환한다.
         */
        menu.stream()
            .filter(Dish::isVegetarian)
            .collect(Collectors.toList())
            .forEach(d -> System.out.println("5.1.1 프레디케이트로 필터링 : 이름 = " + d.getName() + ", 채식주의 = " + d.isVegetarian() ));

        /*
         * 5.1.2 고유 요소 필터링
         * 
         * 스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct라는 메서드도 지원한다.
         * (고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정)
         * 아래는 짝수를 선택하고 중복을 필터링하는 코드 이다.
         */
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        numbers.stream()
            .filter(i -> i % 2 == 0)
            .distinct()
            .forEach((i) -> System.out.println("5.1.2 고유 요소 필터링 : " + i));

        /*
         * 5.1.3 스트림 축소
         * 
         * 스트림은 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원한다.
         * 아래는 300칼로리 이상의 세 요리를 선택해서 리스트를 가져오는 코드이다.
         */
        menu.stream()
            .filter(d -> d.getCalories() > 300)
            .limit(3)
            .collect(Collectors.toList())
            .forEach(d -> System.out.println("5.1.3 스트림 축소 : 이름 = " + d.getName() + ", 칼로리 = " + d.getCalories() ));
        /*
         * 정렬되지 않는 스트림(예를 들면 소스가 Set)에도 limit를 사용할 수 있다. 소스가 정렬되어 있지 않다면
         * limit의 결과도 정렬되지 않는 상태를 반환한다.
         */

        /*
         * 5.1.4 요소 건너뛰기
         * 
         * 스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다.
         * n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
         * 
         * 아래 코드는 300칼로리 이상의 처음 두 요리를 건너뛴 다음에 300칼로리가 넘는 나머지 요리를 반환한다.
         */
        menu.stream()
            .filter(d -> d.getCalories() > 300)
            .skip(2)
            .collect(Collectors.toList())
            .forEach(d -> System.out.println("5.1.4 요소 건너뛰기 : 이름 = " + d.getName() + ", 칼로리 = " + d.getCalories() ));
    }

}

5.2 매핑

Entity

Dish.java

package Part2.Chapter5.Chapter5_5_2.entity;

public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type) {    
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return this.name;
    }

    public int getCalories() {
        return this.calories;
    }

    public Type getType() {
        return this.type;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    @Override
    public String toString() {
        return this.name;
    }

    public enum Type {
        MEAT, FISH, OTHER
    }
}

Main

package Part2.Chapter5.Chapter5_5_2;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import Part2.Chapter5.Chapter5_5_1.entity.Dish;

/*
 * 5.2 매핑
 * 
 * 특정 객체에서 특정 데이터를 선택하는 작업은 데이터 처리 과정에서 자주 수행되는 연산이다.
 * 예를 들어 SQL의 테이블에서 특정 열만 선택할 수 있다.
 * 스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능을 제공한다.
 */
public class Main_5_2 {

    public static void main(String[] args) {
        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", true, 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));

        /*
         * 5.2.1 스트림의 각 요소에 함수 적용하기
         * 
         * 스트림은 함수를 인수로 받는 map 메서드를 지원한다.
         * 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
         * (기존 값을 "고친다(modify)"라기 보단 "새로운 버전을 만든다"라는 개념에 가까우므로
         * "변환(transforming)"에 가까운 "매핑(mapping)"이라는 단어를 사용한다.)
         * 
         * getName은 문자열을 반환하므로 map 메서드의 출력 스트림은 Stream<String>형식을 갖는다.
         */
        menu.stream()
            .map(Dish::getName)
            .collect(Collectors.toList())
            .forEach(s -> System.out.println("Dish Name : " + s));

        /*
         * 단어 리스트가 주어졌을 때 각 단어가 포함하는 글자 수의 리스트를 반환한다 가정해보자.
         * map을 이용해서 각 요소에 적용할 함수는 단어를 인수로 받아서 길이를 반환한다.
         */
        Arrays.asList("Java8", "Lambdas", "In", "Action").stream()
            .map(String::length)
            .collect(Collectors.toList())
            .forEach(len -> System.out.println("String Length : " + len));

        /*
         * 아래는 각 요리명의 길이를 구하는 코드이다.
         */
        menu.stream()
            .map(Dish::getName)
            .map(String::length)
            .collect(Collectors.toList())
            .forEach(len -> System.out.println("Dish Name Length : " + len));

        /*
         * 5.2.2 스트림 평면화
         * 
         * 아래는 리스트에 있는 각 단어를 문자로 매핑한 다음에 distinct로 중복된 문자를 필터링 예제이다.
         * 그러나 중복된 문자 필터링은 되지 않을것이다.
         * 이유는 map에 전달된 람다(word -> word.split(""))가 각 단어의 String[](문자열 배열)을 
         * 반환하기 때문이다. 
         * 따라서 map의 반환 스트림도 Stream<String[]>이다. 그러나 우리가 원하는 스트림 형식은
         * Stream<String>이여야 한다. 
         */
        Arrays.asList("Hello", "World").stream()
            // map에서 Stream<String[]> 스트림을 반환한다.  
            .map(word -> word.split(""))
            // 반환된 스트림이 Stream<String[]>이기 때문에 distinct가 되질 않는다.
            .distinct()
            .collect(Collectors.toList())
            .forEach(word -> {
                String str = "";

                for(int i = 0, len = word.length; i < len; i++) {
                    str += word[i];
                }

                System.out.println(str);
            });

        /*
         * 위 상황을 해결하기 위해서는 flatMap메서드를 사용하면 된다. 
         * flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다.
         * 즉, map(Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환한다.
         * 
         * flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 
         * 하나의 스트림으로 연결하는 기능을 수행한다.
         */
        System.out.println(Arrays.asList("Hello", "World").stream()
            // map에서 Stream<String[]> 스트림을 반환한다.
            .map(word -> word.split(""))
            /*
             * flatMap을 이용해서 하나의 평면화된 스트림을 반환한다.
             * 즉 Stream<String> 스트림이 반환된다.
             */
            .flatMap(Arrays::stream)
            .distinct()
            .collect(Collectors.toList()));

    }

}

스트림 활용

  • 데이터를 어떻게 처리할지는 스트림 API가 관리하므로 편리하게 데이터 관련 작업을 할 수 있다.
  • 스트림 API 내부적으로 다양한 최적화가 이루어질 수 있다.
  • 내부 반복뿐 아니라 코드를 병렬로 실행할지 여부도 결정할 수 있다. 이러한 일은 순차적인 반복을 단일 스레드로 구현했기 때문에 외부 반복으로는 불가능하다.

요약

  • 스트림 API를 이용하면 복잡한 데이터 처리 질의를 표현할 수 있다.
  • filter, distinct, skip, limit 메서드로 스트림을 필터링하거나 자를 수 있다.
  • map, flatMap 메서드로 스트림의 요소를 추출하거나 변환할 수 있다.
  • findFirst, findAny 메서드로 스트림의 요소를 검색할 수 있다. allMatch, noneMatch, anyMatch 메서드를 이용해서 주어진 프레디케이트와 일치하는 요소를 스트림에서 검색할 수 있다.
  • 이들 메서드는 쇼트서킷(short-circuit), 즉 결과를 찾는 즉시 반환하며, 전체 스트림을 처리하지는 않는다.
  • reduce 메서드로 스트림의 모든 요소를 반복 조합하며 값을 도출할 수 있다. 예를 들어 reduce로 스트림의 최대값이나 모든 요소의 합계를 계산할 수 있다.
  • filter, map 등은 상태를 저장하지 않는 "상태 없는 연산(stateless operation)"이다 reduce 같은 연산은 값을 계산하는 데 필요한 상태를 저장한다. sorted, distinct 등의 메서드는 새로운 스트림을 반환하기 앞서 스트림의 모든 요소를 버퍼에 저장해야 한다. 이런 메서드를 "상태 있는 연산(stateful operation)"이라고 부른다.
  • IntStream, DoubleStream, LongStream은 기본형 특화 스트림이다. 이들 연산은 각각의 기본형에 맞게 특화되어 있다.
  • 컬렉션뿐 아니라, 값, 배열, 파일, iterate와 generate 같은 메서드로도 스트림을 만들 수 있다.
  • 크기가 정해지지 않는 스트림을 무한 스트림이라고 한다.
반응형