스트림
자바 언어 설계자들은 개발자의 시간을 절약해주면서도 쉽고 빠르게 코드를 작성할 수 있는 스트림이라는 기능을 추가했다.
아주 쉬운 예시를 통해 스트림의 특징을 파악해본다.
사용할 데이터
컬렉션과 스트림의 차이를 예시로 나타내기 위해 사용할 데이터를 만들었다.
유형을 City
클래스로 정의하고 대한민국의 광역시 목록을 cities에 저장하였다.
City
@Getter @AllArgsConstructor public class City { private String name; private double area; // 면적 private int population; // 인구 private String areaCode; // 지역 번호 }
cities
List<City> cities = Arrays.asList( new City("Seoul", 605.2, 9720846, "02"), new City("Incheon", 1063.3, 2947217, "032"), new City("Busan", 770.1, 3404423, "051"), new City("Gwangju", 501.1, 1455048, "062"), new City("Daegu", 883.5, 2427954, "053"), new City("Ulsan", 1062, 1142190, "052") );
cities 데이터를 가공해달라는 요구사항이 들어왔다.
- 면적(area)이 800 km^2 이상인 광역시의 목록을 추출한다.
- 면적을 기준으로 오름차순 정렬한다.
- 위의 기준을 충족한 광역시의 이름 목록만 반환받고자 한다.
컬렉션 처리
컬렉션으로 처리하면 보통 아래처럼 코드를 작성할 것이다.
List<City> largeCities = new ArrayList<>();
for (City city : cities) {
if (city.getArea() > 800) {
largeCities.add(city);
}
}
Collections.sort(largeCities, new Comparator<>() {
@Override
public int compare(City o1, City o2) {
return (int)(o1.getArea() - o2.getArea());
}
});
List<String> largeCityNames = new ArrayList<>();
for (City city : largeCities) {
largeCityNames.add(city.getName());
}
스트림 처리
같은 요구 사항을 스트림으로 처리한 코드이다.
List<String> streamNameList = cities.stream()
.filter(city -> city.getArea() > 800)
.sorted(Comparator.comparing(City::getArea))
.map(City::getName)
.collect(Collectors.toList());
스트림 처리의 장점
선언형 처리
간단한 병렬 처리
특정 환경에서 성능 향상을 기대할 수 있다.
cities.stream()
⇒cities.parallelStream()
으로 스트림 생성 부분을 교체한다.List<String> streamNameList = cities.parallelStream() .filter(city -> city.getArea() > 800) .sorted(Comparator.comparing(City::getArea)) .map(City::getName) .collect(Collectors.toList());
필요한 부품을 하나씩 조립하듯 구성
- 각 연산을 체이닝으로 잇고 결과를 다음 로직으로 전달한다.
- 필요한 연산만을 조립하여 유연하게 구성할 수 있다.
- 모든 과정이 내부에서 반복된다.
- 컬렉션은 필터링 시 외부 반복자를 사용하여 미해당 원소를 걸러낸다.
- 스트림은 반복자가 겉으로 드러나지 않는다.
- 연산 과정에서 불필요한 변수를 남기지 않는다.
- 컬렉션에선 정렬을 위한
List
형 변수를 하나 사용했다.
- 컬렉션에선 정렬을 위한
위와 같은 특징은 컬렉션과 스트림의 지향점이 다르기 때문에 나타나는 차이이다.
컬렉션은 데이터를 저장하는 자료구조 구현에 초점이 맞추어져 있고, 스트림은 데이터를 연산하는 데에 집중한 API이기 때문이다.
스트림 연산
스트림은 연산이라고 불리우는 부품을 모아 하나의 흐름으로 완성시켜 사용한다.
이를 스트림 파이프라인이라고 하며 그 흐름에는 3아래 가지가 반드시 포함되어야 한다.
- 스트림 생성
- 중간 연산
- 최종 연산
스트림 생성
스트림을 생성하는 것으로 연산을 시작한다.
생성된 스트림은 한 번 사용하면 사라지기 때문에 소비한다는 개념으로 사용해야 한다.
중간 연산
중간 연산은 Stream
을 반환한다.
그 덕분에 중간 연산 메소드는 체이닝으로 다음 중간 연산 메소드를 이어줄 수 있다.
체이닝하여 연결하는 각 연산은 스트림 파이프라인에 저장된다.
가장 중요한 특징은 중간 연산을 아무리 많이 연결한들 파이프라인이 실질적으로 수행되기 전까진 아무 연산도 하지 않는다는 것이다.
이를 Lazy Evaluation라 한다.
Lazy Evaluation
스트림 연산은 최종 연산이 호출되기 전까지 미뤄진다.
그 과정에서 연산에 불필요한 원소를 파악한 뒤 해당 원소를 사용한 연산은 회피하여 로직을 최적화한다.
최종 연산
중간 연산을 이어가며 만든 스트림 파이프라인에서 최종 결과를 도출하는 과정이다.
최종 연산이 끝나면 컬렉션(List
, Map
)이나 자료형(Integer
), void를 반환한다.
'Java' 카테고리의 다른 글
Java Stream (6) 스트림 생성 (0) | 2020.07.22 |
---|---|
Java Stream (5) 기본 자료형 스트림 (0) | 2020.07.22 |
Java Stream (4) 스트림 주요 메소드 (0) | 2020.07.17 |
Java Stream (2) 람다 표현식 (0) | 2020.07.08 |
Java Stream (1) 스트림 개요 (0) | 2020.06.02 |