JAVA-Basic

Lambda , Stream 개인 정리

Jungsoomin :) 2021. 1. 10. 01:18
  1. 코드의 간결 성, 함수를 이해하는 개인 간의 가독 성 상승

  2. Collection 의 요소를 순회하여 Filtering , Mapping 가능

  3. Collection 의 요소를 간결한 코드로 순회하여 결과를 만들어 낼 수 있다.

  4. @FunctionalInterface , 함수형 인터페이스에서 람다식 사용 허용

  5. Anonymous Function , 익명 함수를 만들어내는 방식으로 작성된다. 

  6. 즉, Runtime 시점에 Anonymous Implementation Object 를 생성하는 형태


기본문법

  1. 람다식 -> 매개변수를 가진 코드블록 -> 익명 구현 객체

  2. (매개변수) -> { … } 

  3. (매개변수) 란은 Lambda 에서 사용될 값을 제공

  4. 하나의 매개변수를 가진다면 괄호를 생략가능

  5. 하나의 Statement 로 끝난다면 중괄호 및 return 키워드 생략가능

  6. 리턴 타입을 명시적으로 표현하기 위해 (Return Type) 을 기입 가능

Predicate predicate = (Predicate) o -> "@java.lang.FunctionalInterface()".equals(Predicate.class.getAnnotations()[0].toString());
        //구현 메서드 실행
        boolean test = predicate.test(predicate);
        System.out.println(test); // $1 => true

 

@FunctionalInterface

  1. 람다식을 사용할 때 핵심은 [어떤 함수적 인터페이스를 구현할 것인가] 

  2. Java 에서 허용된 Lambda 작성 가능 인터페이스가 @FunctionalInterface

  3. 하나의 구현 메서드만이 정의 되며 JVM 은 하나의 함수 정보만을 확인

  4. Lambda Expression 에서 사용된 Parameter , Return Type 등을 이와 비교

  5. Java 에서는 단독으로 메서드를 선언하는 것이 불가능, 이에 따라 람다식은 메서드를 가진 [객체]를 생성한다.

  6. Lambda 를 사용할 때 대입되는 인터페이스[람다식의 Target Type] 이라고 부른다.

  7. 하나의 Abstract Method 를 가진 Interface 만이 람다식의 Target Type 이 될 수 있으며 , 이를 함수적 인터페이스 라고 부른다는 결론이 도출 

  8. @FunctionalInterface 어노테이션 선언 시 컴파일러가 두개 이상의 추상메서드가 선언되지 않게 체크한다.


 

 

Closure 와 Lambda

  1. Java 의 Closure 는 자신을 둘러싼 context 내의 변수, 즉 외부 범위 변수를 함수 내부로 바인딩 시킨다.

  2. Lambda 는 표현식에 잡힌 매개변수를 구현 함수 내부로 바인딩 시킨다.

  3. 그렇다면 Lambda 에서 외부 Context의 변수를 가져다가 쓰면 어떻게 되는가?

  4. 이 경우 Lambda 인 동시에 Closure 가 된다.

  5. 클래스 멤버인가,  로컬 변수인가에 따라 제약 사항이 갈린다.

  6. 람다식에서 선언되는 [this] [람다식을 실행한 객체] 이다.

public class ClosureTest {

    static <T> void waitFor(T input, Predicate<T> predicate) throws InterruptedException {
        // 인자로 넘겨주는 변수 외 외부 변수가 없기 때문에 효율적으로 연산
        while (predicate.test(input)) {
            System.out.println("Blocking");
            Thread.sleep(2500);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //Blocking... Blocking.... Blocking...
        Optional<Object> obj_opt = Optional.of(new Object());
        waitFor(obj_opt, Optional::isPresent);


    }
}

 


java.uitl.function

  1. Java 8 부터 빈번하게 사용되는 함수형 인터페이스들을 정의

  2. 목적 : 메소드 인자, 생성자의 인자로 Lambda Expression 을 사용하기 위함

  3. Consumer<T> | Supplier<T> | Function<T> | Operator<T> | Predicate<T> 

  4. 매개 값, 리턴 값의 유무에 따른 차이점을 가진다.


 

Consumer<T>

  • 매개 값이 있으나 리턴 타입은 void , 소비하는 함수형 인터페이스

  • XXXConsumer 로 나뉘며 [accept()] 메서드를 구현

  • Prefix 로 Long, Int, Obj, Bi 등이 있으며 직관적 구성을 가진다.

public class ConsumerTest {
    public static void main(String[] args) {
        Consumer<String> stringConsumer = s -> System.out.print(s);
        stringConsumer.accept("Hello ");

        BiConsumer<String,String> biConsumer = (s, s2) -> System.out.printf("%s %s ! ",s,s2);
        biConsumer.accept("Java","8");

        ObjIntConsumer<String> objIntConsumer = (s, value) -> System.out.printf("%s , %d.",s,value);
        objIntConsumer.accept("Lambda Expression",1);

        //Hello Java 8 ! Lambda Expression , 1.
    }
}

 


 

Supplier<T>

  • 매개 값이 없으며, 리턴 타입이 존재, 공급하는 함수형 인터페이스

  • XXXSupplier 로 나뉘며 [getXXX()] 메서드를 구현

  • Boolean, Double, Int, Long, <T> 로 직관적 구성을 가진다.

public class SupplierTest {
     static class Dice {
        private int num;

        public void setDiceNumber() {
            num = (int)(Math.random() * 6) + 1;
        }

        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) {

        Supplier<Dice> diceSupplier = Dice::new;
        Dice dice = diceSupplier.get();
        dice.setDiceNumber();

        IntSupplier intSupplier = () -> dice.getNum();

        int asInt = intSupplier.getAsInt();

        //Random Number
        System.out.println(asInt);
    }
}

 

Function<T,R>

  • 매개값 , 리턴값을 모두 가짐, [매핑]시키는 함수형 인터페이스

  • XXXFunction 으로 나뉘며, [applyXXX()] 메서드를 구현

  • Bi , A to B  |   To B, A 의 prefix 를 가지며 직관적 구성을 가진다.

  • T 는 보통 매개 값, R은 리턴 값을 나타내며, 여하에 따라 제네릭이 추가될 수 있다.

public class FunctionTest {
    static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Soo min",29),
                new Person("Jung jin", 29)
        );

        Function<Person,String> stringFunction = Person::getName;
        ToIntFunction<Person> toIntFunction = Person::getAge;

        //        Soo min
        //        Jung jin
        //        29
        //        29
        people.forEach(person -> System.out.println(stringFunction.apply(person)));

        people.forEach(person -> System.out.println(toIntFunction.applyAsInt(person)));

    }
}

 

Operator<T>

  • Function 과 동일, [매핑] 이 아니라 [연산] 값을 도출하는 함수적 인터페이스

  • XXXOperator 로 나뉘며, [applyXXX()] 메서드를 구현

  • Function 과는 다르게 리턴타입이 매개 값과 동일하다.

  • Bi, Unary, DoubleBinary, DoubleUnary 등의 prefix 를 가짐.  Unary 는 1  | Binary 는 2 로서 연산 매개 값을 의미

public class OperatorTest {
    private static int sum = 0;

    public static void main(String[] args) {
        int[] scores = {20,45,60,55,80};

        //Local Class 문제로 로컬 변수로 지정시 합산 불가
        IntUnaryOperator intUnaryOperator = operand -> sum += operand;

        Arrays.stream(scores).forEach(intUnaryOperator::applyAsInt);

        System.out.println(sum); // 260
    }
}

 

Predicate<T>

  • 매개 값, 리턴 값을 모두 가짐, boolean 값을 도출하는 함수형 인터페이스

  • XXXPredicate 로 나뉘며 [testXXX()] 메서드를 구현

  • Bi , <T> , Int , Double, Long 의 Prefix 를 가지며 직관적 구성을 가진다.

  • BiPredicate 의 경우 <T,U> 의 제네릭 선언 객체를 [비교]하는데 사용

public class PredicateTest {

    static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Soo min",29),
                new Person("Jung jin", 29)
        );
        BiPredicate<Person,Person> personPredicate  = (person1,person2) -> person1.getAge() == person2.getAge();

        BiPredicate<Person,Person> ageBiPredicate = (person, person2) -> person.getName().equals(person2.getName());

        BiPredicate<Person, Person> compositePredicate = personPredicate.and(ageBiPredicate);


        System.out.println(compositePredicate.test(people.get(0),people.get(1))); //false

        //isEqual
        Predicate condition = Predicate.isEqual("Java 8");

        System.out.println(condition.test("Java 8")); // true
    }
}

 

andThen , compose

  • Operator , Consumer , Function  에는 andThen() compose() default 메서드가 선언

  • 복수의 함수형 인터페이스 구현체를 이어서 일종의 PipeLine 을 형성하는 것

  • andThen() 은 정순 , compose() 는 역순을 나타낸다.

public class AndThenTests {

    static class Account {
        private int age;
        private String name;
        private String address;

        public Account(int age, String name, String address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        public int getAge() {
            return age;
        }

        public String getName() {
            return name;
        }

        public String getAddress() {
            return address;
        }
    }

    public static void main(String[] args) {
        Function<Account,String> function1 = Account::getName;
        Function<String ,String> function2 = s -> s.substring(s.lastIndexOf(" ")+1 , s.length());
        
        // 파이프라인으로 이어놓은 메서드 재정의
        Function<Account,String > compositeFunction = function1.andThen(function2);
        
        //실행
        String apply = compositeFunction.apply(new Account(29, "SooMin Jung", "address"));
        //Jung
        System.out.println(apply);
    }
}

 


 

and, or, negate, isEqual

  • Predicate 를 이어 최종적인 Predicate 객체를 리턴하는 일종의 Pipe Line 

  • && , || , ! 와 동일

  • isEqual 은 Static Method, Target 과 test() 의 매개변수가 같은 지 [검사] 한다.

public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Soo min",29),
                new Person("Jung jin", 29)
        );
        BiPredicate<Person,Person> personPredicate  = (person1,person2) -> person1.getAge() == person2.getAge();

        BiPredicate<Person,Person> ageBiPredicate = (person, person2) -> person.getName().equals(person2.getName());

        BiPredicate<Person, Person> compositePredicate = personPredicate.and(ageBiPredicate);


        System.out.println(compositePredicate.test(people.get(0),people.get(1))); //false

        //isEqual
        Predicate condition = Predicate.isEqual("Java 8");

        System.out.println(condition.test("Java 8")); // true
    }

 

minBy, maxBy

  • BiOperator 에서 제공하는 정적 메서드

  • 두 매개 값을 비교하여 높거나 낮은 값을 리턴하는 Operator 를 리턴

public class MinByMaxBy {
    static class Name {
        String word;
        int primary;

        public Name(String word, int primary) {
            this.word = word;
            this.primary = primary;
        }

        public String getWord() {
            return word;
        }

        public int getPrimary() {
            return primary;
        }
    }
    public static void main(String[] args) {


        BinaryOperator<Name> lastNameOperator = BinaryOperator.maxBy((o1, o2) -> Integer.compare(o1.primary, o2.primary));
        Name apply = lastNameOperator.apply(new Name("Jung", 1), new Name("SooMin", 0));

        System.out.println(apply.getWord()); // Jung

        BinaryOperator<Name> firstNameOperator = BinaryOperator.minBy(Comparator.comparingInt(o -> o.primary));
        apply = firstNameOperator.apply(new Name("Jung", 1), new Name("SooMin", 0));

        System.out.println(apply.getWord()); // SooMin
    }
}

 

메서드 참조, 생성자 참조

  1. 간단한 람다식에서 불필요한 매개변수 기입을 생략하기 위해 사용

  2. 매개변수와 리턴타입을 해석하여 메서드의 매개변수와 리턴타입에 그대로 대입

  3.  :: 기호를 사용, 클래스 :: 메서드  |  참조변수 :: 메서드 | 클래스 :: new

  4. 생성자 오버로딩의 경우 매개 값 수에 따라 자동으로 생성

public class ConstructorReference {
    static class Account {
        private String  name;

        private int age;

        public Account(String name) {
            System.out.println("OneArgsConstructor");
            this.name = name;
        }

        public Account(String name, int age) {
            System.out.println("TwoArgsConstructor");
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Account{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    public static void main(String[] args) {
        Function<String,Account> oneArgFunction = Account::new;
        Account account = oneArgFunction.apply("SooMin");

        System.out.println(account.toString()); // Account{name='SooMin', age=0}



        BiFunction<String, Integer, Account> twoArgsFunction = Account::new;
        Account sooMin = twoArgsFunction.apply("SooMin", 29);

        System.out.println(sooMin.toString()); // Account{name='SooMin', age=29}
    }
}

 

Stream Pipe Line 을 위한 선수 지식

  1. Collection Framework , Lambda , java.util.function 에 대한 이해가 선수지식

  2. Stream Pipe Line 작성 시 Lambda 를 사용, java.util.function 의 인터페이스 사용

  3. 선수 지식을 통해 알게 된 객체를 이용하여 Stream 의 Pipe Line 을 이어주는 것이 Stream Pipe Line 

  4. Iterator , Collections, Optional 등이 사용 됨

 


 

Stream?

  1. 많은 데이터를 다룬다 => Array , Collection

  2. 순회 한다 => for , Iterator

  3. 데이터 소스마다 다른 방식으로 반복자를 돌려 데이터를 다루게 됨

  4. 또한 코드의 가독성이 떨어지며, 재사용성은 감소한다.

  5. 데이터 소스의 추상화. 즉, 어떠한 데이터를 다루던 같은 방식으로 다루게한다.

public class Basic {
    public static void main(String[] args) {
        String[] strArr = {"aaa", "bbb", "ccc"};
        List<String> strList = Arrays.asList(strArr);

        // 다른 데이터 소스도 정렬 -> 출력하는 방식이 동일하고, 간결하다.
        Arrays.stream(strArr).sorted().forEach(System.out::println);
        strList.stream().sorted().forEach(System.out::println);
    }
}

 


 

Stream 의 특징

  1. 데이터 소스를 읽을 뿐, 변경하지 않음. 새로운 결과를 리턴

  2. 한번 열린 스트림은 닫히기 때문일회용

  3. 메서드 내부에 반복문을 숨기고 있어 가독성이 상승

public class Specify {
    public static void main(String[] args) {

        String[] strArr = {"ccc", "bbb", "aaa"};

        Stream<String> stream = Arrays.stream(strArr);

        List<String> collectList = stream.sorted().collect(Collectors.toList());

        //IllegalStateException : stream has already been operated upon or closed
        long count = stream.sorted().count();

//        void forEach(Consumer<? super T> action){
//            Objects.requireNonNull(action);
//
//            for(T t : src) {
//                action.accept(t);
//            }
//        }
    }
}

 

Stream - 연산

  1. Stream 의 연산과정[중간] [최종] 으로 나뉨

  2. 중간 연산은 리턴타입이 Stream<T> 이므로 메서드 체인이 가능

  3. 최종연산은 요소를 소모 후 다른 리턴타입을 가지므로 단 한번 사용

  4. 최종 연산이 정의되지 않는다면 중간 연산도 실행하지 않음

  5. 핵심 메서드는 map() , flatMap()  /  reduce(), collect() . 이외 메서드는 직관적이다.

  6. Wrapper 클래스를 박싱하고 언박싱하는 [비효율성 감소]를 위해 IntStream, LongStream, DoubleStream 이 제공

  7. parallel() <-> sequential() 으로 병렬처리를 하거나 취소 가능

  8. distinct , filter, limit, skip, peek, sorted, map, flatMap ... -> forEach, count, find, match, toArray, reduce, collect ...


 

Stream - 생성

  1. Array => Arrays.stream

  2. Collection => 조상클래스 Collection 에 stream 이 정의 

  3. Stream.of(T... value)

  4. PrimitiveStream => IntStream.of, DoubleStream.of …

  5. 범위 정수 => rangeClosed 

  6. Lambda => Stream.iterate(T , UnaryOperator) , Stream.generate(Supplier)

  7. File => Files.list(Path)

  8. Empty => Stream.empty

  9. Concat => Stream.concat( Stream<T>, Stream<T> )


 

Stream - 중간연산

  • skip, limit : 매개값은 long, 요소를 건너뛰거나, 자름

  • distinct, filter(Predicate<T>) : 중복제거(equals), Predicate 연산 값이 false 경우 제거

  • sorted(Comparator<T>)comparing, comparingInt 등의 메서드에 thenComparing 을 이어 추가 정렬 조건을 제시  데이터소스가 Comparable 을 구현하면 간결하게 작성 가능

  • peek : 중간 결과를 로깅하는데에 유용, 중간 연산이므로 Stream을 닫지 않음

public class MiddleOper {

    static class Student  {

        private int score;

        private String name;

        public Student(int score, String name) {
            this.score = score;
            this.name = name;
        }

        public int getScore() {
            return score;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "score=" + score +
                    ", name='" + name + '\'' +
                    '}';
        }

    }

    public static void main(String[] args) {
        IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);

        // 1, 5, 5
        intStream.filter(i -> i%2 != 0).filter(i -> i%3 !=0).forEach(System.out::println);

        Stream<Student> studentStream = Stream.of(
                new Student(50,"정수민"),
                new Student(90,"신용권"),
                new Student(92, "남궁성"),
                new Student(96, "최범균")
        );

//        Student{score=96, name='최범균'}
//        Student{score=92, name='남궁성'}
//        Student{score=90, name='신용권'}
//        Student{score=50, name='SooMin'}
        studentStream.sorted(Comparator.comparingInt(Student::getScore).reversed()).forEach(System.out::println);
    }
}

 

Stream - map

  1. java.util.functionFunction<T,R> 을 사용

  2. 원하는 데이터조작을 체인으로 역할에 따라 분배  

  3. 단일 책임형식의 Method Chain 이 가독성을 상승 시킴

public class Map {
    public static void main(String[] args) {
        String[] strArr = {"/etc/apt/sources.list","/etc/network/interfaces","main/java/Map.class","File.txt","None",};

        List<String> collect = Arrays.stream(strArr).filter(s -> (s.lastIndexOf("/") != -1) || (s.lastIndexOf(".") != -1))
                .map(s -> {
                    if (s.lastIndexOf("/") != -1) {
                        s = s.substring(s.lastIndexOf("/") + 1);
                    }
                    return s;
                })
                .map(s -> {
                    if(s.lastIndexOf(".") != -1 ){
                        s = s.substring(0, s.lastIndexOf("."));
                    }
                    return s;
                })
                .sorted(Comparator.naturalOrder())
                .collect(Collectors.toList());

        //[File, Map, interfaces, sources]
        System.out.println(Arrays.toString(collect.toArray()));
    }
}

 

Stream - flatMap

  1. 연산 결과, 또는 각 요소가 배열일 경우 사용

  2. Stream<T[]>, Stream<Stream<T>>  -> Stream<T> 로 변환

  3. 각 요소의 원소들을 다시 나열하는 기능을 가졌다.

public class FlatMap {
    public static void main(String[] args) {
        Stream<String[]> streamArr = Stream.of(
                new String[]{"Java ", "Oracle ", "JavaScript"},
                new String[]{"Vue.js ", "SpringBoot ", "Ubuntu"}
        );
        
        // Stream<Stream<String>> 이 되어버린다.
        Stream<Stream<String>> streamStream = streamArr.map(Arrays::stream);

        Stream<String> stringStream = streamArr.flatMap(Arrays::stream);

        Stream<String> strStream = Stream.of(
                "Java Oracle JavaScript",
                "Vue.js SpringBoot Ubuntu");
        
        //원하는 결과가 도출되지 못함
        Stream<String[]> stream = strStream.map(s -> s.split(" "));
        Stream<Stream<String>> streamStream1 = strStream.map(s -> Stream.of(s.split(" ")));

        // 원하는 결과 도출
        Stream<String> flatStream = strStream.flatMap(s -> Stream.of(s.split(" ")));

    }
}

 

Stream - 최종연산

  • forEach : 내부 반복으로 Consumer 의 accept 를 실행

  • match : Predicate 에 조건에 맞는지를 검사, all, any, none, findFirst, findAny

  • count, sum, average, max, min : sum(), average()Primitive Stream 에 포함

  • reduce : BinaryOperator<T> , 원소간의 연산 결과를 다음 원소와 계산

  • collect : Collectors 사용하여 원소를 수집하는 방법을 기술


 

reduce ( T identity , BinaryOperator<T> )

  1. identity 는 [초기 값]을 지정시켜 유연하게 연산

  2. identity 를 생략하면 스트림의 2개의 원소로 연산을 시작

  3. 계산결과를 순차적으로 연산 하기 위해 사용

public class Reduce {
    public static void main(String[] args) {

        IntStream intStream = IntStream.of(5,4,2,17,10,20,31,12,4,1);

        int count = intStream.reduce(0, (identity, element) -> identity + 1); // 10

        int max = intStream.reduce(Integer.MIN_VALUE, (identity, element) -> identity > element ? identity : element); // 31

        int min = intStream.reduce(Integer.MAX_VALUE, (identity, element) -> identity < element ? identity : element);//1

    }
}

 

collect(Collector)

  1. 연산 중 가장 복잡하고, 가장 유용

  2. 매개변수는 Collector 의 구현체

  3. Collector 요소를 어떻게 수집할 것인지 정의

  4. Collector 를 얼마나 유연하게 사용하는지가 핵심

  5. toList , toSet , toMap, toCollection, toArray

  6. counting, summingInt , averagingInt, maxBy, minBy

  7. reducing

  8. joining

  9. groupBy , partitionBy


 

to..

  • 스트림의 원소들을 원하는 데이터소스로 변경

  • toCollection 으로 원하는 컬렉션 지정

public class CollectToDataSource {

    static class Student {
        private String name;
        private String className;
        private int score;

        public Student(String name, String className, int score) {
            this.name = name;
            this.className = className;
            this.score = score;
        }

        public String getName() {
            return name;
        }

        public String getClassName() {
            return className;
        }

        public int getScore() {
            return score;
        }
    }

    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
          new Student("정수민","D",50),
                new Student("신용권","C",92),
                new Student("남궁성","B",95),
                new Student("최범균","A", 97)
        );

        List<Student> toList = studentStream.collect(toList());
        Set<Student> toSet = studentStream.collect(toSet());
        LinkedList<Student> toLink = studentStream.collect(toCollection(LinkedList::new));
        Map<String, Student> collect = studentStream.collect(toMap(Student::getName, student -> student));
        Student[] students = studentStream.toArray(Student[]::new);

    }
}

 

counting , summingInt , averagingInt , maxBy , minBy

  • Stream 의 최종연산에 포함된 메서드와 기능 동일

  • collect 에서 제공하는 이유는 groupingBy 와 함께 사용하기 위함

public class CollectCount {
    static class Student {
        private String name;
        private String className;
        private int score;

        public Student(String name, String className, int score) {
            this.name = name;
            this.className = className;
            this.score = score;
        }

        public String getName() {
            return name;
        }

        public String getClassName() {
            return className;
        }

        public int getScore() {
            return score;
        }
    }

    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("정수민","D",50),
                new Student("신용권","C",92),
                new Student("남궁성","B",95),
                new Student("최범균","A", 97)
        );

        studentStream.collect(counting()); // 4
        studentStream.collect(summingInt(Student::getScore)); // 334
        studentStream.collect(maxBy(Comparator.comparingInt(Student::getScore))); // 97
        studentStream.collect(averagingInt(Student::getScore)); // 83.5

    }
}

 

groupingBy , partitioningBy

  1. Collector 에 Stream 최종연산 메서드와 같은 기능을 제공하는 이유

  2. groupingBy 는 요소를 기준으로 그룹화 => Function

  3. partitioningBy 는 조건을 기준으로 분할 => Predicate

  4. 결과는 동일하게 <Flag, T> 로 리턴

  5. Collector 를 추가하여 조건 추가 가능


 

partitioningBy

public class CollectionGroupPartition {
    static class Student {
        private String name;
        private String className;
        private int score;
        private boolean graduated;

        public Student(String name, String className, int score, boolean graduated) {
            this.name = name;
            this.className = className;
            this.score = score;
            this.graduated = graduated;
        }

        public String getName() {
            return name;
        }

        public String getClassName() {
            return className;
        }

        public int getScore() {
            return score;
        }

        public boolean isGraduated() {
            return graduated;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", className='" + className + '\'' +
                    ", score=" + score +
                    ", graduated=" + graduated +
                    '}';
        }
    }

    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("정수민","D",50,false),
                new Student("신용권","C",92,true),
                new Student("남궁성","B",95,true),
                new Student("최범균","A", 97,true)
        );

        Map<Boolean, Long> partitioningCount = studentStream.collect(partitioningBy(Student::isGraduated, counting()));
        partitioningCount.get(true); // 3
        partitioningCount.get(false); // 1

        Map<Boolean, Map<Boolean, List<Student>>> collect = studentStream
                .collect(partitioningBy(Student::isGraduated, partitioningBy(student -> student.getScore() > 95)));
        //Student{name='최범균', className='A', score=97, graduated=true}
        collect.get(true).get(true).forEach(System.out::println);
    }
}

 

groupingBy

public class CollectionGroup {
    static class Student {
        private String name;
        private String className;
        private int score;
        private boolean graduated;

        public Student(String name, String className, int score, boolean graduated) {
            this.name = name;
            this.className = className;
            this.score = score;
            this.graduated = graduated;
        }

        public String getName() {
            return name;
        }

        public String getClassName() {
            return className;
        }

        public int getScore() {
            return score;
        }

        public boolean isGraduated() {
            return graduated;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", className='" + className + '\'' +
                    ", score=" + score +
                    ", graduated=" + graduated +
                    '}';
        }
    }

    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("정수민","D",50,false),
                new Student("신용권","C",92,true),
                new Student("남궁성","B",95,true),
                new Student("최범균","A", 97,true)
        );
        Map<String, Map<String, Map<Boolean, List<Student>>>> collect =
                studentStream
                        .collect(
                                groupingBy(Student::getClassName,
                                        groupingBy(Student::getName,
                                                partitioningBy(Student::isGraduated))));

        //Student{name='정수민', className='D', score=50, graduated=false}
        collect.get("D").get("정수민").get(false)
                .forEach(System.out::println);

        Map<String, Map<Boolean, Map<Boolean, List<Student>>>> collect2 = studentStream
                .collect(
                        groupingBy(Student::getClassName,
                                partitioningBy(s -> s.getScore() > 92,
                                        partitioningBy(Student::isGraduated))));

        //Student{name='남궁성', className='B', score=95, graduated=true}
        collect2.get("B").get(true).get(true).forEach(System.out::println);
    }
}