자바-19(Stream-1)

dragonappear·2021년 5월 15일
0

Java

목록 보기
19/22

학습할 것 (필수)

  • 스트림이란?
  • 생성하기
  • 가공하기
  • 결과만들기

스트림이란?

  • 자바8에서 추가한 스트림은 람다를 활용할 수 있는 기술 중 하나이다.
  • 자바8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었다.
  • 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메서드를 나눌 경우 루프를 여러번 도는 경우가 발생한다.
  • 스트림은 데이터의 흐름 이다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다.
  • 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있다.
  • 즉, 배열과 컬렉션을 함수형으로 다룰 수 있다.
  • 또 하나의 장점은 간단하게 병렬처리 가 가능해진다.
  • 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬처리라고 하는데, 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있다.

스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.

  1. 생성하기: 스트림 인스턴스 생성
  2. 가공하기: 필터링 및 맵핑 등 원하는 결과를 만들어가는 중간 작업
  3. 결과만들기: 최종적으로 결과를 만들어내는 작업

생성하기

배열 스트림

  • 스트림을 이용하기 위해서는 먼저 생성을 해야한다.
  • 스트림은 배열 또는 컬렉션 인스턴스를 이용해서 생성할 수 있다.
  • 배열은 아래와 같이 Arrays.stream 메서드를 이용한다.
        String[] strings = new String[]{"a","b","c"};
        Stream<String> stream = Arrays.stream(strings);
        Stream<String> stream1 = Arrays.stream(strings,1,3); // 1~2 요소 [b,c]

컬렉션 스트림

  • 컬렉션타입(Collection,List,Set)의 경우 인터페이스에 추가된 디폴트 메소드 stream을 이용해서 스트림을 만들 수 있다.
        List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
        Stream<String> stringStream = list.stream();
        Stream<String> stringStream1 = list.parallelStream(); // 병렬처리 스트림    

비어있는 스트림

  • 비어있는 스트림(empty streams)도 생성할 수 있다.
  • 언제 빈 스트림이 필요할까? 빈 스트림은 요소가 없을 때 null 대신 사용할 수 있다.
   public Stream<String> streamOf(List<String> list){
        return list==null || list.isEmpty()
                ? Stream.empty() : list.stream();
    }

Stream.builder()

  • 빌더를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있따.
  • 마지막에 build 메서드로 스트림을 리턴한다.
        Stream<String> builderStream =
                Stream.<String>builder()
                .add("Eric").add("Elena").add("Java")
                .build();

Stream.generate()

  • generate 메서드를 사용하면 Supplier<T> 에 해당하는 람다로 값을 넣을 수 있다.
  • Supplier<T> 는 인자는 없고 리턴값만 있는 함수형 인터페이스이며 람다에서 리턴하는 값이 들어간다.
 public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }
  • 이 때 생성되는 스트림은 크기가 정해져있지 않고 무한하기 때문에 특정사이즈로 최대 크기를 제한할수있다.
Stream<String> generatedStream =
                Stream.generate(()->"gen").limit(5); // ["gen","gen","gen","gen","gen"]
  • 5개의 "gen"이 들어간 스트림이 생성된다.
  • 다음예제에서는 30이 초기값이고 값이 2씩 증가하는 값들이 들어가게 된다.
  • 즉 요소가 다음 요소의 인풋으로 들어간다.
  • 이 방법도 스트림의 사이즈가 무한하기 때문에 특정사이즈로 제한해야 한다.
Stream<Integer> iteratorStream = 
                Stream.iterate(30, i -> i+2).limit(5); // [30,32,34,36,38]

Stream.iterate()

  • iterate 메서드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어가 요소를 만든다.

기본 타입형 스트림

  • 제네릭을 사용하면 리스트나 배열을 이용해서 기본 타입 스트림을 생성할 수 있지만, 제네릭을 사용하지 않고 직접적으로 해당 타입의 스트림을 다룰 수도 있다.
  • rangerangedClosed 는 범위의 차이이다.
  • 두번째 인자인 종료지점이 포함되냐 안되느냐의 차이이다.
IntStream intStream = IntStream.range(1,5); // [1,2,3,4]
LongStream longStream = LongStream.rangeClosed(1,5); // [1,2,3,4,5]
  • 제네릭을 사용하지 않기 때문에 불필요한 오토박싱(auto-boxing)이 발생하지 않는다.
  • 필요한 경우 boxed 메서드를 이용해서 박싱(boxing)할 수 있다.
Stream<Integer> boxStream = IntStream.range(1,5).boxed(); // [1,2,3,4]
  • 자바8의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream,LongStream,DoubleStream)을 만들어낼 수 있다.
  • 쉽게 난수 스트림을 생성해서 여러가지 후속 작업을 취할 수 있어 유용하다.
DoubleStream doubleStream = new Random().doubles(3); // 난수 3개 생성 

문자열 스트림

  • 스트링을 이용해서 스트림을 생성할 수도 있다. 다음은 각 스트링의 각 문자(char)을 IntStream 으로 변환한 예제이다.
IntStream stringStream2 = "Stream".chars(); // [83, 116, 114, 101, 97, 109]

다음은 정규표현식(RegEx)를 이용해서 문자열을 자리고, 각 요소들로 스트림을 만든 예제이다.


        Stream<String> stringStream3 =
                Pattern.compile(" ,").splitAsStream("Eric,Elena,Java"); // [Eric, Elena, Java]

파일 스트림

  • 자바의 NIO의 Files 클래스의 lines 메서드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어준다.
Stream<String> stringStream4 =
                    Files.lines(Paths.get("file.txt"),Charset.forName("UTF-8"));

병렬 스트림

  • 스트림 생성 시 사용하는 stream 대신 parallelStream 메서드를 사용해서 병렬 스트림을 쉽게 생할 수 있다.
  • 내부적으로 쓰레드를 처리하기 위해 자바7부터 도입된 Fork/Join framework를 사용한다.
//  병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();
        
// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel()    
  • 다음 코드는 각 작업을 쓰레드를 이용해 병렬처리한다.
boolean isMany = parallelStream
                .map(product -> product.getAmount()*10)
                .anyMatch(amount-> amount>200);
  • 다음은 배열을 이용해서 병렬 스트림을 생성하는 경우이다.
Arrays.stream(list).parallel();
  • 컬렉션과 배열이 아닌 경우는 다음과 같이 parallel 메서드를 이용해서 처리한다.
IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();
  • 다시 sequential 모드로 돌리고 싶다면 다음처럼 sequential 메서드를 사용한다.
  • 뒤에서 한번 더 다루겠지만 반드시 병렬 스트림이 좋은 것은 아니다.
IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();

스트림 연결하기

Stream.concat 메서드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어낼 수 있다.

Stream<String> s1 = Stream.of("JAVA","Scala","Groovy");
Stream<String> s2 = Stream.of("Python","go","Swift");
Stream<String> s3 = Stream.concat(s1,s2);

가공하기

  • 전체 요소 중에서 다음과 같은 API를 이용해서 내가 원하는 것만 뽑아낼 수 있다.
  • 이러한 가공 단계를 중간 작업이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서 작성할수있다.

Filtering

  • 필터는 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업이다.
  • 인자로 받는 Predicate는 boolean을 리턴하는 함수형 인터페이스로 평가식이 들어가게된다.
Stream<T> filter(Predicate<? super T> predicate);
       List<String> names = Arrays.asList("Eric" , "Java" , "Elena");
        Stream<String> stream = names.stream()
                .filter(name-> name.contains("a")); // Java, Elena
  • 스트림은 각 요소에 대해서 평가식을 실행하게 되고, 'a'가 들어간 이름만 들어간 스트림이 리턴된다.

Mapping

  • Map은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다.
  • 이 때 값을 변환하기 위한 람다식을 인자로 받는다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
  • 스트림에 들어가 있는 값이 input()이 되어서 특정로직을 거친 후 output이 되어 리턴되는 새로운 스트림에 담기게 된다. 이러한 작업을 맵핑이라고 한다.
        Stream<String> stream1 = names.stream()
                .map(String::toUpperCase);
        // ERIC, JAVA , ELENA
  • 위 예제는 스트링의 toUpperCase 메서드를 실행해서 대문자로 변환한 값들이 담긴 스트림을 리턴한다.

  • 다음처럼 요소 내 들어있는 Product 개체의 수량을 꺼내올수도있다. 각 상품을 상품의 수량으로 맵핑한다.

Stream<Integer> stream = 
  productList.stream()
  .map(Product::getAmount);
// [23, 14, 13, 23, 13]
  • map 이외에도 조금 더 복잡한 flatMap 메소드도 있다.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
  • 인자로 mapper을 받고 있는데, 리턴타입이 Stream이다.

  • 즉, 새로운 스트림을 생성해서 리턴하는 람다를 넘겨야 한다.

  • flatMap은 중첩구조를 한 단계 제거하고 단일 컬렉션으로 만들어주는 역할을 한다.

  • 이러한 작업을 플래트닝 이라고 한다.

  • 다음과 같은 중첩된 리스트가 있다.

        List<List<String>> list =
                Arrays.asList(Arrays.asList("a"),Arrays.asList("b"));
        
        // [ [a] , [b] ]
  • 이를 flatMap 을 사용해서 중첩 구조를 제거한 후 작업할 수 있습니다.
List<String> flatList=  
                list.stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        // [a] , [b]
  • 객체에도 적용할수있다.
List<Student> students = new ArrayList<Student>();
        students.stream()
                .flatMapToInt(student ->
                        IntStream.of(student.getEnglishGrade() , student.getKoreanGrade(), student.getMathGrade()))
                .average().ifPresent(avg -> System.out.println(Math.round((avg*10)/10.0)));      
  • 위 예제에서는 학생 객체를 가진 스트림에서 학새으이 국영수 점수를 뽑아 새로운 스트림을 만들어 평균을 구하는 코드이다.
  • map 메서드 자체만으로는 한번에 할수 없는 기능이다.

Sorting:

  • 정렬의 방법은 다른 정렬과 마찬가지로 Comparator를 이용한다.
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

인자없이 그냥 호출할 경우 오름차순으로 정렬한다.

IntStream.of(14,11,20,29,23)
                .sorted()
                .boxed()
                .collect(Collectors.toList());
            //  [11, 14 , 20 , 23 ,29]
        
  • 아래코드는 스트링 리스트에서 알파벳순으로 정렬한 코드와 Comparator를 넘겨서 역순으로 정렬한 코드이다.

        List<String> list1 = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

        list1.stream()
                .sorted()
                .collect(Collectors.toList());
        // [Go, Groovy, Java, Python, Scala, Swift]

        list1.stream()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());
        // [Swift, Scala, Python, Java, Groovy, Go]
  • Comparator의 compare 메서드는 두 인자를 비교해서 값을 리턴한다.
int compare(T o1, T o2)
  • 기본적으로 Comparator 사용법은 동일하다.
  • 이를 이용해서 문자열 길이를 기준으로 정렬해보겠다.
        list1.stream()
                .sorted(Comparator.comparingInt(String::length))
                .collect(Collectors.toList());
        // [Go, Java, Scala, Swift, Groovy, Python]

        list1.stream()
                .sorted((s1,s2)-> s2.length()-s1.length())
                .collect(Collectors.toList());
        // [Groovy, Python, Scala, Swift, Java, Go]

Iterating

  • 스트림 내 요소들 각각을 대상으로 특정 연산을 수행하는 메소드로는 peek가 있다
  • peek은 그냥 확인 해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 Consumer를 인자로 받는다.
Stream<T> peek(Consumer<? super T> action);
  • 따라서 스트림 내 요소들 각각에 특정 작업을 수행할 뿐 결과에 영향을 미치지 않는다.
  • 다음처럼 작업을 처리하는 중간에 결과를 확인해볼때 사용할수있다.
       int sum = IntStream.of(1,3,5,7,9)
                .peek(i -> System.out.println(i))
                .sum();
        System.out.println("sum = " + sum);

output

1
3
5
7
9
sum = 25

결과만들기

  • 가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계이다.
  • 따라서 스트림을 끝내는 최종작업이다.

Calculating

  • 스트림 API는 다양한 종료 작업을 제공한다.
  • 최소,최대,합,평균 등 기본형 타입으로 결과를 만들어낼수있다.
       int sum = IntStream.of(1,3,5,7,9)
                .sum();

        long count = IntStream.of(1,3,5,7,9)
                .count();

        System.out.println("sum = " + sum);
        System.out.println("long = " + count);

output

sum = 25
long = 5
  • 만약 스트림이 비어있는경우 count 와 sum 은 0을 출력하게 된다.
  • 하지만 평균,최소,최대의 경우에는 표현할수없기 때문에 Optional을 이용해 리턴한다.
        OptionalInt min = IntStream.of(1,3,5,7,9).min();
        OptionalInt max = IntStream.of(1,3,5,7,9). max();

        System.out.println(min + " " + max);

output

sum = 25
long = 5
OptionalInt[1] OptionalInt[9]
  • 스트림에서 바로 ifPresent 메서드를 이용해서 Optional을 처리할수있다.
        DoubleStream.of(1.1,2.2,3.3,4.4,5.5)
                .average()
                .ifPresent(System.out::println);

output

3.3
  • 이 외에도 사용자가 원하는대로 결과를 만들어내기 위해 reducecollect 메서드를 제공한다.

Reduction

  • 스트림은 reduce 라는 메서드를 이용해서 결과를 만들어낸다.

  • reduce 메서드는 총 세가지의 파라미터를 받을수있다.

  1. accumulator: 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직
  2. identity: 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  3. combiner: 병렬 스트림에서 나눠 계산한 결과를 하나로 합치는 동작을 하는 로직.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);

// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);

// 3개 (combiner)
<U> U reduce(U identity,
  BiFunction<U, ? super T, U> accumulator,
  BinaryOperator<U> combiner);
  • 먼저 인자가 1개만 있는 경우일때, 여기서 BinaryOperator<T>는 같은 타입의 인자 두 개를 받아 같은 타입의 결과를 반환하는 함수형 인터페이스이다.
  • 다음예제에서는 두 값을 더하는 람다를 넘겨주고 있다.

        OptionalInt reduced =
                IntStream.of(1,4)
                .reduce((a,b)->{return Integer.sum(a,b); });

        System.out.println(reduced);

output

OptionalInt[5]
  • 이번엔 두 개의 인자를 받는 경우이다.
  • 여기서 10은 초기값이고, 스트림 내 값을 더해서 결과는 16(10+1+2+3)이 된다.
  • 여기서 메서드 참조를 이용해서 넘길수있다.
        int reducedStream =
                IntStream.range(1,4)
                .reduce(10,Integer::sum);

        System.out.println(reducedStream);

output

16
  • 마지막으로 세 개의 인자를 받는 경우이다.
  • Combiner가 하는 역할을 설명만 보았을때는 이해가 잘 안갈 수 있다.
        Integer integer  = Stream.of(1,2,3)
                .reduce(10,
                        Integer::sum,
                        (a,b) ->{
                            System.out.println("combiner was called");
                            return a+b;
                        });

        System.out.println(integer);

output

16
  • 위 결과를 보면 마지막 인자 combiner는 실행되지 않았다는것을 알수있다.
  • Combiner은 병렬 처리시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계이다. 따라서 병렬스트림에서만 동작한다.
        Integer integer = Arrays.asList(1,2,3).parallelStream()
                .reduce(10,
                        Integer::sum,
                        (a,b)->{
                            System.out.println("combiner call");
                            return a+b;
                        });
        System.out.println(integer);

output

36
  • 결과는 36이 나온다
  • 먼저 accumulator를 총 세번 동작한다.
  • 초기값 10에 각 스트림 값을 더한 세 개의 값(10+1,10+2,10+3)을 계산한다.
  • Combiner은 identity와 accumulator를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할이다.
  • 그래서 11+12 = 23 , 23+13 = 36 이렇게 두 번 호출된다.
  • 병렬 스트림이 무조건 시퀀셜보다 좋은것은 아니다.
  • 오히려 간단한 경우에도 이렇게 부가적인 처리가 필요하기에 오히려 느릴 수 있다.

Collecting

  • collect 메서드는 또 다른 종료 작업이다.
  • Collector 타입의 인자를 받아서 처리하는데, 자주 사용 하는 작업은 Collections 객체에서 제공한다.
        List<Product> productList = 
                Arrays.asList(new Product(1,"감자")
                        , new Product(2,"오렌지")
                        , new Product(3,"레몬")
                        , new Product(4,"빵")
                        , new Product(5,"설탕")
                );

Collectors.toList()

  • 스트림에서 작업한 결과를 담은 리스트로 반환한다.
  • map으로 각 요소의 이름을 가져온 후 Collectors.toList()를 이용해서 리스트로 결과를 가져온다.
        List<String> collectorCollection =
                productList.stream()
                .map(Product::getName)
                .collect(Collectors.toList());

        System.out.println(collectorCollection);

output

[감자, 오렌지, 레몬,, 설탕]

Collectors.joining()

  • 스트림에서 작업한 결과를 하나의 스트링으로 이어 붙일 수 있다.
        String listToString =
                productList.stream()
                .map(Product::getName)
                .collect(Collectors.joining());

        System.out.println(listToString);

output

감자오렌지레몬빵설탕
  • Collectors.joining()은 세 개의 인자를 받을 수 있다.
  • 이를 이용하면 간단하게 스트링을 조합할 수 있다.
  1. delimeter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
  2. prefix : 결과 맨 앞에 붙는 문자
  3. suffix : 결과 맨 뒤에 붙는 문자
        String listToString =
                productList.stream()
                .map(Product::getName)
                .collect(Collectors.joining(" , " , "< " , ">"));

        System.out.println(listToString);

output

< 감자 , 오렌지 , 레몬 ,, 설탕>

Collectors.averageingInt()

  • 숫자 값(Integer value)의 평균을 낸다.
        Double average =
                productList.stream()
                .collect(Collectors.averagingInt(Product::getIndex));

        System.out.println(average);

output

8.6

Collectors.summingInt()

  • 숫자값의 합을 낸다.
        Integer sum =
                productList.stream()
                .collect(Collectors.summingInt(Product::getIndex));

        System.out.println(sum);

output

43
  • IntStream 으로 바꿔주는 mapToInt 메서드를 사용해서 좀 더 간단하게 표현할 수도 있다.
        Integer sum =
                productList.stream()
                .mapToInt(Product::getIndex)
                .sum();
        
        System.out.println(sum);
43

Collectors.summarizingInt()

  • 만약 합계와 평균 모두 필요하다면 스트림을 두 번 생성할 필요없이 Collectors.summarizingInt()
    메서드를 사용하면 된다.
        IntSummaryStatistics summaryStatistics =
                productList.stream()
                .collect(Collectors.summarizingInt(Product::getIndex));

        System.out.println(summaryStatistics);

output

IntSummaryStatistics{count=5, sum=43, min=2, average=8.600000, max=23}
  • 이렇게 받아온 IntSummaryStatics 객체에는 위와 같은 정보가 담겨져 있다.
  • 개수 getCount(),합계 getSum(),평균 getAverage(),최소 getMin(),최대 getMax() 를 이용하여 필요한 값을 뽑아올수있다.
  • 위 메서드를 이용하면 collect 전에 이런 통계작업을 위한 map을 호출할 필요가 없게 된다.

Collectors.groupingBy()

  • 특정 조건으로 요소들을 그룹지을수있다.
  • 여기서 받는 인자는 함수형인터페이스 Function 이다.
        List<Product> productList =
                Arrays.asList(new Product(23,"potato")
                        , new Product(14,"orange")
                        , new Product(13,"lemon")
                        , new Product(23,"bread")
                        , new Product(13,"sugar")
                );

        Map<Integer,List<Product>> collectorMapOfLists =
                productList.stream()
                .collect(Collectors.groupingBy(Product::getIndex));

output

{23=[Product{amount=23, name='potatoes'}, 
     Product{amount=23, name='bread'}], 
 13=[Product{amount=13, name='lemon'}, 
     Product{amount=13, name='sugar'}], 
 14=[Product{amount=14, name='orange'}]}
  • 결과는 Map 타입으로 나오고, 같은 수량이면 리스트로 묶어서 보여준다.

Collectors.partitioningBy()

  • 위의 groupingBy는 함수형 인터페이스 Function을 이용해서 특정 값을 기준으로 스트림으로 스트림 내 요소들을 묵었다면, partitioningBy() 는 함수형 인터페이스 Predicate를 받는다.
  • Predicate는 인자를 받아서 boolean값을 리턴한다
       List<Product> productList =
                Arrays.asList(new Product(23,"potato")
                        , new Product(14,"orange")
                        , new Product(13,"lemon")
                        , new Product(23,"bread")
                        , new Product(13,"sugar")
                );

        Map<Boolean,List<Product>> collectorMapOfLists =
                productList.stream()
                .collect(Collectors.partitioningBy(i -> i.getIndex()>15));

output

{false=[Product{amount=14, name='orange'}, 
        Product{amount=13, name='lemon'}, 
        Product{amount=13, name='sugar'}], 
 true=[Product{amount=23, name='potatoes'}, 
       Product{amount=23, name='bread'}]}
  • 평가하는 함수를 통해서 스트림 내 요소들을 true와 fale 두가지로 나눈다.

Collectors.collectingAndThen()

  • 특정타입으로 결과를 collect 한 이후에 추가작업이 필요한 경우에 사용할 수 있다.
  • 이 메서드의 시그니쳐는 다음과 같다.
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
  Collector<T,A,R> downstream,
  Function<R,RR> finisher) { ... }
  • finisher가 추가되었는데, 이것은 collect 한후에 실행할 작업을 의미한다.
        Set<Product> productSet =
                productList.stream()
                .collect(Collectors.collectingAndThen(Collectors.toSet(),Collections::unmodifiableSet));
  • Collectors.toSet을 이용해서 결과를 Set으로 collect 한후 수정불가한 Set으로 변환하는 작업을 추가로 실행하는 코드이다.

Collector.of()

  • 여러가지 상황에서 사용할 수 있는 메서드를 살펴보았다.
  • 이 외에 필요한 로직이 있다면 직접 collect를 만들수도있다.
  • accumulator 와 combiner는 reduce 에서 살펴본 내용과 동일하다.
public static<T, R> Collector<T, R, R> of(
  Supplier<R> supplier, // new collector 생성
  BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
  BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수.
  Characteristics... characteristics) { ... }
  • 다음 코드에서는 collector 를 하나 생성한다.
  • 컬렉터를 생성하는 supplier 에 LinkedList의 생성자를 넘겨준다.
  • 그리고 accumulator 에는 리스트에 추가하는 add 메서드를 넘겨주고 있다.
  • 따라서 이 컬렉터는 스트림의 각 요소에 대해서 LinkedList를 만들고 요소를 추가하게 된다.
  • 마지막을 combiner를 이용해 결과를 조합하는데, 생성된 리스트들을 하나의 리스트로 합치고 있다.
Collector<Product, ?, LinkedList<Product>> toLinkedList = 
  Collector.of(LinkedList::new, 
               LinkedList::add, 
               (first, second) -> {
                 first.addAll(second);
                 return first;
               });
  • 따라서 다음과 같이 collect 메서드에 우리가 만든 커스텀 컬렉터를 넘겨줄수 있고, 결과가 담긴 LinkedList가 반환된다.
 LinkedList<Product> linkedList =
                productList
                .stream().collect(toLinkedList);

        System.out.println(linkedList);

Matching

  • 매칭은 조건식 람다 Predicate를 받아서 해당조건을 만족하는 요소가 있는지 체크한 결과를 리턴한다.
  • 다음과 같은 세가지 메소드가 있다.
  1. 하나라도 조건을 만족하는 요소가 있는지(anyMatch)
  2. 모두 조건을 만족하는지(allMatch)
  3. 모두 조건을 만족하지 않는지(noneMatch)
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
        List<String> list = Arrays.asList("Eric","Elena","Java");

        boolean any = list.stream().anyMatch( a-> a.contains("a"));
        System.out.println(any);
        boolean all = list.stream().allMatch( a->a.length()>3);
        System.out.println(any);
        boolean none = list.stream().noneMatch( a-> a.endsWith("z"));
        System.out.println(any);

output

true
true
true

참고

[스트림참고]https://futurecreator.github.io/2018/08/26/java-8-streams/

0개의 댓글