ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Part2] Java8 In action - Chapter5 - 1
    Java8 In Action 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 같은 메서드로도 스트림을 만들 수 있다.
    • 크기가 정해지지 않는 스트림을 무한 스트림이라고 한다.
    반응형

    댓글

Designed by Tistory.