Java Stream 개요
자바는 90년대 공개된 이후 현재도 가장 인기가 많은 언어 중 하나로 손꼽히고 있다.
이유는 간단하다. 사용자가 필요로 하는 기능이 자바에 계속 추가되니까.
2014년 공개된 자바 8엔 함수형 프로그래밍을 지원하는 스트림이 포함되었다.
스트림의 추가는 자바를 완전히 새로운 형태로 작성할 수 있는 새로운 장을 열었다고 보아도 무방하다.
'함축적이고 짧아진 코드' 그 이상을 제공하고 있기 때문이다.
자바 8 설계에 쓰인 프로그래밍 개념
자바 8을 설계하는데 고려한 프로그래밍 세 가지 개념을 소개한다.
🖇 스트림 처리
스트림은 한 번에 한 개씩 만들어지는 연속적인 데이터 항목의 모임이다.
이론적으로 프로그램은 입력 스트림에서 하나씩 읽어들이고, 출력 스트림에 하나씩 기록한다.
한 프로그램의 출력 스트림이 다른 프로그램의 입력 스트림이 될 수도 있다.
이미 리눅스 커맨드 쉘에서 많이 활용하고 있는 기능일 것이다.
ls -l . | grep txt | sort | tail -3
위 명령어는 현재 디렉토리에서 txt를 포함하는 모든 파일을 찾아낸 뒤 정렬하고, 그 결과의 맨 아래 3줄만 출력한다.
순차적으로 넘기는 것처럼 보이지만 각 명령어는 병렬로 처리될 수 있다.
grep이 완료되기 전에 sort가 동작하는 것이다.
자바 8의 스트림도 병렬 처리가 가능한데, 스레드와 같은 복잡한 처리가 필요하지 않아 매우 간단하다.
🔌 행위 매개변수화 (Behavior parameterization)
코드를 API로 전달한다.
더 쉽게 말하면 메소드를 매개변수로 사용할 수 있다는 것이다.
-
sort의 정렬 기준?
위 리눅스 명령어 예제에서 sort의 기준이 무엇일까? 가끔 바꾸고 싶을 때가 있지 않을까?
기본이 오름차순이라면 내림차순 기준으로 변경하고 싶을 수도 있다.
자바로 구현한다고 생각한다면,
new Comparator() { ...}
를 직접 만들어서 기준을 바꿔주면 된다.자바 7까지는 위와 같은 흐름으로 개발했다.
-
스트림을 활용하면?
스트림은 기존 로직을 크게 해치지 않으면서 내 코드를 주입하듯이 구현할 수 있다.
⛓ 병렬성과 공유 데이터
보다 쉽게 처리하는 병렬성
원활한 병렬 처리를 위해서는 공유 데이터를 사용하지 않는 것이 일반적이다.
동시 접근 시 의도와는 다른 결과가 나타날 수 있기 때문이다.
자바에서는 공유 데이터에 대한 접근 제어를 synchronized
와 같은 키워드를 활용해서 처리했으나 속도가 굉장히 느리다는 단점이 있다.
자바 8 스트림을 활용하면 병렬 처리를 더 쉽게 할 수 있으면서도 속도를 크게 해치지 않는다.
자바와 함수
자바는 함수보다는 메소드라는 용어를 사용한다. (Why functions are called methods in java?)
그러나 자바 8 스트림에서는 함수라는 용어를 사용할 수 있다.
객체의 메소드보다 더 일반적인 형태로 사용하고자 정의된 요소가 필요하기 때문이다.
1급 시민
자바스크립트에서는 함수 자체가 값을 가지게 할 수 있다.
이런 함수를 1급 시민이라고 한다.
자바의 클래스는 그 자체로는 값을 가지지 않기 때문에 2급 시민이다.
메소드 참조와 1급 시민
자바 8에서 새롭게 추가된 특징인 메소드 참조 기능을 활용하면 메소드를 1급 시민으로 만들 수 있다.
// OLD
File[] hiddenFiles = new File(".").listFiles(new FileFileter() {
public boolean accept(File file) {
return file.isHidden();
}
});
// NEW
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
File
의 isHidden 메소드 자체를 값으로 전달했다.
File::isHidden
대신 (File file) -> file.isHidden()
을 사용하면 익명 람다 함수로 전달한다.
🌊스트림
자바의 Collection은 다양한 자료 구조가 구현되어 있다.
개발자는 필요한 것을 가져다 쓰기만 하면 된다.
그러나 코드가 복잡해진다는 단점이 존재했다.
// OLD
Map<String, SomeObject> map = new HashMap<>();
for (SomeObject obj : objs) {
if (obj.isObject()) {
map.put(obj.getName(), obj);
}
}
// NEW
Map<String, SomeObject> map =
objs.stream()
.filter((SomeObject obj) -> obj.isObject())
.collect(groupingBy(SomeObject::getName));
예시가 워낙 짧아 컬렉션을 활용한 코드가 더 단순해 보인다. (본인의 상상력을 활용하여 긴 코드라고 생각해보자...)
전자는 논리적인 흐름으로 파악할 수 있다면 후자는 적절한 이름으로 이어가며 처리한다.
또 중요한 차이가 있는데, foreach에서는 각 요소를 반복하며 데이터를 처리하지만 스트림 API는 라이브러리 내부에서 모든 데이터를 처리한다.
이를 내부 반복(Internal iteration) 이라고 한다.
-
objs에 너무 많은 데이터가 담겨있다면?
foreach문에서는 데이터를 처리하는데 많은 시간을 소요한다.
그러나, stream은 병렬 처리를 쉽게 구현할 수 있기 때문에 더 빠르게 처리할 수 있다.
'Java' 카테고리의 다른 글
Java Stream (6) 스트림 생성 (0) | 2020.07.22 |
---|---|
Java Stream (5) 기본 자료형 스트림 (0) | 2020.07.22 |
Java Stream (4) 스트림 주요 메소드 (0) | 2020.07.17 |
Java Stream (3) 스트림의 개념 (0) | 2020.07.15 |
Java Stream (2) 람다 표현식 (0) | 2020.07.08 |