본문 바로가기

- Java

Stream 기능

반응형

1. 필터링

- 프레이케이트로 필터링

    → 프레디케이트(return boolean)를 인수로 받아 일치하는 모든 요소를 포함하는 스트림을 반환

1
2
3
4
5
6
7
List<Dish> vegetarianMenu =
        Dish.menu.stream()
            .filter(Dish::isVegetarian)
            .collect(toList());
 
System.out.println(vegetarianMenu);
// [french fries, rice, season fruit, pizza]

 

- 고유 요소 필터링

    → 스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드 지원

1
2
3
4
5
6
7
List<Integer> numbers = Arrays.asList(1213324);
    numbers.stream()
        .filter(i -> i % 2 == 0)
        .distinct()
        .forEach(System.out::println);
// 2
// 4

2. 스트림 슬라이싱(JAVA 9)

- 프레디케이트를 이용한 슬라이싱

    1. takeWhite: 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림 슬라이스

1
2
3
4
5
6
7
List<Dish> slicedMenu1
        = Dish.specialMenu.stream()
        .takeWhile(dish -> dish.getCalories() < 320)
        .collect(toList());
 
System.out.println(slicedMenu1);
// [seasonal fruit, prawns]

 

    2. dropWhile: 프레디케이트가 처음 거짓이 되는 지점까지 발견된 요소를 버리고, 남은 모든 요소를 반환

1
2
3
4
5
6
7
List<Dish> slicedMenu2
        = Dish.specialMenu.stream()
        .dropWhile(dish -> dish.getCalories() < 320)
        .collect(toList());
 
System.out.println(slicedMenu2);
// [rice, chicken, french fires]

 

- 스트림 축소

    1. limit: 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환

1
2
3
4
5
6
7
8
List<Dish> dishes
        = Dish.specialMenu.stream()
        .filter(dish -> dish.getCalories() > 300)
        .limit(2)
        .collect(toList());
 
System.out.println(dishes);
// [rice, chicken]

 

- 요소 건너뛰기

    1. skip: 입력 값만큼 요소를 무시

1
2
3
4
5
6
7
8
9
List<Dish> skipDishes
        = Dish.specialMenu.stream()
        .filter(dish -> dish.getCalories() > 100)
        .skip(2)
        .limit(2)
        .collect(toList());
 
System.out.println(skipDishes);
// [chicken, french fires]

3. 매핑

- 스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능 제공

 

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

    → 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑

    → map은 변환에 가까운 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> dishNames =
        Dish.menu.stream()
            .map(Dish::getName)
            .collect(toList());
 
System.out.println(dishNames);
// [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
 
List<Integer> intDishNames =
        Dish.menu.stream()
            .map(Dish::getName)
            .map(String::length// map 메서드 연결
            .collect(toList());
 
System.out.println(intDishNames);
// [4, 4, 7, 12, 4, 12, 5, 6, 6]

 

- 스트림 평면화

    → map으로 고유문자 반환 리스트를 만들 때 distinct로 처리 불가능

1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> words = new ArrayList<>(Arrays.asList("Hello""World"));
 
    List<String[]> world =
        words.stream()
            .map(word -> word.split(""))
            .distinct()
            .collect(toList());
 
for(String[] sArr: world) {
  System.out.println(Arrays.toString(sArr));
}
// [H, e, l, l, o]
// [W, o, r, l, d]

 

    → flatMap: 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑

                     하나의 평면화된 스트림을 반환

1
2
3
4
5
6
7
8
9
List<String> uniqueCharacters =
    words.stream()
        .map(word -> word.split(""))
        .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
        .distinct()
        .collect(toList());
 
System.out.println(uniqueCharacters);
// [H, e, l, o, W, r, d]

4. 검색과 매칭

- 특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리에도 자주 사용

 

- allMatch, anyMatch, noneMatch, findFirst, findAny 등 메서드 제공

 

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

    → anyMatch

1
assertTrue(Dish.menu.stream().anyMatch(Dish::isVegetarian));

 

- 프레디케이트가 모든 요소와 일치하는지 검사

    → allMatch

1
assertTrue(Dish.menu.stream().allMatch(dish -> dish.getCalories() < 1000));

 

- 프레디케이트가 모든 요소와 일치하지 않는지 검사

    → noneMatch

1
assertTrue(Dish.menu.stream().noneMatch(dish -> dish.getCalories() >= 1000));

 

- anyMatch, allMatch, noneMatch, findFirst, findAny 는 스트림 쇼트서킷 기법(자바의 &&, || 연산)

    → 모든 스트림 요소를 처리하지 않고 결과 반환

 

- 요소검색

    1. 임의의 요소 검색

        → findAny

1
2
3
4
5
Dish.menu.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .ifPresent(dish -> System.out.println(dish.getName()));
// french fries

 

    2. 첫번째 요소 검색

        → 일부 스트림에는 논리적인 아이템 순서가 정해져 있을 수도 있음

        → findFirst

1
2
3
4
5
6
7
8
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
    .map(n -> n * n)
    .filter(n -> n % 3 == 0)
    .findFirst();
 
System.out.println(firstSquareDivisibleByThree.orElse(null));
// 9

 

    3. findFirst와 findAny는 언제 사용

        → 병렬성: 병렬 실행에서는 첫번째 요소를 찾기 어렵기 때문

        → 요소의 반환 순서가 상관 없다면 병렬 스트림에서는 제약이 적은 findAny 사용

 

    4. Optional

        → 값의 존재나 부재 여부를 표현하는 컨테이너 클래스

        → ifPresent는 Optional 값을 포함하면 참, 아니면 거짓 반환

5. 리듀싱

- 리듀싱 연산이란 모든 스트림 요소를 처리해서 값으로 도출하는 연산

 

- for-each 루프를 사용해서 값 도출

1
2
3
4
5
6
7
8
List<Integer> numbers = Arrays.asList(4539);
    int sum = 0;
    for(int x : numbers) {
      sum += x;
    }
 
System.out.println(sum);
// 21

    → 리스트에서 숫자가 하나 남을 때까지 reduce 과정 반복

 

- reduce 사용(2개의 인수)

1
2
3
4
int sum2 = numbers.stream().reduce(0, (a,b) -> a+b);
 
System.out.println(sum2);
// 21

    → 초기값 0

    → 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>

- reduce 동작

    → a에 초기값 0이 들어가고, b에는 4이 들어감

    → 누적값 = 4

    → a에 누적값 8이 들어가고, b에는 5가 들어감.

    → 누적값 = 9

    → 반복

    → 메서드 참조로 코드 간결화

1
2
3
4
int sum3 = numbers.stream().reduce(0, Integer::sum);
    
System.out.println(sum3);
// 21

 

- 초깃값이 없는 경우

    → 초기값을 받지 않도록 오버로드된 reduce 존재

    → 그러나 이 reduce는 Optional 객체 반환

    → 스트림에 아무 요소도 없는 경우 reduce 합계가 반환될 수 없어 Optional 사용

1
2
3
4
Optional<Integer> sum4 = numbers.stream().reduce(Integer::sum);
 
System.out.println(sum4.orElse(null));
// 21

 

- 최댓값과 최솟값

    → 최댓값과 최솟값

1
2
3
4
5
6
7
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
 
System.out.println(max.orElse(null));
// 9
System.out.println(min.orElse(null));
// 3

 

- reduce 메서드의 장점과 병렬화

 

- 스트림 연산: 상태 없음과 상태 있음

 

- 중간연산과 최종연산

출처: 내 노션

6. 숫자형 스트림

- reduce를 이용한 스트림 요소의 합

1
2
3
4
int calories = Dish.menu.stream()
        .map(Dish::getCalories)
        .reduce(0, Integer::sum);
// 4300

위 과정에서는 내부적으로 합계를 계산하기 위해서 Integer가 int형으로 언박싱, 따라서 박싱 비용이 소모

 

- 기본형 특화 스트림(primitive stream specialization)

    자바 8에서는 세 가지 기본형 특화 스트림을 제공

        → 스트림 API에서 박싱 비용을 피할 수 있는 특화 스트림

            1. IntStream: int 요소에 특화

            2. DoubleStream: double 요소에 특화

            3. LongStream: long 요소에 특화

    각각의 인터페이스는 숫자 스트림의 합계를 계산하는 sum, 최댓값 요소를 검색하는 max 같이 자주 사용하는 숫자 관련 리듀싱 연산 수행 메서드를 제공

 

- 숫자 스트림으로 매핑

    → 스트림을 특화 스트림으로 변환할 때 mapToInt, mapToDouble, mapToLong 세가지 메서드를 가장 많이 사용

    → map과 같은 기능을 수행하지만, 특화 스트림을 반환

1
2
3
4
int calories2 = Dish.menu.stream()
        .mapToInt(Dish::getCalories)
        .sum();
// 4300

 

- 객체 스트림으로 복원

    → boxed 메서드를 이용해 특화 스트림을 일반 스트림으로 변환

1
2
IntStream intStream = Dish.menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

 

- OptionalInt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OptionalInt calories3 = Dish.menu.stream()
        .mapToInt(Dish::getCalories)
      .max();
System.out.println(calories3.orElse(0));
// 800
 
OptionalInt calories4 = Dish.menu.stream()
    .mapToInt(Dish::getCalories)
    .min();
System.out.println(calories4.orElse(0));
// 120
 
OptionalDouble calories5 = Dish.menu.stream()
    .mapToInt(Dish::getCalories)
    .average();
System.out.println(calories5.orElse(0));
// 477.77777777777777

 

- 숫자 범위

자바 8의 IntStream과 LongSteam에서는 range와 rangeClosed라는 두 가지 정적 메서드를 제공

1
2
3
4
5
6
7
IntStream evenNumbers = IntStream.rangeClosed(1100) .filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
// 50
 
IntStream evenNumbers2 = IntStream.range(1100) .filter(n -> n % 2 == 0);
System.out.println(evenNumbers2.count());
// 49

    → rangeClose(1, 100)은 1과 100을 포함하며 range(1, 100)은 1과 100을 제외

 

- 피타고라스의 수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Stream<int[]> pythagoreanTriples =
        IntStream.rangeClosed(110).boxed()
            .flatMap(a -> IntStream.rangeClosed(a, 10)  // 0.평준화된 스트림으로 변
              .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0// 1.필터링: a, b 두 개의 수만 있을 때 제곱근이 정수인지 확인
              //.boxed()  // 3.b값 생성: map은 Stream의 각 요소를 int 배열로 변환하기 때문
              .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})  // boxed 메서드 대신 IntStream의 mapToObj 메서드 사용
        );
pythagoreanTriples.forEach(t -> System.out.println(t[0+ ", " + t[1+ ", " + t[2]));
 
Stream<int[]> pythagoreanTriples2 = IntStream.rangeClosed(110).boxed()
    .flatMap(a -> IntStream.rangeClosed(a, 10)
        .mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)})
        .filter(t -> t[2] % 1 == 0))  // 제곱근을 두번 계산하지 않도록 필터링 
    .map(array -> Arrays.stream(array).mapToInt(a -> (int) a).toArray());
pythagoreanTriples2.forEach(t -> System.out.println(t[0+ ", " + t[1+ ", " + t[2]));
 
// 3, 4, 5
// 6, 8, 10
cs

7. 스트림 만들기

- 값으로 스트림 만들기

임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해여 생성

1
2
3
4
5
6
7
8
Stream<String> stream = Stream.of("Mordern""Java""In""Action");
stream.map(String::toUpperCase).forEach(System.out::println);
// MORDERN
// JAVA
// IN
// ACTION
 
Stream<String> emptyStream = Stream.empty(); // 스트림 비우기 

 

- null이 될 수 있는 객체로 스트림 만들기

때로는 null이 될 수 있는 객체를 스트림으로 생성(객체가 null = 빈 스트림)

1
2
3
4
5
6
7
8
9
10
String homeVal = System.getProperty("home");
Stream<String> homeValStream =
    homeVal == null ? Stream.empty() : Stream.of(homeVal);
 
Stream<String> homeValStream2 =
    Stream.ofNullable(System.getProperty("home"));  // ofNullable으로 처리
 
Stream<String> values =
    Stream.of("conf""home""user").flatMap(key -> Stream.ofNullable(System.getProperty(key)));
    // flatMap으로 일괄처리

- 배열로 스트림 만들기

Arrays.stream을 이용하여 스트림 생성

1
2
int[] numbers = {23571113};
int sum = Arrays.stream(numbers).sum();  // int[]을 IntStream으로 변환

 

- 파일로 스트림 만들기

파일 처리에 사용하는 NIO API(비블록 I/O)도 스트림 API를 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
String fileName = "/Users/jang/IdeaProjects/halfjang/modern/src/main/resources/data.txt";
long uniqueWord = 0;
try(Stream<String> lines =
    // Stream은 자원을 자동으로 해제할 수 있는 AutoCloseable임으로 try-finally가 필요 없음
    Files.lines(Paths.get(fileName), Charset.defaultCharset())) {
  uniqueWord = lines.flatMap(line -> Arrays.stream(line.split(" ")))  // flatMap으로 고유 단어 수 계산
                    .distinct()
                    .count();
  System.out.println(uniqueWord);
catch (IOException e) {
  System.out.println("ERROR::" + e.getMessage());
}

 

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

스트림 API는 함수에서 스트림을 만들 수 있는 두 정적 메서드 Stream.iterate와 Stream.generate를 제공

두 연산을 이용해서 무한 스트림(언바운드 스트림) 생성 가능

    → 무한 스트림은 limit으로 스트림 크기 제한 필수

        1. iterate

1
2
3
4
5
6
7
8
9
10
11
12
13
Stream.iterate(0, n -> n + 2// 초깃값 0부터 무한으로 +2 수행
        .limit(10)  // 10번만 수행
        .forEach(System.out::println);
//    0
//    2
//    4
//    6
//    8
//    10
//    12
//    14
//    16
//    18

            1.1 피보나치 수열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream.iterate(new int[]{01}, t-> new int[] {t[1], t[0+ t[1]})
        .limit(10)
        .map(t -> t[0])
        .forEach(System.out::println);
//    0
//    1
//    1
//    2
//    3
//    5
//    8
//    13
//    21
//    34

 

            1.2 predicate 지원

1
2
3
4
5
6
7
8
9
10
11
12
13
IntStream.iterate(0, n -> n < 10, n -> n + 4)
        .forEach(System.out::println);
  
// filter는 작업 종료 시점을 알지 못해 사용 불가능
// takeWhile로 사용가능, 근데 IntelliJ가 길다고 줄이라고 함 
 
IntStream.iterate(0, n -> n + 4)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);
 
// 0
// 4
// 8

 

        2. generate

            → iterate와 달리 생산된 각 값을 연속적으로 계산하지 않음

1
2
3
4
5
6
7
8
Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);
// 0.6580158393225022
// 0.4608897593955207
// 0.9981947118300856
// 0.7621314798469475
// 0.2660159676673133

 

        2.1 피보나치 수열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
IntSupplier fib = new IntSupplier() {
  private int previous = 0;
  private int current = 1;
 
  @Override
  public int getAsInt() {
    int oldPre = this.previous;
    int nextVal = this.previous + this.current;
    this.previous = this.current;
    this.current = nextVal;
 
    return oldPre;
  }
};
 
IntStream.generate(fib).limit(10).forEach(System.out::println);
//    0
//    1
//    1
//    2
//    3
//    5
//    8
//    13
//    21
//    34

사용된 익명 클래스와 람다는 비슷한 연산을 수행하지만,

익명 클래스에서 getAsInt는 메서드의 연산은 상태 필드를 정의할 수 있는 점이 다름

→ 부작용이 발생할 수 있음(가변)

→ 람다는 부작용이 없음(불변)

스트림을 병렬로 처리하려면 불변 상태 기법을 고수

 

 

참고: 모던 자바 인 액션
반응형

'- Java' 카테고리의 다른 글

컬렉션 팩토리  (0) 2020.11.08
병렬 데이터 처리  (0) 2020.11.08
Stream 기본  (0) 2020.10.20
lambda  (0) 2020.10.18
final / static final  (0) 2020.07.21