chapter5 - 스트림 활용

  • 주요 키워드
    • 필터링, 슬라이싱, 매핑
    • 검색, 매칭 리듀싱
    • 특정 범위의 숫자와 같은 숫자 스트림 사용하기
    • 다중 소스로부터 스트림 만들기
    • 무한스트림

5.1 필터링과 슬라이싱

5.1.1 프레디케이트로 필터링

List<Dish> vegetarinMenu = menu.stream()
                               .filter(Dish::isVegetarian)
                               .collect(toList());

5.1.2 고유 요소 필터링

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
       .filter(i -> i % 2 == 0)
       .distinct() // hashCode, equals로 결정 하는 고유 요소 필터링. 중복을 제거 한다.
       .forEach(System.out::println);

5.1.3 스트림 축소

List<Dish> dishesLimit3 = menu.stream()
                              .filter(d -> d.getCalories() > 300)
                              .limit(3) // 최상위 3개의 요소를 반환한다.
                              .collect(toList());

dishesLimit3.forEach(System.out::println);

5.1.4 요소 건너뛰기

List<Dish> dishes = menu.stream()
                        .filter(d->d.getCalories() > 300)
                        .skip(2) //개의 요서를 건너 뛰어 나머지 요소를 반환한다.
                        .collect(toList());

5.2 매핑

5.2.1 스트림의 각 요소에 함수 적용하기

// map
List<String> words = Arrays.asList("Hello", "World");
List<Integer> wordLengths = words.stream()
                                 .map(String::length)
                                 .collect(toList());
System.out.println(wordLengths);

5.2.2 스트림 평면화

List<String> words = Arrays.asList("Hello", "World");
words.stream()
     .flatMap((String line) -> Arrays.stream(line.split("")))
     // string을 받아서 split하면 Stream<String[]>이 되는데 이것을 faultMap()으로 평면화하여 각각의 Stream<String>으로 변환 한다. 
     .distinct()
     .forEach(System.out::println);

5.3 검색과 매칭

  • allMatch, anyMatch, noneMatch, findFirst, findAny

5.3.1 프레디케이트가 적어도 한 요소와 일치하는지 확인

if(menu.stream().anyMatch(Dish::isVegetarian)){ //메뉴중 채소가 존재 하는가?
  System.out.println("OK");
}

5.3.2 프레디케이트가 모든 요소와 일치하는지 확인

if( menu.stream().allMatch(d->d.getCalories() > 1000 ){ //메뉴가 모두 1000칼로리 이하인가?
    System.out.println("OK");
}
if( menu.stream().noneMatch(d->d.getCalories() > 1000 ){ //메뉴가 모두 1000칼로리 초과 하는가?
    System.out.println("OK");
}

5.3.3 요소검색

Optional<Dish> dish = menu.stream()
                          .filter(Dish::isVegetarian)
                          .findAny();
  • Optional 이란?
메소드 설명
isPresent() 값을 포함하면 true, 포함하지 않으면 false
ifPresent(Consumer<T> block ) 값이 있으면 블록을 실행한다. Consumer의 시그니처는 T -> void이다.
T get () 값이 존재하면 반환하고 값이 없으면 NoSuchElementException을 일으킨다.
T orElse( T other ) 값이 있으면 값을 반환하고, 값이 없으면 기본값(other)을 반환한다.

5.3.4 첫번째 요소 찾기

List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree = 
                                              someNumbers.stream()
                                                         .map(x -> x * x)
                                                         .filter( x -> x % 3 == 0 )
                                                         .findFirst(); //9 = 첫번째 값을 가져 온다.

5.4 리듀싱

: 폴드

5.4.1 요소의 합

  • 초기값이 있는경우
    List<Integer> number = Arrays.asList(4,5,3,9); 
    int sum = number.stream().reduce(0, (a,b) -> a + b); // ( 초기값, ( 누적값(최초에는 초기값, 최후에는 리턴값), 스트림값)
    int sum2 = number.stream().reduce(0, Integer::sum); // ( 초기값, 메소드 레퍼런스 )
    
  • 초기값이 없는 경우 : 스트림이 아무 요소가 없는경우 아무 값도 반환할수 없어 Optional을 반환한다.
    List<Integer> number = Arrays.asList(4,5,3,9); 
    Optional<Integer> sum = number.stream().reduce((a,b) -> a + b);
    

5.4.2 최댓값과 최솟값

List<Integer> number = Arrays.asList(4,5,3,9); 
Optional<Integer> max = number.stream().reduce(Integer:max); //최대값  
Optional<Integer> min = number.stream().reduce(Integer:min); //최소값
Optional<Integer> max = number.stream().reduce((x,y) -> x>y ? x : y ); //람다식 최대값

5.4.3 카운터

int count = menu.stream()
                .map( d -> 1)
                .reduce( 0, (a,b)->a+b );
long count = menu.stream().count();
연산 형식 반환 형식 인터페이스 형식 함수 디스크립터
filter 중간 연산 Stream<T> Predicate<T> T->boolean
distinct 중간 연산(상태있는 언바운드) Stream<T>
skip 중간 연산(상태있는 바운드) Stream<T> Long
limit 중간 연산(상태 있는 바운드) Stream<T> Long T->R
map 중간 연산 Stream<R> Function<T,R> T -> Stream<R>
flatMap 중간 연산 Stream<R> Function<T,Stream<R>>
sorted 중간 연산(상태 있는 언바운드) Stream<T> Comparator<T> (T,T)->int
anyMatch 최종연산 boolean Predicate<T> T->boolean
noneMatch 최종연산 boolean Predicate<T> T->boolean
allMatch 최종연산 boolean Predicate<T> T->boolean
findAny 최종연산 Optional<T>
findFirst 최종연산 Optional<T>
forEach 최종연산 void Consumer<T> T->void
Collect 최종연산 R Collector<T,A,R>
Reduce 최종연산(상태 있는 바운드) Optional<T> BinaryOperator<T> (T,T)->T
count 최종연산 long  
  • 바운드 : 내부 상태의 크기가 제한 되어 있음.

    5.6 숫자형 스트림

    5.6.1 기본형 특화 스트림

  • 박싱 비용을 없애기 위해 기본형에 특화된 스트림을 제공

    • IntStream, DoubleStream, LongStream
  • 숫자 스트림으로 매핑

    int calories = menu.stream()  // Stream<Dish> 반환
                     .mapToInt(Dish::getCalories) // IntSream 반환 
                     .sum();
    
  • 객체 스트림으로 복원하기

    IntStream intStream = menu.stream().mapToInt(Dish::getCalories); //스트림을 숫자 스트림으로 변환 
    Stream<Integer> stream = intStream.boxed(); // 스트림으로 변환
    
  • OptionalInt : 기본값 : OptionalInt의 max가 없는 없는 경우 값이 0이 될수 있다. 하지만 실제로 max가 0인 경우가 될수도 있다. 그렇다면 이 두개는 보이는 값은 동일하지만 의미가 다르므로 구분 할 필요가 있다.

    OptionalInt maxCalories = menu.stream()
                                .mapToInt(Dish::getCalories)
                                .max(); 
    int max = maxCalroies.orElse(1); // 값이 없을때 기본 최댓값을 명시적으로 설정.
    

5.6.2 숫자 범위

  • range, rangeClosed : IntStream 과 LongStream에서 지원
    • range : 종료값이 불포함
    • rangeClosed : 종료값이 포함
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
                                 .filter(n -> n % 2 == 0);

System.out.println(evenNumbers.count());

5.6.3 숫자 스트림 활용 : 피타고라스 수

Stream<int[]> pythagoreamTriples = 
    IntStream.rangeClosed(1, 100) //a값의 범위를 구한다.
             .boxed() //Stream<Integer>로 변환 
             .flatMap( a ->  
                 IntStream.rangeClosed( a, 100 )//b값의 범위 
                         .filter( b->Math.sqrt( a * a + b * b ) % 1 == 0 ) // 정수 체크
                         .mapToObj( b->
                           new int[]{ a, b, (int)Math.sqrt( a * a + b * b )}
                         )// 반환 형 : IntStream<int[]>
             ); //flatMap으로 결과 값에 대해 평면화 진행, map인경우 Stream<IntStream<int[]>>반환, 이경우 Stream<int[]>를 반환

pythagoreamTriples.limit(5)
                  .forEach(t->System.out.println(t[0] + ", " + t[1] + ", " + t[2] ));

pythagoreamTriples = 
    IntStream.rangeClosed(1, 100) //a값의 범위를 구한다.
             .boxed() //Stream<Integer>로 변환 
             .flatMap( a ->  
                 IntStream.rangeClosed( a, 100 )//b값의 범위 
                         .mapToObj( b->
                           new double[]{ a, b, (int)Math.sqrt( a * a + b * b )}
                         )
                         .filter(t->t[2]%1 == 0) 
             ); 

pythagoreamTriples.limit(5)
                  .forEach(t->System.out.println(t[0] + ", " + t[1] + ", " + t[2] ));

5.7 스트림 만들기

5.7.1 값으로 스트림 만들기

Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty();

5.7.2 배열로 스트림 만들기

int[] numbers = {1,2,3,4,5};
int sum = Arrays.stream(numbers).sum();

5.7.3 파일로 스트림 만들기

  • java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다.

    long uniqueWords = 0;
    try ( 
        Stream<String> lines = Files.lines(Paths.get("data.txt"),
        Charset.defaultCharset())
       ){// 스트림의 자원을 자동으로 해제할 수 있는 AutoClosable이다 
    
        uniqueWords = line.flatMap(line->Arrays.stream(line.split(" "))) // 단어별로 스트림 생성
                          .distinct()
                          .count();
        }catch(IOException e){
          //예외 처리 
        }
    

    5.7.4 함수로 무한 스트림 만들기

  • Stream.iterate, Stream.generate 제공
  • 크기가 고정되지 않고 요청 할때마다 주어진 함수를 이용해서 값을 만들어 낸다.
  • but, 보통 무한한 값을 출력하지 않도록 limit(n)함수를 함께 연결해서 사용한다.

  • iterate : 값을 요청할때마다 값을 생산할수 있으며 끝이 없으므로 무한 스트림을 만든다. (언바운드 스트림)

Steam.iterate(0, n-> n+2)
     .limit(10)
     .forEach(System.out::println);
  • generate : 상태 저장이 없는 무한 스트림 ( 각 값을 연속적으로 구하지 않음 )
Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);

IntSupplier fib = new IntSupplier(){
    private int previous = 0;
    private int current = 1;
    public int getAsInt(){

      int oldPrevious = this.previous;
      int nextValue = this.previous + this.current;
      this.previous = this.current;
      this.current = nextValue;
      return oldPrevious;

    }
};

IntStream.generate(fib).limit(10).forEach(System.out::println);