스트림이란?
JDK 8에서 추가된 Stream API에 대해서 알아보자.(파일 I/O에서 사용되는 스트림과는 다르다.)스트림은 데이터소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서들을 정의해 놓았다. 데이터소스를 추상화하였다는 것은, 데이터 소스가 무엇이든 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미한다.
Stream 가공하기(중간연산)
생성한 Stream 객체에서 요소들을 가공하기 위해서 중간 연산이 필요하다.
[Filter - 필터링]
Filter는 Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어내는 연산이다.
Java에서는 filter 함수의 인자로 함수형 인터페이스 Predicate를 받고 있기 때문에, boolean을 반환하는 람다식을 작성하여 filter 함수를 구현할 수 있다.
String의 stream에서 a가 들어간 문자열만을 포함하도록 필터링하는 예제.
String[] nameArr = {"IronMan", "Caption", "Hulk", "Thor"};
List<String> list = Arrays.asList(nameArr);
Stream<String> stream = list.stream()
.filter(name -> name.contains("a"));
stream.forEach(System.out::println);
IronMan Caption
[Map - 데이터 변환]
Map은 기존의 Stream 요소들을 변환하여 새로운 Stream을 형성하는 연산이다. 저장된 값을 특정한 형태로 변환하는데 주로 사용되며,
Java에서는 map 함수의 인자로 함수형 인터페이스 function을 받고 있다.
Stream을 모두 대문자로 String의 요소들로 변환하는 예.
Stream<String> streamMap = list.stream()
.map(s -> s.toUpperCase());
streamMap.forEach(System.out::println);
IRONMAN CAPTION HULK THOR
[Sorted - 정렬]
Stream의 요소들을 정렬하기 위해서는 sorted를 사용해야 하며, 파라미터로 Comparator를 넘길 수도 있다. Comparator인자 없이 호출할 경우에는 오름차순으로 정렬이 되며, 내림차순으로 정렬하기 위해서는 Comparator의 reverseOrder를 이용하면 된다.
Stream의 String 요소들을 정렬하는 예
Stream<String> streamSorted = list.stream()
.sorted();
Stream<String> streamSorted_reverse = list.stream()
.sorted(Comparator.reverseOrder());
streamSorted.forEach(System.out::println);
streamSorted_reverse.forEach(System.out::println);
Caption Hulk IronMan Thor
----------
Thor IronMan Hulk Caption
[Distinct - 중복 제거]
Stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 distinct를 사용할 수 있다. distinct는 중복된 데이터를 검사하기 위해 Object의 equals() 메소드를 사용한다.
중복된 Stream의 요소들을 제거하는 예
List<String> list2 = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift", "Java");
Stream<String> streamDistinct = list2.stream()
.distinct();
streamDistinct.forEach(System.out::println);
Java Scala Groovy Python Go Swift
[Peak - 특정 연산 수행]
Stream의 요소들을 대상으로 Stream에 영향을 주지 않고 특정 연산을 수행하기 위한 peek 함수가 존재한다. peek 함수는 Stream의 각각의 요소들에 대해 특정 작업을 수행할 뿐 결과에 영향을 주지 않는다. 파라미터로 함수형 인터페이스 Consumer를 인자로 받는다.
stream의 요소들 중간에 출력하기를 원할 때 다음과 같이 사용할 수 있다.
int sum = IntStream.of(1, 3, 5, 7, 9)
.peek(System.out::println)
.sum();
1 3 5 7 9
Stream 결과 만들기(최종 연산)
중간 연산을 통해 생성된 Stream을 바탕으로 결과를 만들어 낸다.
[ 최댓값/최솟값/총합/평균/갯수 - Max/Min/Sum/Average/Count ]
Stream의 요소들을 대상으로 최솟값이나 최댓값 또는 총합을 구하기 위한 최종 연산들이 존재한다.
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();
System.out.println(min.getAsInt());
System.out.println(max.getAsInt());
int max2 = IntStream.of().max().orElse(0);
System.out.println(max2);
IntStream.of(1, 3, 5, 7, 9).average().ifPresent(System.out::println);
long count = IntStream.of(1, 3, 5, 7, 9).count();
long longStreamSum = LongStream.of(1, 3, 5, 7, 9).sum();
System.out.println(count);
System.out.println(longStreamSum);
1 9
0
5.0
5 25
[collect - 데이터 수집]
Stream의 요소들을 List나 Set, Map, 등 다른 종류의 결과로 수집하고 싶은 경우에는 collect 함수를 이용한다.
class Product{
int num;
String name;
public Product(int num, String name) {
super();
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product [num=" + num + ", name=" + name + "]";
}
}
public class StreamAPI02 {
public static void main(String[] args) {
List<Product> productList = Arrays.asList(
new Product(23, "potatoes"),
new Product(14, "orange"),
new Product(13, "lemon"),
new Product(23, "bread"),
new Product(13, "sugar"));
productList.forEach(System.out::println);
}
}
Product [num=23, name=potatoes]
Product [num=14, name=orange]
Product [num=13, name=lemon]
Product [num=23, name=bread]
Product [num=13, name=sugar]
Collectors.toList()
Stream에서 작업한 결과를 List로 반환받을 수 있다.
List<String> nameList = productList.stream()
.map(Product::getName)
.collect(Collectors.toList());
nameList.forEach(System.out::println);
potatoes orange lemon bread sugar
Collectors.joining()
Stream에서 작업한 결과를 1개의 String으로 이어붙일 수 있다.
Collectors.joining()은 총 3개의 인자를 받을 수 있다.
public static Collector<CharSequence,?,String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix)
delimiter: 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
prefix: 결과 맨 앞에 붙는 문자
suffix: 결과 맨 뒤에 붙는 문자
String listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining());
System.out.println(listToString);
listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining(" "));
System.out.println(listToString);
listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining(", ", "<", ">"));
System.out.println(listToString);
potatoesorangelemonbreadsugar
potatoes orange lemon bread sugar
<potatoes, orange, lemon, bread, sugar>
Collectors.averagingInt(), Collectors.summingInt(), Collectors.summarizingInt()
averagingInt(): 평균을 생성하는 Collector를 반환(값이 없으면 결과는 0)
summingInt(): 합을 생성하는 Collector를 반환(값이 없으면 결과는 0)
summarizingInt(): int-production 매핑 함수를 적용해서 요약 통계를 반환
Double averageAmount = productList.stream()
.collect(Collectors.averagingInt(Product::getNum));
Integer summingAmount = productList.stream()
.collect(Collectors.summingInt(Product::getNum));
summingAmount = productList.stream()
.mapToInt(Product::getNum)
.sum();
17.2
86
86
Collectors.summarizingInt()는 IntSummaryStatistics 객체가 반환되므로, 필요한 값에 대해 get 메소드를 이용하여 원하는 값을 꺼내면 된다.
//Collectors.summarizingInt()
public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(
IntSummaryStatistics::new,
(r, t) -> r.accept(mapper.applyAsInt(t)),
(l, r) -> { l.combine(r); return l; }, CH_ID);
}
//IntSummaryStatistics
public class IntSummaryStatistics implements IntConsumer {
private long count;
private long sum;
private int min = Integer.MAX_VALUE;
private int max = Integer.MIN_VALUE;
public String toString() {
return String.format(
"%s{count=%d, sum=%d, min=%d, average=%f, max=%d}",
this.getClass().getSimpleName(),
getCount(),
getSum(),
getMin(),
getAverage(),
getMax());
}
}
IntSummaryStatistics statistics = productList.stream()
.collect(Collectors.summarizingInt(Product::getNum));
System.out.println(statistics.toString());
//IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}
Collectors.groupingBy()
Stream에서 작업한 결과를 특정 그룹으로 묶을 수 있다. groupingBy는 매개변수로 함수형 인터페이스 Function을 필요로 한다.
수량을 기준으로 grouping을 하는 경우 아래와 같이 작성할 수 있으며, 같은 수량일 경우에 List로 묶어서 값을 반환한다.
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
.collect(Collectors.groupingBy(Product::getNum));
System.out.println(collectorMapOfLists);
/*
{23=[Product [num=23, name=potatoes], Product [num=23, name=bread]]
, 13=[Product [num=13, name=lemon], Product [num=13, name=sugar]]
, 14=[Product [num=14, name=orange]]}
*/
Collectors.partitioningBy()
Collectors.partitioningBy()는 함수형 인터페이스 Predicate를 받아 Boolean을 Key값으로 partitioning한다.
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
Map<Boolean, List<Product>> mapPartitioned = productList.stream()
.collect(Collectors.partitioningBy(p -> p.getNum() > 15));
System.out.println(mapPartitioned);
/*
{false=[Product [num=14, name=orange], Product [num=13, name=lemon], Product [num=13, name=sugar]]
, true=[Product [num=23, name=potatoes], Product [num=23, name=bread]]}
*/
[Match - 조건 검사]
Stream의 요소들이 특정한 조건을 충족하는지 검사하고 싶은 경우에는 match 함수를 이용할 수 있다.
match 함수는 함수형 인터페이스 Predicate를 받아서 해당 조건을 만족하는지 검사를 하게 되고, 검사 결과를 boolean으로 반환한다.
match 함수에는 크게 다음의 3가지가 있다.
anyMatch: 최소한 한 개의 요소가 주어진 조건에 만족하는지 검사
allMatc: 모든 요소들이 Predicate으로 주어진 조건을 만족하는지 검사
nonMatch: 모든 요소들이 주어진 조건을 만족하는지 검사
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
List<String> names = Arrays.asList("Ericc", "Elena", "Java");
boolean anyMatch = names.stream()
.anyMatch(name -> name.contains("a"));
boolean allMatch = names.stream()
.allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream()
.noneMatch(name -> name.endsWith("s"));
/*
true false true
*/
참고
https://mangkyu.tistory.com/114
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
'Programming > Java' 카테고리의 다른 글
[Java] Optional API (0) | 2023.04.09 |
---|---|
[Java] Stream API (0) | 2023.04.09 |
[Java] - 7. 자바 8 API의 기본 메서드와 스태틱 메소드 (0) | 2023.04.04 |
[Java] - 6. 인터페이스 기본 메소드와 스태틱 메소드 (0) | 2023.04.01 |
[Java] - 5. 메소드 레퍼런스 (0) | 2023.04.01 |