안녕하세요. 오늘은 Java 8의 기능 중 하나인 Stream에 대해서 알아보도록 하겠습니다. Stream은 배열, List등의 요소들의 처리를 담당하는 Class로써 기존의 배열처리를 간단하게 해주며, functional style로 처리할 수 있도록 해줍니다. 그리고 병렬처리 Optional 등 다양한 처리를 할 수 있도록 지원해줍니다.
Stream 생성
Stream을 사용하기 위해서는 Stream을 생성할 필요가 있습니다. collection 또는 배열을 이용해서, 또는 자체적으로 생성할 수 있습니다. 아래와 같이 말이죠.
// from Array
String[] arr = new String[]{"a","b","c"};
Stream<String> stream = Arrays.stream(arr);
// from Collection
List<String> lists = Arrays.asList("a", "b", "c");
Stream<String> stream = lists.stream();
// only Stream Class
Strean<String> stream = Stream.of("a", "b", "c");
Stream의 멀티 스레딩
Stream API는 간단하게 multithreading으로 데이터를 처리할 수 있도록 parallelStream() 메서드를 지원해주고 있습니다. 사용방법은 상당히 편하니다만 이를 사용할 때는 반드시 내부의 로직을 알고 있어야합니다. 그 내용은 이 링크를 확인해 주시기바랍니다. [java8] 병렬 Stream 그렇지 않으면 원하지 않은 오류가 발생할 수 있습니다.
사용법은 아래와 같습니다.
list.parallelStream().forEch(element->doWork(element));
Stream Operations
일반적으로 Stream은 2가지 형태로 나눌 수 있습니다.
- intermediate operations
- return Stream
- terminal operations
- return 특정한 type의 값
이런 형태를 띄므로 intermediate operations를 통해서 chain형태로 구성할 수 있습니다. 아래 예제를 보도록 하겠습니다.
List<String> list = Arrays.asList("a", "b", "c");
long count = list.stream() // List<String> to Stream<String>
.distinct() // return Stream<String>
.count(); // return long
위를 설명하면 제일 먼저 String ArrayList를 Stream으로 변경합니다. 그리고 distinct() 메서드를 통해 중복되는 값을 제거합니다. 이때 distinct()에 의해서 반환되는 값은 Stream<String>
입니다. 즉, 사용자는 한번더 Stream
의 메서드를 사용할 수 있다는 말입니다. 그리고 마지막에 count()는 Stream에 들어있는 수를 long type으로 리턴합니다.
이런식으로 2가지의 일을 1개의 line으로 구성할 수 있다는 사실을 알게되었습니다. Stream을 이용하면 여러가지 일을 한번에 처리할 수 있게되어 간략화 시킬 수 있습니다.
Stream의 지원하는 제어
Stream이 간략화 시킬 수 있다는 사실을 알게되었습니다. 그러면 이제 Stream에서 지원하는 여러 방법에 대해서 알아보도록 하겠습니다. 모든 예제는 아래의 List를 사용한다고 하겠습니다.
List<String> list = Arrays.asList("a", "b", "c");
순환
Stream API는 for, for-each, while loop를 대신 할 수 있습니다. 아래와 같이 사용하면 말이죠.
list.stream().foreach(element -> ...somthingToDo...);
사실 stream() method에 순환이라는 개념이 들어가 있습니다. 그래서 위와같이 사용하기 보다는 순환에 대해서 특정부위를 확인하는 식으로 사용하는게 좋습니다. 아래와 같이 말이죠.
// before
for ( String string : list) {
if(string.contains("a")) {
return true;
}
}
// after
boolean isExist = list.stream().anyMatch(element -> element.contains("a));
필터링
filter() 메서드는 Stream에서 특정한 내용만 통과시켜 파이프라인을 형성할 수 있도록 해줍니다.
Stream<String> stream = list.stream().filter(e -> element.contains("b"));
이렇게 반환된 stream의 요소로는 "b" 하나만을 남기고 모두 filtering되어 사라졌음을 확인할 수 있습니다. 만약 filtering 메서드를 커스터마이징 하고 싶다면 메서드를 만들 때 return 값은 boolean이라는 규칙을 지켜주시면 됩니다.
public class Compare {
public static boolean isB(String string) {
return string.contains("b");
}
}
// 사용
list.stream().filter(Compare::isB); // b
이렇게 사용할 수 있게 됩니다. 혹시 lambda식에 대해서 잘 모르시겠다면 저의 이전포스터 들을 참조하시기 바랍니다.
매핑
매핑은 Stream의 요소를 특정한 규칙을 기반으로 변경할 수 있습니다.
위의 요소들을 대문자로 변경하고 싶다면 아래와같이 사용하면 됩니다.
list.stream.map(String::toUpperCase); // A, B, C
커스터마이징을 하고싶다면 메서드를 제작할 시 객체의 반환값을 가지면 됩니다.
중복제거
reduce() 메서드를 사용하면 중복으로 적어야하는 연산을 한번에 할 수 있습니다.
list.stream().reduce("1", (a, b) -> a + b); // 1abc
조합
collection() 메서드는 Stream을 다시 List, Map 등 다양한 우리가 일반적으로 알고 있는 Collection, Object로 변환해 줍니다.
List<String> resultList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
resultList에는 stream연산을 통해 나온 결과가 나오게 됩니다.
마무리
제 개인적인 기준에서는 Stream이야 말로 Java 8에서 추구하는 간결성을 달성하기위한 최고의 무기가 아닐까 생각합니다. lambda식을 잘 사용하는 것만으로도 굉장하지만 이를 이용하여 Stream까지 자유롭게 사용 할 수 있다면 우리가 항상 지루하게 수십줄 씩 썼던 내용을 1줄, 2줄로 줄여버릴 수 있습니다.
감사합니다.
해당 문서는 아래 참조문서를 통해 조금 더 쉽게 이해하기 위해 재가공했습니다.
참조
'language, framework, library > Java' 카테고리의 다른 글
[java] java Enum의 개념과 기본 메서드 (0) | 2020.03.11 |
---|---|
[Java 8] Java 8, Stream의 Collectors (0) | 2019.11.29 |
[Java 8] Java 8에서의 새로운 특징 (0) | 2019.11.19 |
[java 8] 예제로 보는 lambda식_비교하기 (0) | 2019.11.18 |
[Java 8] 람다 표현식과 주의 사항 (0) | 2019.11.13 |
댓글