Java 8 Streams

Streams

스트림은 ‘데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

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

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

    전체 -> 맵핑 -> 필터링 1 -> 필터링 2 -> 결과 만들기 -> 결과물

생성하기

배열 / 컬렉션 / 빈 스트림
  • 배열

      // Arrays.stream() 메소드를 이용해 생성 가능
      String[] arr =new String[]{"a", "b", "c"};
      Stream<String> stream = Arrays.stream(arr);
      Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);// 1~2 요소 [b, c]
    
  • 컬렉션

      // 컬렉션 타입의 추가된 메소드 stream() 으로 생성 가능
      publicinterfaceCollection<E>extendsIterable<E> {
      	default Stream<E>stream() {
      		return StreamSupport.stream(spliterator(),false);
        }
      	// ...
      }
      // 아래 방법으로 생성 가능
      List<String> list = Arrays.asList("a", "b", "c");
      Stream<String> stream = list.stream();
      Stream<String> parallelStream = list.parallelStream(); // 병렬 처리 스트림
    
  • 빈 스트림

      // 요소가 없을 때, 빈 스트림을 생성하여 사용한다.
      public Stream<String> streamOf(List<String> list) {
        return list == null || list.isEmpty() ? Stream.empty() : list.stream();
      }
    
Stream.builder() / Stream.generate() / Stream.iterate()
  • bulder() 사용

      // 빌더를 사용해 스트림에 직접 원하는 값 넣기.
      Stream<String> builderStream = Stream.<String>builder()
      	.add("1")
      	.add("2")
      	.add("3")
        .build(); // [1, 2, 3]
    
  • generate()

      // Supplier<T> 에 해당하는 람다로 값을 넣는다. Supplier<T> 는 인자는 없고 리턴값만 있는 함수형 인터페이스로, 람다에서 리턴하는 값이 들어간다.
      public static<T> Stream<T> generate(Supplier<T> s) { ... }
      // 단 이렇게 생성하게 되면, 길이의 제한이 없기 때문에 길이를 제한해 주어야 한다.
      Stream<String> generatedStream = Stream.generate(() -> "1").limit(5); // [1, 1, 1, 1, 1]
    
  • iterate()

      // iterate() 를 이용하면, 초기 값과 해당 값을 다루는 람다를 이용해 스트림에 들어갈 요소를 만든다.
      // 마찬가지로, 길이의 제한이 없어 제한을 두어야 한다.
      Stream<Integer> iteratedStream = Stream.iterate(301 n -> n + 2).limit(5); // [1, 3, 5, 7, 9]
    

기본 타입형 / String / 파일 스트림

기본 타입형

제네릭을 사용하지 않고, 직접적으로 해당 타입의 스트림을 다룬다.

// range() 메소드는, 종료 지점을 포함하냐 안하느냐 차이를 가지는 범위 지정 메소드
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]

// 제네릭을 사용하지 않기 때문에 오토박싱이 일어나지 않으나, box 메소드를 이용해 박싱 할 수 있다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();

// 자바8에서는 랜덤 클래스의 난수 생성 메소드를 이용해, 세가지 타입의 스트림(int, long, double) 을 만들 수 있다.
DoubleStream doubles = new Random().doubles(5); // double 난수 5개 생성
String

String의 각 문자를 스트림화 할 수 있다.

// char는 본질적으로 숫자이기 때문에 IntStream 으로 변환이 가능하다.
IntStream charsStream = "Stream".chars(); // [83, 116, 114, 101, 97, 109]

// 정규표현식으로 문자를 split 하고, 이를 스트림화 할 수 있다.
Stream<String> stringStream = Pattern.compile(", ").splitAsStream("1, 2, 3"); // [1, 2, 3]
File Stream

파일의 각 라인을 스트림화 할 수 있다.

// 텍스트 파일의 각 라인을 읽어오는 Files의 lines() 메소드를 사용함.
Stream<String> lineStream = 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(arr).parallel();

// 컬렉션과 배열이 아닌 경우, 아래와 같이 parallel()을 이용하여 처리.
IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();

// 다시 시퀀셜 모드로 돌리고 싶은 경우, squential() 메소드를 사용한다.
IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();
스트림 연결하기

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

// 두 개의 스트림을 합쳐 새로운 스트림 생성.
Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("4", "5, "6");
Stream<String> concat = Stream.concat(stream1, stream2);
// [1, 2, 3, 4, 5, 6]

가공하기

filter
  • filter() → 스트림 내 요소를 필터링 함.

      // 스트림 내 요소를 평가하여 걸러내는 작업을 하는 필터링.
      Stream<T> filter(Predicate<? super T> predicate);
        
      // name = [apple, banana, kiwi]
      Stream<String> stream = names.stream()
        .filter(name -> name.contains("a")); // [apple, banana]
    
map
  • map() → 스트림 내 요소를 하나씩 특정 값으로 변환.

      // 스트림 내 요소를 특정 값으로 변환하는 맵핑, 변환하여 새로운 스트림으로 리턴된다.
      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
        
      // String::toUpperCase는 대문자로 변환하는 스트링 내 메소드를 표현하는 람다식
      Stream<String> stream = names.stream()
        .map(String::toUpperCase); // [APPLE, BANANA, KIWI]
        
      // 임의의 person 객체의 getAge() 를 통해, 나이를 가져오는 예제
      Stream<Integer> stream = person.stream()
        .map(Person::getAge); // [10, 20, 30, 40, 50]
    
flatMap
  • flatMap() → 중첩 구조를 제거하고 단일 컬렉션으로 변환.

      // Map 보다 조금 복잡한 flatMap 이 있다. 이 메소드는 mapper를 인자로 받아 새로운 스트림을 생성해 리턴한다.
      // 중첩 구조를 한단계 제거하고, 단일 컬렉션으로 만들어주는 역할을 한다.
      <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
        
      // 예제 1
      List<List<String>> list = Arrays.asList(Arrays.asList("a"), Arrays.asList("b")); // [[a], [b]]
        
      // 다음과 같이 중첩을 제거한 결과를 반환한다.
      List<String> flatList = list.stream()
      	.flatMap(Collection::stream)
        .collect(Collectors.toList()); // [a, b]
        
      // 예제 2
      // 학생 객체에서 각 과목 점수를 추출하여 평균을 구하는 예제
      students.stream()
        .flatMapToInt(student -> 
                      IntStream.of(student.getKor(), 
                                   student.getEng(), 
                                   student.getMath()))
        .average().ifPresent(avg -> 
                             System.out.println(Math.round(avg * 10)/10.0));
    
sorted
  • sorted() → 스트림 내 요소 정렬

      // comparator를 이용하여 정렬 한다.
      Stream<T> sorted();
      Stream<T> sorted(Comparator<? super T> comparator);
        
      // 인자 없이 호출하는 경우 오름차순 정렬.
      IntStream.of(14, 11, 20, 39, 23)
        .sorted()
        .boxed()
        .collect(Collectors.toList()); // [11, 14, 20, 23, 39]
        
      // 인자를 넘겨서 비교
      List<String> lang = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");
        
      // 알파벳 순 정렬
      lang.stream()
        .sorted()
        .collect(Collectors.toList()); // [Go, Groovy, Java, Python, Scala, Swift]
        
      // 역순으로 정렬하는 예제
      lang.stream()
        .sorted(Comparator.reverseOrder())
        .collect(Collectors.toList()); // [Swift, Scala, Python, Java, Groovy, Go]
        
      // Comparator 의 compare 메소드는 두 인자를 비교해서 값을 리턴합니다.
      int compare(T o1, T o2);
        
      // 해당 메소드를 이용해 문자열 길이를 기준으로 정렬하는 예제
      lang.stream()
        .sorted(Comparator.comparingInt(String::length))
        .collect(Collectors.toList());
      // [Go, Java, Scala, Swift, Groovy, Python]
        
      lang.stream()
        .sorted((s1, s2) -> s2.length() - s1.length())
        .collect(Collectors.toList());
      // [Groovy, Python, Scala, Swift, Java, Go]
    
peek
  • peek() → 스트림 내 요소를 반복 확인, 특정 결과를 리턴하지 않음.

      // 스트림 내 요소를 확인 하는 peek(), 결과를 반환하지 않음.
      Stream<T> peek(Consumer<? super T> action);
        
      // 아래 처럼 중간에 값을 확인하는 등의 용도로 사용할 수 있다.
      int sum = IntStream.of(1, 3, 5, 7, 9)
        .peek(System.out::println)
        .sum();
    

결과 만들기

Calculating
  • count()

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

      long sum = LongStream.of(1, 3, 5, 7, 9).sum();
    
  • min()

      OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
    
  • max()

      OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();
    
  • ifPresent() → Optional 처리를 하기 위해 사용

      DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
        .average()
        .ifPresent(System.out::println); // 평균 값 3.3 리턴.
    
Reduction
  • reduce() → 결과를 만들어 내기 위해 사용하는 메소드
    • 파라미터
      • accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
      • identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
      • combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.
        // 1개 (accumulator)
        Optional<T> reduce(BinaryOperator<T> accumulator);
              
        // BinaryOperator<T> 는 같은 타입의 인자 2개를 받아, 같은 타입의 결과를 리턴하는 람다형 함수.
        OptionalInt reduced = 
          IntStream.range(1, 4) // [1, 2, 3]
          .reduce((a, b) -> {
            return Integer.sum(a, b); // 6(1+2+3)
          });
        /*----------------------------------------------------------------------------------------------------*/
        // 2개 (identity)
        T reduce(T identity, BinaryOperator<T> accumulator);
              
        // 메소드 참조를 통해 2개의 인수를 넘김.
        int reducedTwoParams = 
          IntStream.range(1, 4) // [1, 2, 3]
          .reduce(10, Integer::sum); // 초기값 10 + (1+2+3) = 16 리턴.
        /*----------------------------------------------------------------------------------------------------*/
        // 3개 (combiner)
        <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
              
        // 3개를 넘기는 경우, 그러나 마지막 combiner는 실행되지 않는다.
        Integer reducedParams = Stream.of(1, 2, 3)
          .reduce(10, // identity
                  Integer::sum, // accumulator
                  (a, b) -> {
                    System.out.println("combiner was called");
                    return a + b;
                  }); // ?
              
        // 마지막 Combiner는 병렬 처리 시, 각자 다른 쓰레드에서 실행된 결과를 합치는 단계이기 때문에 실행하기 위해서는 병렬 스트림에서만 동작한다.
        Integer reducedParallel = Arrays.asList(1, 2, 3)
          .parallelStream()
          .reduce(10, Integer::sum, // (10+1)+(10+2)+(10+3) -> 11, 12, 13
                  (a, b) -> { // 11 + 12 , 12 + 13
                    System.out.println("combiner was called");
                    return a + b;
                  }); // (11+12) + 13 = 36
        // 병렬 스트림이 무조건 시퀀셜보다 좋은 것은 아닙니다. 오히려 간단한 경우에는 이렇게 부가적인 처리가 필요하기 때문에 오히려 느릴 수도 있습니다.
      
Collecting
  • collect() → collect 메소드는 또 다른 종료 작업입니다. Collector 타입의 인자를 받아서 처리를 하는데요, 자주 사용하는 작업은 Collectors 객체에서 제공하고 있습니다.

      List<Product> productList = Arrays.asList(
      	new Product(23, "potatoes"),
        new Product(14, "orange"),
        new Product(13, "lemon"),
        new Product(23, "bread"),
        new Product(13, "sugar"));
    
    • Collectors.toList() → 스트림에서 작업한 결과를 리스트로 반환.

        // 맵으로 이름을 가져온 후, 이를 리스트로 반환.
        List<String> collectorCollection =
          productList.stream()
            .map(Product::getName)
            .collect(Collectors.toList()); // [potatoes, orange, lemon, bread, sugar]
      
    • Collectors.joining() → 스트림에서 작업한 결과를 하나의 스트링으로 리턴.

      Collectors.joining 은 세 개의 인자를 받을 수 있습니다. 이를 이용하면 간단하게 스트링을 조합할 수 있습니다.

      • delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
      • prefix : 결과 맨 앞에 붙는 문자
      • suffix : 결과 맨 뒤에 붙는 문자
        // 맵으로 이름을 가져온 후, join()
        String listToString = 
         productList.stream()
          .map(Product::getName)
          .collect(Collectors.joining()); // potatoesorangelemonbreadsugar
              
        // 앞, 뒤에 문자열 삽입
        String listToString = 
         productList.stream()
          .map(Product::getName)
          .collect(Collectors.joining(", ", "<", ">")); // <potatoes, orange, lemon, bread, sugar>
      
    • Collectors.averageingInt() → 숫자 값(Integer value)의 평균(arithmetic mean)을 냅니다.

        Double averageAmount = 
         productList.stream()
          .collect(Collectors.averagingInt(Product::getAmount)); // 17.2
      
    • Collectors.summingInt() → 숫자값의 합(sum)을 냅니다.

        Integer summingAmount = 
         productList.stream()
          .collect(Collectors.summingInt(Product::getAmount)); // 86
              
        // IntStream 으로 바꿔주는 mapToInt 메소드를 사용해서 좀 더 간단하게 표현할 수 있습니다.
        Integer summingAmount = 
          productList.stream()
          .mapToInt(Product::getAmount)
          .sum(); // 86
      
    • Collectors.summarizingInt()

      만약 합계와 평균 모두 필요하다면 스트림을 두 번 생성해야 할까요? 이런 정보를 한번에 얻을 수 있는 방법으로는 summarizingInt 메소드가 있습니다.

        IntSummaryStatistics statistics =  productList.stream()
        	.collect(Collectors.summarizingInt(Product::getAmount));
      

      이렇게 받아온 IntSummaryStatistics 객체에는 다음과 같은 정보가 담겨 있습니다.

        IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23}
      
      • 개수 getCount()
      • 합계 getSum()
      • 평균 getAverage()
      • 최소 getMin()
      • 최대 getMax()

      이를 이용하면 collect 전에 이런 통계 작업을 위한 map 을 호출할 필요가 없게 됩니다. 위에서 살펴본 averaging, summing, summarizing 메소드는 각 기본 타입(int, long, double)별로 제공됩니다.

    • Collectors.groupingBy() → 특정 요소로 그룹지을 수 있음.

        // 수량 가져와서 그룹핑 하기.
        Map<Integer, List<Product>> collectorMapOfLists =
         productList.stream()
          .collect(Collectors.groupingBy(Product::getAmount));
              
        // 결과.
        {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'}]}
      
    • Collectors.partitioningBy() ****위의 groupingBy 함수형 인터페이스 Function 을 이용해서 특정 값을 기준으로 스트림 내 요소들을 묶었다면, partitioningBy은 함수형 인터페이스 Predicate 를 받습니다. Predicate 는 인자를 받아서 boolean 값을 리턴합니다.

        // 함수형 인터페이스로 조건에 맞는 요소들을 그룹핑하여 반환
        Map<Boolean, List<Product>> mapPartitioned = 
          productList.stream()
          .collect(Collectors.partitioningBy(el -> el.getAmount() > 15));
              
        // 결과.
        {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'}]}
      
    • Collectors.collectingAndThen()

      특정 타입으로 결과를 collect 한 이후에 추가 작업이 필요한 경우에 사용할 수 있습니다. 이 메소드의 시그니쳐는 다음과 같습니다. finisher 가 추가된 모양인데, 이 피니셔는 collect 를 한 후에 실행할 작업을 의미합니다.

        **public** **static**<T,A,R,RR> Collector<T,A,RR> **collectingAndThen**(
          Collector<T,A,R> downstream,  Function<R,RR> finisher) { ... }
      

      다음 예제는 Collectors.toSet 을 이용해서 결과를 Set 으로 collect 한 후 수정불가한 Set 으로 변환하는 작업을 추가로 실행하는 코드입니다.

        Set<Product> unmodifiableSet =  productList.stream()  
        	.collect(Collectors.collectingAndThen(Collectors.toSet(),
                                                Collections::unmodifiableSet));
      
    • Collector.of()

      이 외에 필요한 로직이 있다면 직접 collector 를 만들 수도 있습니다. 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) { ... }
              
        // 스트림의 각 요소에 대해서 LinkedList 를 만들고 요소를 추가하게 됩니다. 마지막으로 combiner 를 이용해 결과를 조합하는데, 생성된 리스트들을 하나의 리스트로 합치는 컬렉터 메소드 예시
        Collector<Product, ?, LinkedList<Product>> toLinkedList = 
          Collector.of(LinkedList::new, 
                       LinkedList::add, 
                       (first, second) -> {
                         first.addAll(second);
                         return first;
                       });
              
        // 위에서 만든 커스텀 컬렉터를 넘겨서 처리할 수 있다.
        LinkedList<Product> linkedListOfPersons = 
          productList.stream()
          .collect(toLinkedList);
      
Matching

매칭은 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴합니다. 다음과 같은 세 가지 메소드가 있습니다.

  • 하나라도 조건을 만족하는 요소가 있는지(anyMatch)
  • 모두 조건을 만족하는지(allMatch)
  • 모두 조건을 만족하지 않는지(noneMatch)
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

// 예시
List<String> names = Arrays.asList("Eric", "Elena", "Java");

boolean anyMatch = names.stream()
  .anyMatch(name -> name.contains("a")); // true
boolean allMatch = names.stream()
  .allMatch(name -> name.length() > 3); // true
boolean noneMatch = names.stream()
  .noneMatch(name -> name.endsWith("s")); // true
Iterating
  • foreach

    foreach 는 요소를 돌면서 실행되는 최종 작업입니다. 보통 System.out.println 메소드를 넘겨서 결과를 출력할 때 사용하곤 합니다.

    앞서 살펴본 peek과는 중간 작업과 최종 작업의 차이가 있습니다.

      names.stream().forEach(System.out::println);
    

동작 순서

  • 모든 요소가 첫 번째 중간 연산을 수행하고 남은 결과가 다음 연산으로 넘어가는 것이 아니라, 한 요소가 모든 파이프라인을 거쳐서 결과를 만들어내고, 다음 요소로 넘어가는 순서.

성능 향상

  • 요소의 범위를 줄이는 작업을 먼저 실행하는 것이 불필요한 연산을 막을 수 있어 성능을 향상시킬 수 있습니다. 이런 메소드로는 skipfilterdistinct 등이 있습니다.
    • skip(long n) → 스트림의 첫 번째 요소를 버린 후 이 스트림의 나머지 요소로 구성된 스트림을 반환합니다.
    • filter(Predicate<? super T> predicate) → 지정된 조건자와 일치하는 이 스트림의 요소로 구성된 스트림을 반환합니다.
    • distinct() → Object.equals(Object) 로 판단하여 중복을 제거한 스트림을 반환합니다.

스트림 재사용

  • 종료 작업을 하지 않는 한 하나의 인스턴스로서 계속해서 사용이 가능합니다. 하지만 종료 작업을 하는 순간 스트림이 닫히기 때문에 재사용은 할 수 없습니다.
  • 스트림은 저장된 데이터를 꺼내서 처리하는 용도이지 데이터를 저장 하려는 목적으로 설계되지 않았기 때문입니다.

지연 처리 Lazy Invocation

  • 스트림에서 최종 결과는 최종 작업이 이루어질 때 계산됩니다.
  • 최종작업을 실행하지 않으면 실제로 스트림의 연산이 실행되지 않는다.

Null-safe 스트림 생성하기

  • NullPointerException 은 개발 시 흔히 발생하는 예외입니다. Optional 을 이용해서 null에 안전한(Null-safe) 스트림을 생성해보겠습니다.

      // 널-세이프 한 스트림을 만드는 메소드
      public <T> Stream<T> collectionToStream(Collection<T> collection) {
          return Optional
            .ofNullable(collection)
            .map(Collection::stream)
            .orElseGet(Stream::empty);
        }
        
      // 테스트
      List<Integer> intList = Arrays.asList(1, 2, 3);
      List<String> strList = Arrays.asList("a", "b", "c");
        
      Stream<Integer> intStream = 
        collectionToStream(intList); // [1, 2, 3]
      Stream<String> strStream = 
        collectionToStream(strList); // [a, b, c]
        
      // 널 테스트 -> 예외가 발생하지 않고 빈 스트림으로 종료됨.
      List<String> nullList = null;
        
      nullList.stream()
        .filter(str -> str.contains("a"))
        .map(String::length)
        .forEach(System.out::println); // NPE!
    

줄여쓰기 Simplified

  • 스트림 사용 시 다음과 같은 경우에 같은 내용을 좀 더 간결하게 줄여쓸 수 있습니다. IntelliJ 를 사용하면 다음과 같은 경우에 줄여쓸 것을 제안해줍니다. 그 중에서 많이 사용되는 것만 추렸습니다.

      collection.stream().forEach() 
       collection.forEach()
        
      collection.stream().toArray() 
       collection.toArray()
    
      Arrays.asList().stream() 
       Arrays.stream() or Stream.of()
    
      Collections.emptyList().stream() 
       Stream.empty()
    
      stream.filter().findFirst().isPresent() 
       stream.anyMatch()
    
      stream.collect(counting()) 
       stream.count()
    
      stream.collect(maxBy()) 
       stream.max()
    
      stream.collect(mapping()) 
       stream.map().collect()
    
      stream.collect(reducing()) 
       stream.reduce()
    
      stream.collect(summingInt()) 
       stream.mapToInt().sum()
    
      stream.map(x -> {...; return x;}) 
       stream.peek(x -> ...)
    
      !stream.anyMatch() 
       stream.noneMatch()
    
      !stream.anyMatch(x -> !(...)) 
       stream.allMatch()
    
      stream.map().anyMatch(Boolean::booleanValue) 
       stream.anyMatch()
    
      IntStream.range(expr1, expr2).mapToObj(x -> array[x]) 
       Arrays.stream(array, expr1, expr2)
    
      Collection.nCopies(count, ...) 
       Stream.generate().limit(count)
    
      stream.sorted(comparator).findFirst() 
       Stream.min(comparator)
    
  • 특정 케이스에서 다르게 동작하는 경우.

    // 1. 생략 가능
    collection.stream().forEach() 
      collection.forEach()
    
    // 2. 동기화에서는 차이 발생
    // not synchronized
    Collections.synchronizedList(...).stream().forEach()
        
    // synchronized
    Collections.synchronizedList(...).forEach()
    
    // 바로 호출 가능한 경우
    stream.collect(maxBy()) 
      stream.max()
    
    // 스트림이 비어있는 경우에는 또 다르게 동작함.
    collect(Collectors.maxBy()) // Optional
    Stream.max() // NPE 발생 가능
    
  • 참고

    • https://futurecreator.github.io/2018/08/26/java-8-streams/
    • https://futurecreator.github.io/2018/08/26/java-8-streams-advanced/

© 2024. Chiptune93 All rights reserved.