본문 바로가기
카테고리 없음

모던 자바 인 액션 스터디 4. 스트림으로 데이터 수집

by 손너잘 2021. 3. 14.

웨지

6장 퀴즈 나갑니다아OX퀴즈 5개 할게용 이유도 달아주심 좋아요

  1. GroupingBy를 활용하여 PartitionBy와 동일한 분할을 구현할 수 있다 (O / X)
  2. PartitionBy를 활용하여 GroupingBy와 동일한 그룹핑을 구현할 수 있다 (O / X)
  3. Collectors의 정적 메소드 counting, summingInt, maxBy, minBy는 요소가 없을 경우 0을 반환한다. (O/X)
  4. 내 상황에 적합한 Collector 정적 메소드가 없을 경우엔 Collector인터페이스 구현체를 집적 구현하는 방법 밖에 없다. (O/X)
  5. 병렬 스트림인 경우 Collect도 항상 병렬 리듀싱을 실행한다. (O/X)

나의 답

더보기

1. O, groupingBy(i -> 조건식) 으로 같은 기능을 구현할 수 있다.

2. X, partitionBy는 Predecate를 이용한 분류, 따라서 groupingBy와는 같은 구현이 불가능하다.

3. X, Optional을 반환한다.

4. X, collect()에서 바로 생성이 가능하다.
5. X, collect 구현에서 Concurrent 가 되어있어야 수행한다.


손너잘

 

손너잘은 신규 뱅킹 서비스를 런칭했다. 새로운 서비스에서는, 사용자의 연령대 별 적금에 대한 통계를 보여주고자 한다.

신규 서비스의 개발을 맡은 손너잘은 회사로 부터 아래와 같은 요구사항을 받는다.

public class Main {

    static class Customer {
        private final String name;
        private final int[][] savingLogs;
        private final int age;

        public Customer(String name, int[][] savingLogs, int age) {
            this.name = name;
            this.savingLogs = savingLogs;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int[][] getSavingLogs() {
            return savingLogs;
        }

        public int getAge() {
            return age;
        }
    }

    enum AgeRange {
        TEEN_TWENTY,
        TWENTY_THIRD,
        THIRD_FOURTH;
    }

    public static void main(String[] args) {
        List<Customer> customers = List.of(
                new Customer("손너잘", new int[][]{
                        {74, 80, 1, 68, 74, 80, 59, 68, 74, 80, 59, 68},
                        {45, 34, 12, 54, 63, 2232, 64, 34, 13, 34, 75, 33},
                        {23, 34, 66, 624, 74, 43, 1231, 56, 46, 34, 57, 43},
                        {13, 44, 56, 74, 4235, 45, 6, 23445, 53, 66, 77, 5},
                }, 20),
                new Customer("손나잘", new int[][]{
                        {74, 830, 59, 68, 74, 80, 5239, 268, 74, 80, 59, 68},
                        {45, 34, 22, 54, 63, 2232, 64, 34, 13, 34, 75, 33},
                        {23, 34, 2366, 64, 74, 43, 13241, 56, 46, 34, 57, 43},
                        {13, 44, 5623, 723, 45, 45, 643, 54645, 53, 66, 77, 5},
                }, 24),
                new Customer("발너잘", new int[][]{
                        {74, 80, 59, 68, 74, 80, 59, 68, 74, 80, 59, 68},
                        {5, 4, 22, 54, 63, 2, 64, 34, 3, 34, 75, 33},
                        {23, 3, 6, 4, 74, 43, 11, 56, 6, 34, 7, 43},
                        {13, 44, 56, 3, 4, 4, 6, 45, 53, 66, 77, 5},
                }, 34),
                new Customer("발너못", new int[][]{
                        {74, 80, 59, 68, 74, 80, 59, 68, 74, 80, 59, 68},
                        {45, 3124, 22, 54, 63, 222, 64, 34, 13, 34, 75, 333},
                        {23, 3412, 66, 64, 74, 413, 121, 56, 46, 34, 57, 43},
                        {13, 44, 56, 7, 45, 45, 6, 45, 53, 66, 747, 25},
                }, 38),
                new Customer("손잘너", new int[][]{
                        {74, 80, 59, 684, 74, 80, 59, 68, 74, 80, 59, 648},
                        {45, 334, 22, 54, 63, 22, 64, 34, 13, 334, 75, 133},
                        {213, 34, 66, 64, 74, 43, 11, 561, 46, 34, 57, 43},
                        {113, 434, 546, 537, 45, 45, 6, 45, 53, 6136, 77, 5},
                }, 18),
                new Customer("손너못", new int[][]{
                        {74, 80, 59, 68, 74, 80, 59, 68, 74, 80, 59, 68},
                        {42345, 34, 2122, 54, 1363, 22, 64, 34, 13, 34, 75, 33},
                        {23, 34, 66, 634, 74, 43234, 11, 56, 46, 34, 57, 43435},
                        {13, 44, 23456, 7, 45, 45, 6, 45, 53, 66, 77, 345},
                }, 14),
                new Customer("발냄새", new int[][]{
                        {74, 80, 54329, 68, 74, 523480, 59, 68, 74, 23480, 59, 2348},
                        {45, 34, 22, 54, 63, 22342, 64, 34, 13, 34, 75, 32343},
                        {23, 34, 23466, 64, 74, 43, 23411, 56, 4634, 34, 57, 43},
                        {123453, 44, 56, 7234, 45, 45, 6, 45, 53, 66, 77, 5},
                }, 37)
        );
    }
}

고객들의 4년간의 적금 이력과, 이름, 나이가 주어진다. 주어진 배열의 행은 년수, 열은 각 달마다 저금한 금액을 나타낸다.

적금의 금리는 복리로 계산되며, 매 달마다 이전 달의 금액의 10퍼센트를 더하여 계산한다.

고객의 나이대는 10~20, 20~30, 30~40 대로 구성되어 있으며 이는 enum 클래스에 지정된 요소만으로 구분해야 한다.

enum클래스는 필요에 따라 언제든지 수정 가능하다.

 

손너잘이 서비스를 개발할 수 있도록 도와주자. 출력은, 사용자의 연령대별, 이자를 포함한 최고 적금금액과, 이자를 포함한 평균 적금금액을 출력해야 한다.

 

 

더보기

 

enum AgeRange {
    TEEN_TWENTY(age -> 10 <= age && age < 20),
    TWENTY_THIRD(age -> 20 <= age && age < 30),
    THIRD_FOURTH(age -> 30 <= age && age < 40);

    private final IntPredicate intPredicate;

    AgeRange(IntPredicate intPredicate) {
        this.intPredicate = intPredicate;
    }

    public static AgeRange judge(int age) {
        return Arrays.stream(values())
                .filter(i -> i.intPredicate.test(age))
                .findAny()
                .get();
    }
}

public static void main(String[] args) {
    ... 중략
    Map<AgeRange, DoubleSummaryStatistics> collect = customers.stream()
            .collect(
                    groupingBy(
                            c -> AgeRange.judge(c.getAge()),
                            mapping(i -> Arrays.stream(i.getSavingLogs())
                                            .flatMapToInt(Arrays::stream)
                                            .mapToDouble(j -> j)
                                            .reduce(0, (j, k) -> j + k * 1.1),
                                    summarizingDouble(i -> i)
                            )
                    )
            );
    System.out.println(collect.get(AgeRange.TEEN_TWENTY).getMax() + " " + collect.get(AgeRange.TEEN_TWENTY).getAverage());
    System.out.println(collect.get(AgeRange.TWENTY_THIRD).getMax() + " " + collect.get(AgeRange.TWENTY_THIRD).getAverage());
    System.out.println(collect.get(AgeRange.THIRD_FOURTH).getMax() + " " + collect.get(AgeRange.THIRD_FOURTH).getAverage());
}

파즈

6장

  1. 적절한 Collectors 클래스의 정적 팩토리 메서드로 답하시오. 
    1. 스트림의 항목에서 정수 프로퍼티 값을 더하기 위한 메서드
    2. 스트림 항목의 정수 프로퍼티의 평균값을 위한 메서트
    3. 다른 컬렉터로 감싸고 그 결과에 변환 함수를 적용하기 위한 메서드
  2. " : " 로 각 String 요소들을 이으려고 한다. collect( ? )  ?에 들어갈 것을 적으시오.
  3. maxBy와 minBy 는 인자로 어떤 형식을 가지고 있는지 적으시오.
  4. groupingBy와 partitioningBy는 어떤 점이 다른지 답하시오.
  5. collect 내의 reducing 메서드와 map 후 reduce 메서드를 하는 것의 차이점을 적으시오.

나의 답

더보기

1.  summingInt(), averagingInt(), collectAndThen()

2. joining(":")

3. Comparator

4. Key 요소가 groupingBy는 K, partitioningBy는 Boolean으로 되어있다.

5. ??

 

웨지의 답

5.의미론적인 차이 발생, reduce는 각 요소들을 하나하나 누적시키는 데 목적이 있고 collect는 자료구조를 생성하는데 목적이 있음, 현실적으로는 reduce에서 concurrent하지 않은 자료구조를 생성하고 누적하려고 하면 결과가 박살날수도 있음


와일더

돈이 부족한 와일더에게 배민 할인쿠폰이 생겼다.
하지만 할인쿠폰을 사용하기 위해서는 제약사항이 존재한다.
최소 주문 금액이 만 원이 넘어야하며 만 오천원까지 주문금액의 50%를 할인받을 수 있다.
쿠폰을 사용하기 위해서는 배달비를 제외한 순수 금액이 만원이 넘어야하며 할인 적용은 배달비를 포함하여 적용된다고 한다.
극한의 효율을 내기 위해 만 오천원어치를 구매하고 50% 할인율을 모두 적용 받을 수 있는 매장을 골라내보자.

public class Main {
    public static void main(String[] args) {
        List<Brand> brands = Arrays.asList(
                new Brand("피자알볼로", 2000, 15000),
                new Brand("버거킹", 0, 18000),
                new Brand("롯데리아", 0, 20000),
                new Brand("이삭 토스트", 2000, 12000),
                new Brand("멕시카나", 5000, 15000),
                new Brand("던킨 도너츠", 3000, 12000),
                new Brand("1L 커피", 3400, 8000)
        );
    }
}

 

나의 답

 

나봄

각 그룹별 가장 높은 score를 가진 학생들에게 배민 쿠폰을 쏘려고 한다!
Student 객체는 다음과 같다.

public class Student {
    enum Group {
        A, B, C
    }
    private static int serial = 0;
    private final Group group;
    private final String name;
    private final int score;
    public Student(Group group, String name, int score) {
        this.group = group;
        this.name = name;
        this.score = score;
    }
    public static Student create(Group group) {
        Student student = new Student(group, "name" + group.name() + serial, serial * 10);
        serial++;
        return student;
    }
    public Group getGroup() {
        return group;
    }
    public String getName() {
        return name;
    }
    public int getScore() {
        return score;
    }
}

그리고 실행시키는 AppRunner 클래스는 다음과 같다.

public class AppRunner {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            students.add(Student.create(Student.Group.A));
            students.add(Student.create(Student.Group.B));
            students.add(Student.create(Student.Group.C));
        }
    }
}

각 그룹별로 가장 높은 점수의 학생의 이름을 Map<Student.Group, String>으로 그루핑을 해주세요! 그리고 그루핑된 Map을 Group명 : 이름 처럼 출력해주세요!!

 

나의 답

더보기
public static void main(String[] args) {
    List<Student> students = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        students.add(Student.create(Student.Group.A));
        students.add(Student.create(Student.Group.B));
        students.add(Student.create(Student.Group.C));
    }

    students.stream()
            .collect(groupingBy(
                            Student::getGroup,
                            collectingAndThen(
                                    maxBy(comparingInt(Student::getScore)),
                                    student -> student.get().getName()
                            )
                    )
            ).entrySet().forEach(System.out::println);
}

라이언

@@@@에 들어갈 코드를 작성하시오,
또한 Function.identity() 를 사용하는것 과 람다식(x->x)을 사용하는 것의 차이를 서술하시오.

public class Application {
    public static void main(String[] args)
    {
        List<String> g
                = Arrays.asList("우아한", "테크", "코스");
        Map<String, Long> result
                = g.stream().collect(
                Collectors.@@@@@@(
                        Function.identity(),
                        Collectors.@@@@@@()));
        System.out.println(result);
    }
}
//Output:
//        {코스=1, 테크=1, 우아한=1}

나의 답

더보기

toMap, counting

 

의미론적 차이, Function.identity는 static 함수이기 때문에 인스턴스화 새로운 인스턴스를 생성하지 않아 성능적 이점이 있다.

완태

6장 질문.
groupingBy 이용하여, 충분히 partitioningBy 를 구현하여 사용할 수 있을 것 같은데요,
그래도 만든 이유는 있을텐데요..
partitioningBy를 이용했을 때 가질 수 있는 장점들이 어떤 것들이 있을까요?

 

구현 예시

Map<Boolean, List<Employee>> grouped = employees.stream()
                .collect(Collectors.groupingBy(Employee::isActive));
List<Employee> activeEmployees = grouped.get(true);
List<Employee> formerEmployees = grouped.get(false)

나의 답

더보기

의미론적 차이일 것 같다. Predecate를 사용함으로써 2분법으로 분리하겠다는 의지..?

완태의 답

empty를 넣었을 때, 리턴 값.

System.out.println(
Stream.empty().collect(Collectors.partitioningBy(a -> false)));
// Output: {false=[], true=[]}System.out.println(
Stream.empty().collect(Collectors.groupingBy(a -> false)));
// Output: {}

다니

6장 문제

  1. collect / Collectors / Collection 의 차이를 설명해주세요.
  2. Collector 인터페이스의 5가지 메소드를 설명해주세요.
  3. 아래 결과처럼 나오도록 @@@ 이 부분에 groupingBy() 를 이용해서 코드를 작성해주세요.

나의 답

 

// Application.java
public class Application {
    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(
                new Dog("치와와", 7),
                new Dog("스피츠", 15),
                new Dog("포메라니안", 2),
                new Dog("진돗개", 5));
        Map<String, List<Dog>> dogsByAge = @@@
        for (Map.Entry<String, List<Dog>> dogMap : dogsByAge.entrySet()) {
            List<Dog> dogValues = dogMap.getValue();
            List<String> dogNames = dogValues.stream()
                    .map(Dog::getName)
                    .collect(toList());
            System.out.print(dogMap.getKey() + " = ");
            System.out.println(String.join(", ", dogNames));
        }
    }
}
// Dog.java
package stream6;
public class Dog {
    private final String name;
    private final int age;
    public Dog(final String name, final int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return this.name;
    }
    public int getAge() {
        return this.age;
    }
}
// 결과
young = 포메라니안, 진돗개
old = 스피츠
middle-aged = 치와와

나의 답.

더보기

1. collect는 데이터 수집을 위한 최종 연산, collectors를 데이터 수집을 위한메소드 팩토리 패키지, Collection은 자바의 자료구조 패키지

2. 

supplier : 누적 연산을 하기 위한 새로운 자료구조 컨테이너 생성

accumulator : 누적 연산 부분, 새로운 요소와 supplier를 통해 생성된 컨테이너를 인수로 받는다.

finisher : 최종적으로 반환하기 위함. T->T를 할 수 도 있고 T->A를 할 수 도 있다.

combiner : 두 컨테이너를 합치기 위함. 병렬연산에서 각자 연산한 결과를 합치기 위해 사용

characteristics : 컬렉터 연산의 특징을 정의, Set방식으로 반환.

 

3.

public class Solution {
    static class Dog {
        private final String name;
        private final int age;
        public Dog(final String name, final int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return this.name;
        }
        public int getAge() {
            return this.age;
        }
    }

    interface AgeSpecification {
        boolean isSatisfiedBy(int age);
        String getName();
    }

    static class YoungAge implements  AgeSpecification {
        private final String name = "young";

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean isSatisfiedBy(int age) {
            return age <= 5;
        }
    }

    static class MiddleAge implements  AgeSpecification {
        private final String name = "middle-aged";

        @Override
        public String getName() {
            return name;
        }
        @Override
        public boolean isSatisfiedBy(int age) {
            return 5 < age && age <= 10;
        }
    }

    static class OldAge implements  AgeSpecification {
        private final String name = "old";

        @Override
        public String getName() {
            return name;
        }
        @Override
        public boolean isSatisfiedBy(int age) {
            return 10 < age;
        }
    }

    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(
                new Dog("치와와", 7),
                new Dog("스피츠", 15),
                new Dog("포메라니안", 2),
                new Dog("진돗개", 5));

        List<AgeSpecification> ageSpecifications = List.of(
                new YoungAge(),
                new MiddleAge(),
                new OldAge()
        );

        Map<String, List<Dog>> dogsByAge = dogs.stream()
                .collect(groupingBy(dog -> ageSpecifications.stream()
                        .filter(ages -> ages.isSatisfiedBy(dog.getAge())).findAny().get()))
                .entrySet().stream().collect(toMap(entry -> entry.getKey().getName(), Map.Entry::getValue));

        for (Map.Entry<String, List<Dog>> dogMap : dogsByAge.entrySet()) {
            List<Dog> dogValues = dogMap.getValue();
            List<String> dogNames = dogValues.stream()
                    .map(Dog::getName)
                    .collect(toList());
            System.out.print(dogMap.getKey() + " = ");
            System.out.println(String.join(", ", dogNames));
        }
    }

 

김김

이번 장은 유독 함수형 코드 특유의 괄호가 넘쳐나네요. 코드의 원형이 헷갈려서 공식 docs를 뒤져보니, groupingBy는 다음과 같이 3개의 원형을 가지고 있네요.

groupingBy(Function<? super T,? extends K> classifier)
groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)

3번은 잘 다루어지지 않고 있으니 생략하겠습니다. 1번과 2번의 원형을 보고 classifier와 downstream으로 받는 것이 무엇인지 생각해보고, 각각의 오버로딩된 함수를 사용했을 때 기대할 수 있는 기댓값(리턴 타입의 차이 등)은 어떻게 다를지 설명해봅시다.

 

더보기

classifier는 Map에서 Key를 매핑하기 위한, 각 요소가 어느 Key에 Mapping 될지 정하는 Function함수

downstream은 value가 어느 형식으로 저장될지 정하는 collector

찰리

6장 문제어설픈자는 살아남지 못하는 우테코 야생
권위의 균형을 맞추기위해 다양한 데이터를 수집중입니다.총 5개의 데이터를 수집하려고 합니다.
각각의 결과를 알려주세요.
출력양식은 신경쓰지 마시고 편한대로 표현해주시면 됩니다.Wooteco 객체는 이름만 출력하도록 되어있습니다.ex)
BACKEND=[Cobi, Roid, Woni]
true = [FRONTEND = [ name, is, name ]

public static void main(String[] args) {
    List<Wooteco> wootecos = Arrays.asList(
            new Wooteco("Cobi", Type.BACKEND, 9501),
            new Wooteco("Gson", Type.BACKEND, 9500),
            new Wooteco("Roid", Type.FRONTEND, 9999),
            new Wooteco("GU", Type.BACKEND, 9500),
            new Wooteco("Woni", Type.QUEEN, 30000),
            new Wooteco("Jun", Type.FRONTEND, 9499),
            new Wooteco("Crown", Type.BACKEND, 9502)
    );
    // 1번 1번 1번 1번
    Map<Type, List<Wooteco>> groupByType = wootecos.stream()
            .collect(groupingBy(Wooteco::getType));
    System.out.println(groupByType);
    // 2번 2번 2번 2번
    Map<Type, Wooteco> mostAuthorityGroupByType = wootecos.stream()
            .collect(groupingBy(Wooteco::getType, collectingAndThen(
                    maxBy(Comparator.comparingInt(Wooteco::getAuthority)),
                    Optional::get)));
    System.out.println(mostAuthorityGroupByType);
    // 3번 3번 3번 3번
    Map<Type, Wooteco> mostAuthorityGroupByType2 = wootecos.stream()
            .collect(toMap(Wooteco::getType, Function.identity(), BinaryOperator.maxBy(Comparator.comparingInt(Wooteco::getAuthority))));
    System.out.println(mostAuthorityGroupByType2);
    // 4번 4번 4번 4번
    Map<Type, Integer> totalAuthorityGroupByType = wootecos.stream()
            .collect(groupingBy(Wooteco::getType, summingInt(Wooteco::getAuthority)));
    System.out.println(totalAuthorityGroupByType);
    // 5번 5번 5번 5번
    Map<Boolean, Map<Type, List<Wooteco>>> partitionWooteco = wootecos.stream()
            .filter(wooteco -> wooteco.getAuthority() > 9500)
            .collect(partitioningBy(Wooteco::isQueen, groupingBy(Wooteco::getType)));
    System.out.println(partitionWooteco);
}
class Wooteco {
    private String name;
    private Type type;
    private int authority;
    public Wooteco(String name, Type type, int authority) {
        this.name = name;
        this.type = type;
        this.authority = authority;
    }
    public String getName() {
        return name;
    }
    public Type getType() {
        return type;
    }
    public int getAuthority() {
        return authority;
    }
    public boolean isQueen() {
        return type.equals(Type.QUEEN);
    }
    public boolean isBackEnd() {
        return type.equals(Type.BACKEND);
    }
    public boolean isFrontEnd() {
        return type.equals(Type.FRONTEND);
    }
    @Override
    public String toString() {
        return "{ " + name + " }";
    }
}
enum Type {
    BACKEND,
    FRONTEND,
    QUEEN;
}

나의 답

더보기

1. BACKEND = cobi, gson, gu, crown FRNTEND = roid, jun, QUEEN = woni

2. BACKEND = crown, FRONT = roid, QUEEN = woni

3. BACKEND = crown, FRONT = roid, QUEEN = woni

4.BACKEND = 38002, FRONTEND = 19498, QUEEEN = 30000

5. true = [queen = woni], false = [backend = coni, crown, frontend = roid]

중간곰

지난주 완태의 문제 기억하시나요?
병렬 스트림은 실행 순서에 따라 값이 달라질 수 있음을 알게 됐는데요.책을 읽다가 ParallelStream으로도 같은 결과를 만들 수 있다는 사실을 알았어요.
아래 코드를 살짝 고쳐서 아래 기대 결과가 나오게 만들어보세요.
(힌트. 인자 3개짜리 reduce 메서드 이용)

 

final List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0);
final double sequentialMinus = numbers.stream()
    .reduce(0.0, (a, b) -> a - b);
final double parallelMinus = numbers.parallelStream()
    .reduce(0.0, (a, b) -> a - b);
System.out.println(sequentialMinus + " / " + parallelMinus);
final double sequentialDivide = numbers.stream()
    .reduce(1.0, (a, b) -> a / b);
final double parallelDivide = numbers.parallelStream()
    .reduce(1.0, (a, b) -> a / b);
System.out.println(sequentialDivide + " / " + parallelDivide);
/* 
  현재 실행 결과
  -10.0 / 0.0
  0.041666666666666664 / 1.5
  기대 결과
  -10.0 / -10.0
  0.041666666666666664 / 0.041666666666666664
 */

 

나의 답

더보기

 

    public static void main(String[] args) {
        final List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0);
        final double sequentialMinus = numbers.stream()
                .reduce(0.0, (a, b) -> a - b);
        final double parallelMinus = numbers.parallelStream()
                .reduce(0.0, (a, b) -> a - b, Double::sum);
        System.out.println(sequentialMinus + " / " + parallelMinus);
        final double sequentialDivide = numbers.stream()
                .reduce(1.0, (a, b) -> a / b);
        final double parallelDivide = numbers.parallelStream()
                .reduce(1.0, (a, b) -> a / b, (a, b) -> a * b);
        System.out.println(sequentialDivide + " / " + parallelDivide);
/*
  현재 실행 결과
  -10.0 / 0.0
  0.041666666666666664 / 1.5
  기대 결과
  -10.0 / -10.0
  0.041666666666666664 / 0.041666666666666664
 */
    }
}

검프

Dish의 이름을 의미있게 모으기 위해 사용할 수 있는 기능이 여러개 있습니다.
이번엔 Collectos의 joining, String의 joining, Collectors의 reduce로 해봤는데요.
이 3가지의 차이가 무엇이고, 어떤걸 쓰는게 더 좋을까요?

package modernjavainaction.chap06;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
public class Application {
    public static void main(String[] args) {
        List<Dish> menu = Dish.menu;
        //collectors를 사용한다.
        String collectMenu = menu.stream()
                .map(Dish::getName)
                .collect(Collectors.joining(", "));
        System.out.println("collectMenu = " + collectMenu);
        //String.join을 사용한다.
        String joinMenu = String.join(", ", menu.stream().map(Dish::getName).collect(Collectors.toList()));
        System.out.println("joinMenu = " + joinMenu);
        //reduce를 사용한다.
        String reduceMenu = menu.stream()
                .collect(Collectors.reducing("", Dish::getName, (s1, s2) -> s1 + s2 + ", "));
        System.out.println("reduceMenu = " + reduceMenu);
    }
    static public class Dish {
        public static final List<Dish> menu = asList(
                new Dish("pork"),
                new Dish("beef"),
                new Dish("chicken"),
                new Dish("rice")
        );
        private final String name;
        public Dish(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }
}

나의 답

더보기

joining과 String.join은 내부적으로 Stringjoinner를 사용한다. 스트림의 경우 스트림을 여는 오버헤드가 더 있으므로 String.join을 사용한다.

reducing의 경우는 누적연산이 진행될 때 마다 새로운 인스턴스를 생성하므로 성능상 좋지 않다.

댓글