[모던 자바 인 액션] chap05. 스트림 활용

2023. 3. 5. 11:08·Book/모던 자바 인 액션
목차
  1. 1. 필터링
  2. 2. 스트림 슬라이싱
  3. 3. 매핑
  4. 4. 검색과 매칭
  5. 5. 리듀싱
  6. 6. 숫자형 스트림
  7. 8. 스트림 만들기
내용
필터링, 슬라이싱, 매칭
검색, 매칭, 리듀싱
특정 범위의 숫자와 같은 숫자 스트림 사용하기
다중 소스로부터 스트림 만들기
무한 스트림
// 외부 반복
List<Dish> vegetrianDishes = new ArrayList<>();
	for(Dish d : menu) {
		if(d.isVegetarian())
			vegetrianDishes.add(d);
	}

// 내부 반복
List<Dish> vegetrianDishes = menu.stream()
		.filter(Dish::isVegetarian)
		.collect(Collectors.toList());

 

1. 필터링

1. 프레디케이트로 필터링

filter 메서드는 프레디케이트를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.

List<Dish> vegetrianDishes = menu.stream()
		.filter(Dish::isVegetarian)	// 채식 요리인지 확인하는 메서드 참조
		.collect(Collectors.toList());
2. 고유 요소 필터링

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

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

 

2. 스트림 슬라이싱

1. 프레디케이트를 이용한 슬라이싱
자바9는 스트림의 요소를 효과적으로 선택할 수 있도록 takeWhile, dropWhile 두 가지 새로운 메서드를 지원한다.
  • TAKEWHILE활용 - 코드를 보면 스페셜 메뉴는 이미 칼로리 별로 정렬이 되있는 상태다. 리스트가 이미 정렬되있다는 사실을 이용해 320 칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단할 수 있다. 작은 리스트에서는 이와 같은 동작이 별거 아닌 것처럼 보일 수 있지만 아주 많은 요소를 포함하는 큰 스트림에서는 상당한 차이가 될 수 있다. takeWhile을 이용하면 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이스할 수 있다.
List<Dish> specialMenu = Arrays.asList(
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER));

        List<Dish> filterMenu = specialMenu.stream()
                .filter(dish -> dish.getCalories() < 320)
                .collect(Collectors.toList());

        List<Dish> sliceMenu = specialMenu.stream()
                .takeWhile(dish -> dish.getCalories() < 320)
                .collect(Collectors.toList());
        // season fruit, prawns 반환
  • DROPWHILE 활용 - 나머지 요소를 선택하려면 dropWhile을 이용해 이 작업을 완료할 수 있다. dropWhile은 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다. 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 요소를 모두 반환한다. dropWhile은 무한한 남은 요소를 가진 무한 스트림에서도 동작한다.
List<Dish> sliceMenu2 = specialMenu.stream()
                .dropWhile(dish -> dish.getCalories() < 320)
                .collect(Collectors.toList());
           // rice, chicken, frenchi fries 반환
2. 스트림 축소

스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원한다. 스트림이 정렬되어 있으면 최대 요소 n개를 반환한다. 정렬되지 않은 스트림에도 limit을 사용할 수 있다. 정렬되어 있지 않다면 limit의 결과도 정렬되지 않은 상태로 반환한다.

List<Dish> filterMenu = specialMenu.stream()
                .filter(dish -> dish.getCalories() > 320)
                .limit(3)	// 3개 반환
                .collect(Collectors.toList());
                // rice, chicken, french fries
3. 요소 건너뛰기

스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.

List<Dish> filterMenu = specialMenu.stream()
                .filter(dish -> dish.getCalories() < 320)
                .skip(2)
                .collect(Collectors.toList());
                // 빈 스트림

 

3. 매핑

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

 

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

스트림은 함수를 인수로 받는 map 메서드를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.

List<String> dishNames = menu.stream()
                .map(Dish::getName)	// getName은 String을 반환하기때문에, 
 				//	map 메서드의 출력 스트림은 Strema<String> 형식을 갖는다.
                .collect(Collectors.toList());
        
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
                .map(String::length) // String.length는 Stream<Integer> 형식 반환
                .collect(Collectors.toList());
                
List<Integer> dishNameLengths = menu.stream()
                .map(Dish::getName)
                .map(String::length)
                .collect(Collectors.toList());
// map 메서드 연결로 Integer을 뽑아낸다.

 

2. 스트림 평면화

["Hello", "Wolrd"] -> ["H", "e", "l", "o", "W", "r", "d"] 로 만들어보자.

List<String> words = Arrays.asList("Hello", "World");

words.stream()
	.map(word -> word.split(""))
    .distinct()
    .collect(toList());
  	// Stream<String[]>
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);

words.stream()
	.map(word -> word.split(""))
    .map(Arrays::stream)		// List<Stream<String>>
    .distinct()
    .collect(toList());
List<String> uniqueCharacters = words.stream()
	.map(word -> word.split(""))
    .flatMap(Arrays::stream)		// Stream<String>
    .distinct()
    .collect(toList());

flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에

모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다.

 

4. 검색과 매칭

1. 프레디케이트가 적어도 한 요소와 일치하는지 확인
if(menu.stream().anyMatch(Dish::isVegetarian)) {
	System.out.pritnln("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch 적어도 한 요소와 일치하는지 검사한다.

 

2. 프레디케이트가 모든 요소와 일치하는지 검사
boolean isHealthy = menu.stream()
	.allMatch(dish -> dish.getCalories() < 1000);

allMatch 모든 요소가 일치하는지 검사한다.

boolean isHealthy = menu.stream()
	.noneMatch(d -> d.getCalories() >= 1000);

noneMatch 일치하는 요소가 없는지 확인한다.

 

3. 요소 검색
Optional<Dish> dish = menu.stream()
	.filter(Dish::isVegetarian)			// Stream<Dish>
    	.findAny();

findAny 현재 스트림에서 임의의 요소를 반환한다.

 

4. 첫 번째 요소 찾기
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = someNumbers.stream()
	.map(n -> n * n)
    	.filter(n -> n % 3 == 0)
    	.findFirst();		// 9
findAny와 findFirst는 병렬성 때문에 사용하는 것이다. 병렬 실행에서는 첫 번째 요소를 찾기 어렵다. 따라서 요소의 반환 순서가 상관없다면 제약이 적은 findAny를 사용하는게 좋다!

 

5. 리듀싱

모든 스트림 요소를 처리해서 값으로 도출하는 연산을 리듀싱 연산이라고 한다. 함수형 프로그래밍에서는 이 과정이 마치 종이(스트림)를 작은 조각이 될 때까지 반복해서 접는 것과 비슷하다는 의미로 폴드라고 부른다.

 

1. 요소의 합
int sum = 0;
for(int x : numbers)
	sum += x;
// 이 코드에서는 초깃값 0, 리스트의 요소를 조합하는 연산 + 총 2개를 사용했다.    

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce는 두개의 인수를 갖는다.

  • 초깃값 0
  • 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
int product = numbers.stream().reduce(1, (a, b) -> a * b);
// 곱셈도 적용 가능하다.

int sum = numbers.stream().reduce(0, Integer::sum);
// Integer가 지원하는 sum을 이용하면 더 편하게 구현이 가능해진다.

 

2. 최댓값과 최솟값
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 최댓값 출력
Optional<Integer> min = numbers.stream().reduce(Integer::min);
// 최솟값 출력

 

6. 숫자형 스트림

int calories = menu.stream()
	.map(Dish::getCalories)
    	.reduce(0, Integer::sum);
        // 이 코드에는 박싱 비용이 숨어있다.

 

1. 기본형 특화 스트림
int calories = menu.stream()
	.mapToInt(Dish::getCalories)		// IntStream
    	.sum();
        
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);	// Stream<Integer> -> IntStream
Stream<Integer> stream = intStream.boxed(); 	// IntStream -> Stream<Integer>

OptionalInt maxCalories = menu.stream()
	.mapToInt(Dish::getCalories)
    	.max();
int max = maxCalories.orElse(1);

이 외에도 DoubleStream, LongStream을 제공한다.

 

2. 숫자 범위
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
	.filter(n -> n % 2 == 0);		// 2, 4, 6 ... 100
System.out.println(evenNumbers.count());	// 50

rangeClosed(int a, int b)는 a, b의 범위를 포함하지만, range(int a, int b)는 a,b의 범위를 포함하지 않는다.

 

3. 활용 : 피타고라스 수
Stream<int[]> pythoagoreanTriples = IntStream.rangeClosed(1, 100).boxed()
	.flatMap(a -> IntStream.rangeClosed(a, 100)
    		.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
            	.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)})
    );
    
pythoagoreanTriples.limit(5)
	.forEach(item -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
    
// 3, 4, 5
// 5, 12, 13
// 6, 8, 10
// 7, 24, 25
// 8, 15, 17

// 위 코드 개선? -> 정수를 무조건 두번 곱해 검증하는 방식이다.
Stream<double[]> pythoagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed()
	.flatMap(a -> IntStream.rangeClosed(a, 100)
            .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}
            .filter(t -> t[2] % 1 == 0)		// 반드시 정수여야 한다.
    );

 

8. 스트림 만들기

1. 값으로 스트림 만들기
Stream<String> stream = Steram.of("Modern ", "Java ", "In ", "Action");
// 문자열로 된 Stream 생성
stream.map(String::toUpperCase)
	.forEach(System.out::println);
    // MODERN JAVA IN ACTION
   
Stream<String> emptyStream = Stream.empty();		// 빈 String Stream 생성

 

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

** 자바 9에서 새롭게 추가된 메서드다.

String homeValue = System.getProperty("home");
Stream<String> homeValueStream = homeValue == null ? Stream.empty() : Stream.of(homeValue);
// 자바 9 이전의 프로퍼티 스트림 객체 생성 코드

Stream<String> values = Stream.of("config", "home", "user")
	.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
// 자바 9 이후의 스트림 객체 생성 코드    

Stream<String> values = Stream.of("config", "home", "user")
	.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
// 프로퍼티의 생성 코드

 

3. 배열로 스트림 만들기
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers)	// IntStream을 반환한다.
	.sum();
// sum = 141

Arrays.stream 은 배열을 정수로 받는다.

 

4. 파일로 스트림 만들기
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"),
	Charset.defaultCharset())) {		// AutoCloseable을 구현해야만 함.
    long uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
        .distinct()
        .count();
} catch(IOException e) {
	...
}

 

5. 함수로 무한 스트림 만들기

iterate 메서드

Stream.iterate(0, n -> n+2)
	.limit(10)
    .forEach(System.out::println);
// 끝이 없이 연산하기때문에 무한 스트림이다. limit을 걸어 제한을 둬야한다.

Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
	.limit(10)
    .forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
// (0, 1), (1, 1), (1, 2) ...
// 피보나치 수열을 만들수도 있다.

Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
	.limit(10)
    .map(t -> t[0])
    .forEach(System.out::println);
// 0, 1, 1, 2, 3 ...    
// map을 만들어 앞 글자로 일반적인 피보나치 수열을 만들수도 있다.

Stream.iterate(0, n -> n + 4)
        .takeWhile(n -> n < 100)
        .forEach(System.out::println);
// takeWhile을 이용해 조건까지 연산을 시킬수도 있다.

 

generate 메서드

Stream.generate(Math::random)
	.limit(5)
    .forEach(System.out::println);

** generate 메서드는 가변상태 객체다. 병렬 환경에 안좋기때문에, 굳이 병렬 환경에 적용해야한다면 iterate를 쓰는것이 좋다. 

'Book > 모던 자바 인 액션' 카테고리의 다른 글

[모던 자바 인 액션] chap09. 리팩터링, 테스팅, 디버깅  (0) 2023.03.21
[모던 자바 인 액션] chap06. 스트림으로 데이터 수집  (0) 2023.03.07
[모던 자바 인 액션] chap04. 스트림이란?  (0) 2023.03.01
[모던 자바 인 액션] chap03. 람다 표현식(2)  (0) 2023.02.24
[모던 자바 인 액션] chap03. 람다 표현식(1)  (0) 2023.02.21
  1. 1. 필터링
  2. 2. 스트림 슬라이싱
  3. 3. 매핑
  4. 4. 검색과 매칭
  5. 5. 리듀싱
  6. 6. 숫자형 스트림
  7. 8. 스트림 만들기
'Book/모던 자바 인 액션' 카테고리의 다른 글
  • [모던 자바 인 액션] chap09. 리팩터링, 테스팅, 디버깅
  • [모던 자바 인 액션] chap06. 스트림으로 데이터 수집
  • [모던 자바 인 액션] chap04. 스트림이란?
  • [모던 자바 인 액션] chap03. 람다 표현식(2)
jun96
jun96
프로그래밍 공부
  • jun96
    jun의 공부노트
    jun96
  • 전체
    오늘
    어제
    • 분류 전체보기 (66)
      • Spring (6)
        • 개념 (3)
        • 에러 (1)
      • Java (1)
      • Book (20)
        • 모던 자바 인 액션 (12)
        • 디자인 패턴의 아름다움 (7)
      • Algorithm (1)
      • 코딩테스트 (35)
      • 일상 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 이력서
  • 공지사항

  • 인기 글

  • 태그

    백준
    모던자바인액션
    프로그래머스
    aws에 배포하기
    아직 미완성
    wikidocs
    Java
    최프
    junit5
    python설치
    전자정부프레임워크
    DeepDive
    aws배포
    datetime
    디자인패턴의아름다움
    Algorithm
    도커컨테이너빌드업
    자바
    스프링
    알고리즘
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
jun96
[모던 자바 인 액션] chap05. 스트림 활용
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.