Java 8 Streams 소개
업데이트: Link
Introduction to Java 8 Streams
원문: Introduction to Java 8 Streams
1. Overview
이 글에서는 Java 8에 추가된 새로운 기능 중 주요 기능 중 하나인 스트림에 대해 간략히 살펴보겠습니다.
스트림에 대해 설명하고 간단한 예제를 통해 스트림 생성 및 기본 스트림 작업을 소개합니다.
2. 스트림 API
Java 8의 주요 신기능 중 하나는 요소 시퀀스를 처리하는 클래스가 포함된 스트림 기능 - java.util.stream
- 의 도입입니다.
중앙 API 클래스는 Stream<T>
입니다. 다음 섹션에서는 기존 데이터 제공자를 사용하여 스트림을 생성하는 방법을 설명합니다.
2.1. 스트림 생성
스트림은 stream()
및 of()
메서드를 사용하여 컬렉션이나 배열과 같은 다양한 요소 소스에서 생성할 수 있습니다:
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
stream = Stream.of("a", "b", "c");
Collection
인터페이스에 stream()
기본 메서드가 추가되어 모든 컬렉션을 구성요소로 사용하여 Stream<T>
를 생성할 수 있습니다:
Stream<String> stream = list.stream();
2.2. 스트림을 사용한 멀티 스레딩
또한 스트림 API는 병렬 모드에서 스트림의 요소에 대한 연산을 실행하는 parallelStream()
메서드를 제공하여 멀티스레딩을 단순화합니다.
아래 코드는 스트림의 모든 요소에 대해 doWork()
메서드를 병렬로 실행할 수 있게 해줍니다:
list.parallelStream().forEach(element -> doWork(element));
다음 섹션에서는 기본적인 스트림 API 작업 몇 가지를 소개합니다.
3. 스트림 작업
스트림에서 수행할 수 있는 유용한 작업이 많이 있습니다.
중간 연산(Stream<T>
반환)과 종료 연산(정해진 타입의 결과 반환)으로 나뉩니다. 중간 연산은 연쇄를 허용합니다.
스트림에 대한 작업은 소스를 변경하지 않는다는 점도 주목할 필요가 있습니다.
다음은 간단한 예시입니다:
long count = list.stream().distinct().count();
따라서 distinct()
메서드는 이전 스트림의 중복 요소를 제거한 새 스트림을 생성하는 중간 연산을 나타냅니다. 그리고 count()
메서드는 스트림의 크기를 반환하는 말단 연산 입니다.
3.1. Iterating
스트림 API는 for
, for-each
, while
루프를 대체하는 데 도움이 됩니다. 이를 통해 연산 로직에 집중할 수 있습니다. 예를 들어
boolean isExist = false;
for (String string : list) {
if (string.contains("a")) {
isExist = true;
break;
}
}
이 코드는 Java 8 코드 한 줄로 변경할 수 있습니다:
boolean isExist = list.stream().anyMatch(element -> element.contains("a"));
3.2. Filtering
filter()
메서드를 사용하면 조건을 만족하는 요소 스트림을 선택할 수 있습니다.
예를 들어 다음 목록을 고려해 보세요:
ArrayList<String> list = new ArrayList<>();
list.add("One");
list.add("OneAndOnly");
list.add("Derek");
list.add("Change");
list.add("factory");
list.add("justBefore");
list.add("Italy");
list.add("Italy");
list.add("Thursday");
list.add("");
list.add("");
다음 코드는 List<String>
의 Stream<String>
을 생성하고, 이 스트림에서 char 'd'
를 포함하는 모든 요소를 찾은 다음, 필터링된 요소만 포함하는 새 스트림을 생성합니다:
Stream<String> stream = list.stream().filter(element -> element.contains("d"));
3.3. Mapping
Stream
구성요소에서 새로운 구성요소를 Stream
으로 수집하려면 map()
메서드를 사용할 수 있습니다:
List<String> uris = new ArrayList<>();
uris.add("C:\\My.txt");
Stream<Path> stream = uris.stream().map(uri -> Paths.get(uri));
따라서 위의 코드는 초기 Stream
의 모든 요소에 특정 람다 표현식을 적용하여 Stream<String>
을 Stream<Path>
로 변환합니다.
모든 요소가 고유한 요소 시퀀스를 포함하는 스트림이 있고 이러한 내부 요소의 스트림을 생성하려는 경우 flatMap()
메서드를 사용해야 합니다:
List<Detail> details = new ArrayList<>();
details.add(new Detail());
Stream<String> stream
= details.stream().flatMap(detail -> detail.getParts().stream());
이 예제에서는 Detail
유형의 요소 목록이 있습니다. Detail
클래스에는 List<String>
인 PARTS
필드가 포함되어 있습니다. flatMap()
메서드를 사용하면 PARTS
필드에서 모든 요소가 추출되어 새로운 결과 스트림에 추가됩니다. 그 후에는 초기 Stream<Detail>
이 손실됩니다.
3.4. Matching
스트림 API는 특정 술어에 따라 시퀀스의 요소를 검증할 수 있는 편리한 도구 세트를 제공합니다. 이를 위해 다음 메서드 중 하나를 사용할 수 있습니다. anyMatch()
, allMatch()
, noneMatch()
. 이름만 들어도 알 수 있습니다. 이들은 boolean
을 반환하는 터미널 연산입니다:
boolean isValid = list.stream().anyMatch(element -> element.contains("h")); // true
boolean isValidOne = list.stream().allMatch(element -> element.contains("h")); // false
boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h")); // false
빈 스트림의 경우, 주어진 술어와 함께 allMatch()
메서드는 true
를 반환합니다:
Stream.empty().allMatch(Objects::nonNull)); // true
Stream.empty().noneMatch(Objects::nonNull)); // true
Stream.empty().anyMatch(Objects::nonNull); // false
서술어를 만족하지 않는 요소를 찾을 수 없으므로 이는 합리적인 기본값입니다.
마찬가지로 anyMatch()
메서드는 빈 스트림에 대해 항상 false
를 반환합니다.
다시 말하지만, 이 조건을 충족하는 요소를 찾을 수 없기 때문에 이는 합리적입니다.
3.5. Reduction
Stream API를 사용하면 Stream
유형의 reduce()
메서드를 사용하여 지정된 함수에 따라 요소 시퀀스를 특정 값으로 줄일 수 있습니다. 이 메서드에는 두 개의 매개변수가 필요합니다. 첫 번째는 시작 값, 두 번째는 누산기 함수입니다.
List<Integer>
가 있고 이 모든 요소와 일부 초기 Integer
(이 예제에서는 23)의 합을 갖고 싶다고 가정해 보겠습니다. 따라서 다음 코드를 실행하면 결과는 26(23 + 1 + 1 + 1)이 됩니다.
List<Integer> integers = Arrays.asList(1, 1, 1);
Integer reduced = integers.stream().reduce(23, (a, b) -> a + b);
3.6. Collecting
스트림을 Stream
타입의 collect()
메서드로도 줄일 수 있습니다. 이 작업은 스트림을 Collection
또는 Map
으로 변환하고 스트림을 단일 문자열 형태로 나타낼 때 매우 편리합니다. 거의 모든 일반적인 수집 작업에 대한 솔루션을 제공하는 유틸리티 클래스 Collectors
가 있습니다. 사소한 작업이 아닌 일부 작업의 경우 사용자 정의 Collector
를 만들 수 있습니다.
List<String> resultList
= list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
이 코드는 터미널 collect()
연산을 사용하여 Stream<String>
의 구성요소 String
을 대문자로 바꾼Stream<String>
으로 변환한 뒤 List<String>
으로 변환합니다.
4. 결론
이 글에서는 가장 흥미로운 Java 8 기능 중 하나인 Java 스트림에 대해 간략하게 살펴보았습니다.
스트림을 사용하는 더 많은 고급 예제가 있지만, 이 글의 목적은 기능을 사용하여 시작할 수 있는 작업에 대한 빠르고 실용적인 소개와 탐색 및 추가 학습을 위한 출발점을 제공하는 것입니다.
이 글에 첨부된 소스 코드는 GitHub Repository에서 확인할 수 있습니다.
댓글남기기