스트림 자르기 - skip(), limit()
skip()과 limit()은 스트림의 일부를 잘라낼 때 사용한다.
skip(3)은 처음 3개의 요소를 건너뛰고, limit(5)는 스트림의 요소를 5개로 제한한다.
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.print("skip: ");
list.stream().skip(3).forEach(x -> System.out.print(x + " "));
System.out.println();
System.out.print("limit : ");
list.stream().limit(5).forEach(x -> System.out.print(x + " "));
--- 결과 ---
skip: 4 5 6 7 8 9 10
limit : 1 2 3 4 5
스트림 요소 걸러내기 - filler(), distinct()
distinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.
Stream<T> filter(Predicate<? super T> predicate)
Steram<T> distinct()
List<Integer> list = List.of(1, 1, 2, 2, 3, 3, 4, 4);
System.out.print("distinct : ");
list.stream().distinct().forEach(x -> System.out.print(x + " "));
System.out.println();
System.out.print("filter : ");
list.stream().filter(f -> f % 2 == 0 ).forEach(x -> System.out.print(x + " "));
--- 결과 ---
distinct : 1 2 3 4
filter : 2 2 4 4
정렬 - sorted()
스트림을 정렬할때는 sorted()를 사용하면 된다.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
sorted()는 지정된 Comparator로 스트림을 정렬하는데, Comparator대신 int값을 반환하는 람다식을 사용하는 것도 가능하다. Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준으로 정렬한다. 단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.
List<Integer> list = List.of(3, 5, 4, 2, 8, 6, 5, 7, 9);
list.stream().sorted().forEach(x -> System.out.print(x + " "));
System.out.println();
--- 결과 ---
2 3 4 5 5 6 7 8 9

정렬에 사용되는 가장 기본적인 메서드는 comparing()이다.
List<Student> init = InitTest.init();
init.stream().sorted(
Comparator.comparing((Student x) -> x.getScore()).reversed()
.thenComparing(k -> k.getHak())
.thenComparing(y -> y.getBan())
.thenComparing(z -> z.getName()
)
).forEach(a -> System.out.println("a = " + a));
--- 결과 ---
a = Student{name='알렉스', male=true, hak=1, ban=1, score=300}
a = Student{name='김물병', male=true, hak=2, ban=1, score=300}
a = Student{name='산체스', male=false, hak=1, ban=1, score=250}
a = Student{name='멍청이', male=true, hak=1, ban=4, score=250}
a = Student{name='박마카', male=false, hak=2, ban=1, score=250}
a = Student{name='밍밍밍', male=true, hak=2, ban=4, score=250}
a = Student{name='김말똥', male=true, hak=1, ban=1, score=200}
a = Student{name='바보', male=false, hak=1, ban=4, score=200}
a = Student{name='이선풍', male=true, hak=2, ban=1, score=200}
a = Student{name='박박박', male=false, hak=2, ban=4, score=200}
a = Student{name='개붕이', male=true, hak=1, ban=2, score=150}
a = Student{name='말티즈', male=false, hak=1, ban=4, score=150}
a = Student{name='나선환', male=true, hak=2, ban=2, score=150}
a = Student{name='박커피', male=false, hak=2, ban=4, score=150}
a = Student{name='턱시도', male=false, hak=1, ban=2, score=100}
a = Student{name='최시바', male=true, hak=1, ban=3, score=100}
a = Student{name='김에러', male=false, hak=2, ban=2, score=100}
a = Student{name='최우산', male=true, hak=2, ban=3, score=100}
a = Student{name='붕어빵', male=false, hak=1, ban=3, score=50}
a = Student{name='나칫솔', male=true, hak=2, ban=3, score=50}
위의 소스는 학생들의 성적이 가장 높은순, 학년별, 반별, 이름별로 정렬하여 출력한다.
comparing() 이후에 정렬 조건을 추가할 때는 thenComparing()을 사용한다.
변환 - map()
스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때 사용한다.
public static void main(String[] args) {
List<Student> list = InitTest.init();
List<Info> infoList = list.stream()
.sorted(Comparator.comparing(Student::getScore).reversed()
.thenComparing(Student::getName))
.limit(10)
.map(m -> new Info(m.getName(), m.getScore()))
.collect(Collectors.toList());
infoList.forEach(x -> System.out.println("x = " + x));
}
public static class Info {
String name;
int score;
public Info(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Info{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
--- 결과 ---
x = Info{name='김물병', score=300}
x = Info{name='알렉스', score=300}
x = Info{name='멍청이', score=250}
x = Info{name='밍밍밍', score=250}
x = Info{name='박마카', score=250}
x = Info{name='산체스', score=250}
x = Info{name='김말똥', score=200}
x = Info{name='바보', score=200}
x = Info{name='박박박', score=200}
x = Info{name='이선풍', score=200}
위의 소스는 점수가 가장 높은순서, 이름순으로 정렬한후 info객체로 다시 만들어서 출력하는 소스이다.
map()도 중간연산 이므로 하나의 스트림에 여러 번 적용할 수 있다.
조회 - peek()
연산과 연산 사이에 올바르게 처리되었는지 확인할때 사용된다.
forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제되지 않는다.
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list2 = list.stream().filter(f -> f % 2 == 0)
.peek(x -> System.out.println("x = " + x))
.filter(ff -> ff % 4 ==0)
.collect(Collectors.toList());
System.out.println();
list2.forEach(x -> System.out.println("x = " + x));
--- 결과 ---
x = 2
x = 4
x = 6
x = 8
x = 10
x = 4
x = 8
위와같이 peek은 중간연산이므로 다른 중간연산을 할 수 있다.
mapToInt(), MapToLong(), mapToDouble()
map()은 연산 결과로 Stream<T> 타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있다.
List<Student> list = InitTest.init();
IntStream intStream = list.stream().mapToInt(x -> x.getScore());
System.out.println("total Score : " + intStream.sum());
LongStream longStream = list.stream().mapToLong(x -> x.getScore());
System.out.println("Max Score : " + longStream.max().getAsLong());
DoubleStream doubleStream = list.stream().mapToDouble(x -> x.getScore());
System.out.println("Average : " + doubleStream.average().getAsDouble());
--- 결과 ---
total Score : 3500
Max Score : 300
Average : 175.0
스트림의 요소가 하나도 없을 때, sum()은 0을 반환하면 그만이지만 다른 메서드들은 단수히 0을 반환할 수 없다. 여러 요소들을 합한 평균이 0일 수도 있기 때문이다. 이를 구분하기 위해 단순히 double값을 반환하는 대신, double타입의 값을 내부적으로 가지고 있는 OptionalDouble을 반환하는 것이다. 그리고 sum, average, max, min 메서드는 최종연산이기 때문에 호출 후에 스트림이 닫힌다는 점을 주의해야 한다.
만일 sum()과 average()를 모두 호출해야할 때, 스트림을 또 생성해야하므로 불편하다. 그래서 기본형 스트림에서는 summaryStatistics()라는 메서드가 따로 제공된다.
List<Integer> list = List.of(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("intSummaryStatistics.getSum() = " + intSummaryStatistics.getSum());
System.out.println("intSummaryStatistics.getMax() = " + intSummaryStatistics.getMax());
System.out.println("intSummaryStatistics.getMax() = " + intSummaryStatistics.getMax());
System.out.println("intSummaryStatistics.getAverage() = " + intSummaryStatistics.getAverage());
--- 결과 ---
intSummaryStatistics.getSum() = 55
intSummaryStatistics.getMax() = 10
intSummaryStatistics.getMax() = 10
intSummaryStatistics.getAverage() = 5.5
평탄화 - flatMap()
Stream<T[]>를 Stream<T>로 변환한다. 스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루는 것이 더 편리할 때가 있다. 그럴 때는 map()대신 flatMap()을 사용하면 된다.
Stream<List<Integer>> listStream = Stream.of(List.of(1, 2, 3, 4, 5), List.of(6, 7, 8, 9, 10));
Stream<Integer> integerStream = listStream.flatMap(x -> x.stream());
integerStream.forEach(x -> System.out.println("x = " + x));
--- 결과 ---
x = 1
x = 2
x = 3
x = 4
x = 5
x = 6
x = 7
x = 8
x = 9
x = 10
위 코드와 같이 Stream<List<Integer>> -> Stream<Integer> 로 변환 할 수 있다.
References
Java의 정석, 남궁 성 지음
'Java' 카테고리의 다른 글
| Stream - 최종연산 (0) | 2023.09.22 |
|---|---|
| Stream (0) | 2023.09.20 |
| 함수형 인터페이스 (0) | 2023.09.13 |
| 람다식 (0) | 2023.09.12 |
| 날짜와 시간 - parse (0) | 2023.09.11 |