스트림 은 자바API에 새로 추가된 기능으로, 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소로 정의할 수 있다.
아래는 칼로리기준으로 요리를 정렬하는 자바7코드다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish dish : menu) {
if (dish.getCalories() < 400) {
lowCaloricDishes.add(d);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish dish1, Dish dish2) {
return Integer.compare(dish1.getCalories(), dish2.getCalories());
}
});
Lish<String> lowCaloricDishesName = new ArrayList<>();
for(Dish dish: lowCaloricDishes) {
lowCaloricDishesName.add(dish.getName());
}
아래는 같은내용의 자바8코드다.1
2
3
4
5List<String> lowCaloricDishesName = menu.stream()
.filter(dish -> dish.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
스트림의 특징
- 선언형 : 더 간결하고 가독성이 좋아진다.
- 조립할 수 있음 : 유연성이 좋아진다.
- 병렬화 : 성능이 좋아진다.
스트림 이용하기
스트림 이용 과정은 다음과 같이 세 가지로 요약할 수 있다
- 질의를 수행할 데이터 소스
- 스트림 파이프라링ㄴ을 구성할 중간 연산 연결
- 스트림 파이프라인을 실행하고 결과를 만들 최종연산
스트림 활용
필터링과 슬라이싱
스트림 인터페이스는 filter 메서드를 지원한다. filter메서드는 Predicate를 인수로 받아 일치하는 모든 요소를 포함하는 스트림을 반환한다.1
2
3List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian) // 채식요리인지 확인하는 메서드 레퍼런스
.collect(toList());
distinct
distinct를 사용하면 중복을 필터링한다.1
2
3
4
5List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct() // 중복된 요소를 제거
.forEach(System.out::println);
limit
limit를 사용하면 스트림 사이즈를 제한할 수 있다.1
2
3
4List<Dish> dishes = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3) // 요소수를 3개로 제한
.collect(toList());
skip(n)
skip(n)을 이용하면 n개 요소를 제외하고 나머지 요소를 반환한다.1
2
3
4List<Dish> dishes = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.skip(2) // 2개 요소를 건너뛴다
.collect(toList());
매핑
매핑 은 특정 객체에서 특정 데이터를 선택하는 작업으로, SQL의 테이블에서 특정 열만 선택하는 것과 같은 작업이다.
아래 예는 메뉴에서 요리명만 추출하는 내용이다.1
2
3List<String dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
검색과 매칭
anyMatch
Predicate가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인1
2
3if(menu.stream().anyMatch(Dish::isVegetarian)) {
..
}
allMatch
모든 요소가 주어진 Predicate와 일치하는지 검사1
2boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000)
noneMatch
주어진 Predicate와 일치하는 요소가 없는지 확인1
2boolean isHealthy = menu.stream()
.noneMatch(dish -> dish.getCalories() >= 1000);
findAny
현재 스트림에서 임의의 요소를 반환한다.1
2
3Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian) // filter와 연결해서 사용할 수 있다.
.findAny();
findFirst
스트림 내 첫번째 요소를 불러온다.1
2
3
4
5List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDicisibleByThree = someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
findFirst는 병렬실행에 유용하며, 반환요소가 상관없다면 findAny를 사용해도 상관없다.
리듀싱
모든 요소를 반복적으로 처리하는 과정으로 함수형 프로그래밍에서는 폴드 라고 불리기도 한다.
reduce는 두 개의 인수를 갖는다.
- 초깃값 (생략될 수 있다)
- BinaryOperator
1 | int sum = numbers.stream().reduce(0, (a, b) -> a + b); |
스트림 만들기
아래의 다양한 방법으로 스트림을 만들 수 있다.
- Stream.of : 값으로 스트림 만들기
- Arrays.stream : 배열로 스트림 만들기
- File.lines : 파일로 스트림 만들기
- Stream.iterate : 함수로 무한스트림 만들기
- Stream.generate : 함수로 무한스트림 만들기
Reference : Java 8 in Action