이 글은 모던 자바 인 액션 책을 읽고 정리한 내용입니다.
http://www.yes24.com/Product/Goods/77125987
모던 자바 인 액션 - 예스24
자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능과 문법, 디자인 패턴
www.yes24.com
필터링
프레디케이트로 필터링
스트림 인터페이스는 filter 메서드를 지원한다. 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);
스트림 슬라이싱
이번에는 스트림의 요소를 선택하거나 스킵하는 다양한 방법을 알아보자.
프레디케이트를 이용한 슬라이싱
자바 9는 takeWhile, dropWhile 두 가시 새로운 메서드를 지원한다.
TAKEWHILE 활용
List 요소 중에 칼로리가 320 이하의 요리만 선택하고 싶다면 어떻게 해야할까?
앞에서 배운 filter를 이용해 filter(dish -> dish.getCalories < 320)
이런 식으로 사용할 수 있다.
하지만, 이미 정렬되어 있는 리스트의 경우라면 320 칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단할 수 있다.
List slicedMenu = menu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(toList());
DROPWHILE 활용
나머지 요소를 선택하려면 어떻게 해야 할까? dropWhile을 이용해보자.
List slicedMenu = menu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(toList());
스트림 축소
스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n)
메서드를 지원한다. 스트림이 정렬되어 있으면 최대 요소 n개를 반환할 수 있다.
다음은 300칼로리 이상의 세 요리를 선택해 리스트로 만드는 코드이다.
List slicedMenu = menu.stream()
.filter(dish -> dish.getCalories() >300)
.limit(3)
.collect(toList());
요소 건너뛰기
스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다.
매핑
특정 객체에서 특정데이터를 선택하는 작업이다.
스트림의 각 요소에 함수 적용하기
map 메서드는 함수를 인수로 받는 스트림이다. 이때 ㅈ인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소를 매핑된다.
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
스트림 평면화
메서드 map을 응용해서 리스트에서 고유 문자로 이루어진 리스트를 반환해보자.
예제 : ["Hello", "World"]
리스트에서 해당 문자열들을 이루는 고유한 알파벳을 포함하는 리스트에서 ["H","e", "l", "o", "W", "r", "d"]
를 반환받고 싶다.
잘못된 방법1. map 메서드를 사용해서 단어를 단일문자로 매핑하고 distinct를 걸어준다면..?
words.stream().map(word -> word.split("")) //단일문자 매핑
.distinct().collect(toList()); //distinct
여기서 문제는 map메서드가 반환하는 타입이 Stream이 아닌 Stream<String[]>이라는 것이다. split() 함수의 리턴타입이 String[]이어서 그렇다.
잘못된 방법2. map과 Arrays.stream 활용
Arrays.stream() 메서드는 String배열을 인자로 받아서 Stream으로 만들어준다.
하지만 map메서드를 통해서 반환받은 스트림객체에 .map(Arrays::stream) 메서드를 추가하면 String배열마다 별도의 스트림을 생성하게 되므로 결과적으로 List 이 최종 반환된다.
words.stream().map(word -> word.split("")) //단일문자 매핑
.map(Arrays::stream)
.distinct().collect(toList()); //distinct
해결 방법. flatMap을 사용해서 하나의 단일 스트림으로 반환 받기
flatMap을 사용하면 요소별로 생성되는 스트림을 하나의 스트림으로 평면화(연결)해준다.
즉 반환받은 하나의 Stream스트림 객체에 .distinct()를 체이닝 함으로써 원했던 고유한 알파벳 리스트 ["H","e", "l", "o", "W", "r", "d"] 를 반환 받을 수 있다.
검색과 매칭
특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리도 자주 사용된다.
Predicate를 이용한 요소 검사
- anyMatch : Predicate가 적어도 한 요소와 일치하는지 확인
if(menu.stream().anyMatch(Dish::isVegetarian)) {
...
}
- allMatch : Predicate가 모든 요소와 일치하는지 검사
- noneMatch : allMathch와 반대 연산
이 세 메서드는 스트림 쇼트 서킷 기법, 즉 자바의 &&, || 같은 연산을 활용한다.
요소 검색
findAny 메서드는 현재 스트림에서 임의의 요소를 반환한다. 다른 스트림과 연결해서 사용하며 조건에 만족하는 스트림 요소가 하나도 있는 경우 이를 반환한다.
아래의 예시에서 사용된 Optional클래스는 null처리 등을 쉽게 할 수 있도록 자바 8 부터 제공하는 클래스이다.
Optional dish = menu.stream().filter(Dish::isVegetarian).findAny();