외부 반복을 내부 반복으로 바꿔 편리하게 데이터 관련 작업을 처리한다.
스트림 API 내부적으로 다양한 최적화가 이루어질 수 있다.
- 필터링, 슬라이싱, 매칭
- 검색, 매칭, 리듀싱
- 특정 범위의 숫자와 같은 스트림 사용하기
- 다중 소스로부터 스트림 만들기
- 무한 스트림
-
프리디케이트로 필터링
: filter 메서드는 프리디케이트(불린을 반환하는 함수)를 인수로 받아서 프리디케이트와일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetarianMenu = menu.stream() .filter(Dish::isVegetarian) .collect(toList());
-
고유 요소 필터링
: distinct 메서드는 고유 요소로 이루어진(중복을 제거한) 스트림을 반환한다.
고유 여부는 hashCode, equals로 결정된다.List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() .forEach(System.out::println);
-
스트림 축소
: limit(n) 메서드는 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환한다.
정렬되어 있으면 정렬된 n개의 요소를, 그렇지 않은 스트림은 정렬되지 않은 n개의 요소를 반환한다.List<Dish> dishs = menu.stream() .filter(d -> d.getCalories() > 300) .limit(3) .collect(toList);
-
요소 건너뛰기
: skip(n) 메서드는 처음 n개 요소를 제왜ㅣ한 스트림을 반환한다.
n개 이하의 요소를 포함하는 스트림에 호출하면 빈 스트림이 반환된다.List<Dish> dishs = menu.stream() .filter(d -> d.getCalories() > 300) .skip(2) .collect(toList);
-
스트림의 각 요소에 함수 적용하기
: map 메서드는 함수를 인수로 받아 각 요소에 함수를 적용한 결과를 새로운 요소로 매핑(변환)하여 반환한다.
List<String> dishsName = menu.stream() .map(Dish::getName) .map(String::length) .collect(toList);
-
스트림 평면화
: flatMap 메서드는 스트림의 각 값을 다른 스트림(스트림의 콘텐츠)으로 만든 다음 하나의 평면화된 스트림으로 연결하여 반환한다.
List<String> uniqueCharacters = word.stream() .map(w -> w.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList);
쇼트서킷 평가
전체 스트림을 처리하지 않았더라도 결과를 반환한다.
하나라도 결과가 나오면 나머지 표현식의 결과가 상관없는 상황을 **쇼트서킷**이라 부른다.
allMatch, noneMatch, findFirst, findAny 등의 연산이 해당된다.
무한한 요소를 가진 스트림을 유한한 크기로 줄일 때 유용하다.
-
프레디케이트가 적어도 한 요소와 일치하는지 확인
: anyMatch 메서드는 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인하여 boolean을 반환한다.
if(menu.stream().anyMatch(Dish::isVegetarian)) { System.out.println("The menu is (somewhat) vegetarian friendly!!"); }
-
프레디케이트가 모든 요소와 일치하는지 검사()
: allMatch 메서드는 스트림의 모든 요소가 프레디케이트와 일치하는지 검사한다.
boolean isHealthy = menu.stream() .allMatch(d -> d.getCalories() < 1000);
: noneMatch 메서드는 주어진 프레디케이트와 일치하는 요소가 없는지 확인한다.
boolean isHealthy = menu.stream() .noneMatch(d -> d.getCalories() >= 1000);
-
요소 검색
: findAny 메서드는 현재 스트림에서 임의의 요소를 반환한다.
Optional<Dish> dish = menu.stream() .filter(Dish::isVegetarian) .findAny();
-
첫 번째 요소 찾기
: findFirst 메서드는 첫 번째 요소를 반환한다.
리스트 또는 정렬된 연속 데이터와 같은 논리적인 아이템 순서가 정해져 있는 경우 사용한다.
병렬 실행의 경우 첫 번째 요소를 찾기 어렵기 때문에 findFirst, 아닌 경우 제약이 적은 findAny를 사용한다.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();
Optional이란?
Optional<T> 클래스(java.util.Optional)는 값의 존재나 부재 여부를 표현하는 컨테이너 클래스다.
findAny의 경우 아무 요소도 반환하지 않을 수 있으므로 null 에러를 발생할 수 있다.
(null확인 관련 버그를 피하는 방법은 10장에서 설명한다.)
Optional은 값이 존재하는지 확인하고 없을 때 어떻게 처리할 것인지 강제하는 기능을 제공한다.
- isPresent(): Optional이 값을 포함하면 true, 포함하지 않으면 false를 반환한다.
- isPresent(Consumer<T> block): 값이 있으면 주어진 블록을 실행한다.
- T get(): 값이 존재하면 값을 반환하고, 없으면 NoSuchElementException을 일으킨다.
- T orElse(T other): 값이 있으면 값을 반환하고, 없으면 기본값을 반환한다.
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(d -> System.out.println(d.getName());리듀싱 연산
- 모든 스트림 요소를 처리해서 값으로 도출하는 연산
- 함수형 프로그래밍 언어 용어로는 이 과정을 마치 종이(우리의 스트림)를 작은 조각이 될때까지 반복해서 접는 것과 비슷하다는 의미로 폴드 라고 부른다.
- 스트림이 하나의 값으로 줄어들때까지 반복해서 조합한다.
List<Integer> numbers = Arrays.asList(3,4,5,1,2);
//두개의 인수 (초깃값, 두요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>)
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println(sum);-
초기값 없음
: 초깃값을 받지 않도록 오버로드된 reduce도 있다. Optional 객체를 반환한다.
Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a + b));
-
최댓값과 최솟값
Optional<Integer> sum = numbers.stream().reduce(Integer::max); Optional<Integer> sum = numbers.stream().reduce(Integer::min);
reduce 메서드의 장점과 병렬화
- 7장 포크/조인 프레임워크 참조
스트림 연산: 상태 없음과 상태 있음
- map, filter 등은 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림으로 보낸다.
따라서 이들은 보통 상태가 없는, 즉 내부 상태를 갖지 않는 연산이다.
- reduce,sum,max 같은 연산은 결과를 누적할 내부 상태가 필요하다.
예제의 내부 상태는 작은 값이다. 우리 예제에서는 int 또는 double을 내부 상태로 사용했다.
스트림에서 처리하는 요소 수와 관계없이 내부 상태의 크기는 한정 되어 있다.
- sorted나 distinct 같은 연산은 filte나 map처럼 스트림을 입력으로 받아 다른 스트림을
출력하는 것처럼 보일수 있다.
스트림의 요소를 정렬하거나 중복을 제거하려면 과거의 이력을 알고 있어야 한다.
예를 들어 어떤 요소를 출려 스트림으로 추가하려면 모든 요소가 버퍼에 추가되어 있어야 한다.
- 연습
public class PuttingIntoPractice{
public static void main(String ...args){
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
// Query 1: Find all transactions from year 2011 and sort them by value (small to high).
List<Transaction> tr2011 = transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(tr2011);
// Query 2: What are all the unique cities where the traders work?
List<String> cities =
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(toList());
System.out.println(cities);
// Query 3: Find all traders from Cambridge and sort them by name.
List<Trader> traders =
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(comparing(Trader::getName))
.collect(toList());
System.out.println(traders);
// Query 4: Return a string of all traders’ names sorted alphabetically.
String traderStr =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.reduce("", (n1, n2) -> n1 + n2);
System.out.println(traderStr);
// Query 5: Are there any trader based in Milan?
boolean milanBased =
transactions.stream()
.anyMatch(transaction -> transaction.getTrader()
.getCity()
.equals("Milan")
);
System.out.println(milanBased);
// Query 6: Update all transactions so that the traders from Milan are set to Cambridge.
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Milan"))
.forEach(trader -> trader.setCity("Cambridge"));
System.out.println(transactions);
// Query 7: What's the highest value in all the transactions?
int highestValue =
transactions.stream()
.map(Transaction::getValue)
.reduce(0, Integer::max);
System.out.println(highestValue);
}
} int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);위 코드에는 박싱 비용이 숨어 있다.
int calories = menu.stream()
~~.map(Dish::getCalories)~~
.sum();기본형 특화 스트림
-
숫자 스트림
- mapToInt
- mapToDouble
- mapToLong
-
객체 스트림으로 복원하기
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream<Integer> stream = intStream.boxed();
-
기본값: OptionalInt
OptionalInt maxCalories = menu.stream() .mapToInt(Dish::getCalories) .max(); int max = maxCalories.orElse(1);
숫자 범위
- 특정 범위의 숫자를 생성
IntStream evenNumbers = IntStream.rangeClosed(1, 100) .filter(n -> n % 2 == 0);
IntStream evenNumbers = IntStream.range(1, 100) .filter(n -> n % 2 == 0);
숫자 스트림 활용 : 피타고라스 수
- 피타고라스 수
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).boxed() .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})); pythagoreanTriples.forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
값으로 스트림 만들기
-
문자열 스트림
Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action"); stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty();
-
배열 스트림
int[] numbers = {2, 3, 5, 7, 11, 13}; System.out.println(Arrays.stream(numbers).sum());
-
무한 스트림 만들기
- iterate
- iterate는 요청할 때마다 값을 생산할수 있으며 끝이 없으므로 무한 스트림을 만든다. 이러한 스트림을 언바운드 스트림이라고 표현한다.
Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
- generate
- generate는 Supplier를 인수로 받아서 새로운 값을 생산한다.
Stream.generate(Math::random) .limit(10) .forEach(System.out::println);
IntSupplier fib = new IntSupplier(){ private int previous = 0; private int current = 1; public int getAsInt(){ int nextValue = this.previous + this.current; this.previous = this.current; this.current = nextValue; return this.previous; } }; IntStream.generate(fib).limit(10).forEach(System.out::println);
- 위 코드에서는 IntSupplier 인스턴스를 만들었다. 만들어진 객체는 기존 피보나치 요소와 두인스턴스 변수에 어떤 피보나치 요소가 들어 있는지 추적하므로 가변 상태 객체다. getAsInt를 호출하면 객체 상태가 바뀌며 새로운 값을 샌산한다. iterate를 사용했을 때는 각 과정에서 새로운 값을 생성하면서도 기존 상태를 바꾸지 않는 순수한 불변 상태를 유지했다. 스트림을 병렬로 처리하면서 올바른 결과를 얻으려면 불변 상태 기법을 고수해야 한다는 사실을 7장에서 배울 것이다.
- iterate