반응형
스트림을 활용하면 외부 반복 을 내부 반복 으로 바꿀 수 있어 좀 더 직관적인 코드를 작성할 수 있습니다. 또한 병렬로 수행할지 여부를 결정할 수 있습니다.
본문에서는 스트림 API를 활용하는 방법을 정리합니다.
필터링과 슬라이싱
- filter()
- distinct()
- limit()
- skip()
// ** 프레디케이트로 필터링 **
// 스트림 인터페이스의 filter 메서드는 Predicate를 인수로 받고,
// 일치하는 모든 요소를 반환한다.
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
// ** 고유 요소 필터링 **
// 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, 4 출력
// ** 스트림 축소 **
// (정렬 여부와 상관없이)지정된 개수 이하의 크기만 반환하는 limit(n) 메서드
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
// ** 요소 건너뛰기 **
// 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
매핑
- map()
- flatMap() : 각 배열을 스트림이 아니라 스트림의 컨텐츠로 매핑한다.
// ** 스트림의 각 요소에 함수 적용하기 **
// 예제 1> 메뉴의 이름으로 변환(매핑)
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
// 예제 2> 단어의 길이로 변환(매핑)
List<String> words = Arrays.asList("Java8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
// 예제 3> 메뉴 이름의 글자수로 변환(매핑)
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
// ** 스트림 평면화 **
// ["Hello", "World"] --> ["H", "e", "l", "o", "W", "r", "d"] 로 변환 예제
List<String> words = Arrays.asList("Hello", "World");
List<String> flattedDistinctedWord = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Copllectors.toList());
검색과 매칭
- allMatch()
- anyMatch()
- noneMatch()
- findFirst()
- findAny() : 현재 스트림에서 임의의 요소를 반환. 병렬 스트림에서는 첫 번째 요소를 찾기 어렵기 때문에 제약이 적은 findAny를 사용한다.
allMatch(), anyMatch(), noneMatch() 메서드는 자바의 &&
, ||
연산 처럼 활용된다.
// ** Predicate가 적어도 한 요소와 일치하는지 확인 **
// 메뉴 중 채식주의자 용 메뉴가 하나라도 존재하면 메세지 출력
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
// ** Predicate가 모든 요소와 일치하는지 검사 **
// 모든 메뉴가 1000 칼로리 미만인지 확인
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000);
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
// ** 임의의 요소 검색 **
// 채식 요리 선택
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(d -> System.out.println(d.getName()));
// ** 첫 번째 요소 검색 **
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(); // 9
리듀싱
- reduce()
reduce 메서드는 병렬 처리가 가능하다는 장점이 있다.
// ** 요소의 합 **
// before
int sum = 0;
for (int x : numbers) {
sum += x;
}
// after
int sum = numbers.steam().reduce(0, (a, b) -> a + b);
int sum = numbers.steam().reduce(0, Integer:sum); // 메서드 레퍼런스 사용
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b)); // 초기값을 받지 않는 방법 (Optional 객체로 반환)
// ** 최대값과 최소값 **
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
연산 | 형식 | 반환 형식 | 사용된 함수형 인터페이스 형식 | 함수 디스크립터 |
---|---|---|---|---|
filter | 중간 연산 | Stream | Predicate | T -> boolean |
distinct | 중간 연산 (상태 있는 언바운드) |
Stream | ||
skip | 중간 연산 (상태 있는 언바운드) |
Stream | Long | |
limit | 중간 연산 (상태 있는 언바운드) |
Stream | Long | |
map | 중간 연산 | Stream | Function<T, R> | T -> R |
flatMap | 중간 연산 | Stream | Function<T, Stream> | T -> Stream |
sorted | 중간 연산 (상태 있는 언바운드) |
Stream | Comparator | (T, T) -> int |
anyMatch | 최종 연산 | boolean | Predicate | T -> boolean |
noneMatch | 최종 연산 | boolean | Predicate | T -> boolean |
allMatch | 최종 연산 | boolean | Predicate | T -> boolean |
findAny | 최종 연산 | Optional | ||
findFirst | 최종 연산 | Optional | ||
forEach | 최종 연산 | void | Consumer | T -> void |
Collect | 최종 연산 | R | Collector<T, A, R> | |
Reduce | 최종 연산 (상태 있는 언바운드) |
Optional | BinaryOperator | (T, T) -> T |
count | 최종 연산 | long |
숫자형 스트림
기본형 특화 스트림
Java8에는 박싱 비용을 줄이고 sum, max와 같이 자주 사용되는 숫자 관련 리듀싱 연산 메서드를 함께 제공하는 세 가지 기본형 특화 스트림을 제공한다.
- IntStream
- DoubleStream
- LongStream
- OptionalInt
- OptionalDouble
- OptionalLong
// ** 숫자 스트림으로 매핑 **
// mapToInt 메서드는 IntStream 인터페이스로 반환하며,
// 기본적으로 sum, min, average 등 다양한 유틸리티 메서드를 제공한다.
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
// **객체 스트림으로 복원 **
// boxed() 메서드를 사용하여 특화 스트림을 일반 스트림으로 변환할 수 있다.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
// Optional 컨테이너 클래스
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
int max = maxCalories.orElse(1); // 값이 없을 때 기본 최대값을 명시적으로 설정
숫자 범위
IntStream과 LongStream은 특정 범위의 숫자를 이용하기 위한 2가지 정적 메서드를 제공한다. :
- range(시작값, 종료값) : 결과에 시작값, 종료값 미포함
- rangeClosed(시작값, 종료값) : 결과에 시작값, 종료값 포함
IntStream evenNumbers = IntStream.rangeClosed(1, 100) // 1~100
.filter(n -> n % 2 == 0); // 짝수 필터링
System.out.println(evenNumbers.count()); // 50개
스트림 만들기
값으로 스트림 만들기
- Stream.of() : 임의의 수로 인수를 받아 스트림으로 만든다.
- Stream.empty() : 스트림을 비운다.
Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
stream.map(String.toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty();
배열로 스트림 만들기
- Arrays.stream()
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); // 합계 41
파일로 스트림 만들기
NIO API(비블록 I/O)도 스트림 API를 활용할 수 있게 되었다.
java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다. (ex> Files.lines
)
// 파일에서 고유한 단어 수를 찾는 프로그램
// Files.lines 메서드는 주어진 파일의 행 스트림을 문자열로 반환한다.
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
함수로 무한 스트림 만들기
스트림 API는 함수에서 스트림을 만들 수 있는 두 개의 정적 메서드를 제공한다.
이전에 봤던 크기가 고정된 컬렉션과는 달리 크기가 고정되지 않은 무한 스트림(Infinite Stream) 을 만들 수 있다. (언바운드 스트림(Unbounded Stream))
따라서 반드시 limit(n)
함수와 함께 사용해야 한다.
- Stream.iterate()
- 초기값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산한다.
- 일반적으로 연속된 일련의 값을 만들 때 사용한다.
- Stream.generate()
- Supplier를 인수로 받아서 새로운 값을 생산한다.
// iterate
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println); // 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 출력
// generate
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
반응형
'프로그래밍 > Java & Spring Framework' 카테고리의 다른 글
스트림 (Stream) (0) | 2019.11.16 |
---|---|
람다 표현식 (Lambda Expression) (0) | 2019.11.16 |
동작 파라미터화 (Behavior Parameterization) (0) | 2019.11.16 |
Private 메소드 단위 테스트하기 (0) | 2019.11.16 |