학습목표
· 스트림이란?
· 스트림 사용방법
· 스트림의 특징
· 스트림의 종류
· 스트림 구현 객체를 얻기 위한 메소드
- 컬렉션에서 스트림 얻기
- 배열에서 스트림 얻기
- 숫자 범위에서 스트림 얻기
- 파일에서 스트림 얻기
- 디렉토리에서 스트림 얻기
· 스트림 파이프라인
· 중간 처리 메소드와 최종 처리 메소드 종류
- 필터링: distinct(), filter()
- 매핑: flatXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()
- 정렬: sorted()
- 루핑: peek(), forEach()
- 매칭: allMatch, anyMatch, noneMatch()
- 기본 집계: sum(), count(), average(), max(), min()
- 커스텀 집계: reduce()
- 수집: collect()
스트림이란?
· 배열과 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자
· 람다식: 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수
스트림 사용방법
· 자바 8 이후 스트림을 통한 배열, 컬렉션의 순차적 처리: 람다식 이용
// java.util.Collection의 stream() 메소드로 스트림 객체를 얻고,
// stream.forEach 메소드를 통해 컬렉션 요소를 하나씩 콘솔에 출력
List<String> list = Arrays.asList("tester1","tester2","tester3");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));
· 자바 7 이전의 배열, 컬렉션의 순차적 처리: Iterator 반복자 이용
List<String> list = Arrays.asList("tester1","tester2","tester3");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String name = iterator.next();
System.out.println(name);
}
스트림의 특징
· 람다식으로 요소 처리 코드를 제공
· Stream이 제공하는 대부분의 요소 처리 메소드는 함수형 인터페이스 매개 타입을 가지므로, 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 매개값으로 전달
public class LamdaExpressionExam {
public static void main(String[] args){
List<String> vegetables = Arrays.asList("broccoli","tomato","cabbage");
Stream<String> stream = vegetables.stream();
stream.forEach(s -> {
System.out.println(s);
});
}
}
· 내부 반복자를 사용하므로 병렬 처리가 쉬움
· 내부 반복자(internal iterator): 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공
· 외부 반복자(external iterator): 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져옴
ex) index를 이용하는 for문, Iterator를 이용하는 while문
· 내부 반복자의 장점:
1. 반복은 컬렉션에 맡겨두고, 요소 처리 코드에만 집중할 수 있음 (할 일이 줄어듦)
2. 코드가 간결해짐
3. 요소의 병렬 처리가 컬렉션 내부에서 처리
· 병렬 처리: 한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것
▶ 예시 - 병렬 처리와 순차 처리 비교
public class ParallelExam {
public static void main(String args[]){
List<String> vegetables = Arrays.asList("broccoli","tomato","cabbage","eggplant");
//순차 처리
Stream<String> stream = vegetables.stream();
stream.forEach(ParallelExam :: print); //메서드참조
//병렬 처리
Stream<String> parallelStream = vegetables.parallelStream();
parallelStream.forEach(ParallelExam::print);
}
public static void print(String str){
System.out.println(str+ " : " + Thread.currentThread().getName());
}
}
· 중간 처리와 최종 처리를 할 수 있음
- 중간 처리: 매핑, 필터링, 정렬
- 최종 처리: 반복, 카운팅, 평균, 총합 등 집계 처리
▶ 예시 - 야채 무게를 가져와서(중간 처리) 평균값을 산출(최종 처리)
public class MapAndReduceExam {
public static void main(String args[]){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",0.3),
new Vegetable("tomato",0.2),
new Vegetable("cabbage",2.0),
new Vegetable("eggplant",0.5)
);
//순차 처리
Stream<Vegetable> stream = vegetables.stream();
double average = stream
.mapToDouble(Vegetable :: getKg) //중간처리: 야채 객체를 무게로 매핑
.average() //최종 처리(평균 점수)
.getAsDouble();
System.out.println(average);
}
static class Vegetable{
String name;
double kg;
Vegetable(String name, double kg){
this.name = name;
this.kg = kg;
}
public String getName() {
return name;
}
public double getKg() {
return kg;
}
}
}
스트림의 종류
· java.util.stream 패키지에는 스트림 API들이 존재하며, BaseStream 인터페이스를 부모로 다양한 자식 인터페이스가 존재함
· BaseStream: 모든 스트림에서 사용할 수 있는 공통 메소드 정의
· Stream: 객체 요소를 처리하는 스트림
· InteStream, LongStream, DoubleStream: int, long, double 기본 타입 요소를 처리하는 스트림
스트림 구현 객체를 얻기 위한 메소드
리턴 타입 | 메소드(매개 변수) | 소스 |
Stream<T> | java.util.Collection.stream() java.util.Collection.parallelStream() |
컬렉션 |
Stream<T> IntStream LongStream DoubleStream |
Arrays.stream(T[]), Stream.of(T[]) Arrays.stream(int []), IntStream.of(int[]) Arrays.stream(long[]), LongStream.of(long[]) Arrays.stream(double[]), DoubleStream.of(double[]) |
배열 |
IntStream | IntStream.range(int, int) IntStream.rangeClosed(int, int) |
int 범위 |
LongStream | LongStream.range(long, long) LongStream.rangeClose(long, long) |
long 범위 |
Stream<Path> | Files.find(Path, int, BiPredicate, FileVisitOption) Files.list(Path) |
디렉토리 |
Stream<String> | Files.line(Path, charset) BufferedReader.lines() |
파일 |
DoubleStream IntStream LongStream |
Random.doubles() Random.ints() Random.longs() |
랜덤 수 |
컬렉션에서 스트림 얻기
List<String> list = Arrays.asList("tester1","tester2","tester3");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));
배열에서 스트림 얻기
String[] vegetables = {"broccoli","tomato","cabbage"};
Stream<String> stream = Arrays.stream(vegetables);
stream.forEach(s-> System.out.println(s))
숫자 범위에서 스트림 얻기
IntStream intStream = IntStream.rangeClosed(1,100);
intStream.forEach(i -> System.out.println(i)); //1부터 100까지 출력
파일에서 스트림 얻기
· Files와 BufferedReader의 lines() 메소드를 이용하여 문자 파일의 내용을 스트림을 통해 행 단위로 읽고 콘솔에 출력
Path path = Paths.get("/Users/ted.sc/Desktop/java8.txt");
Stream<String> stream;
//1. Filses.lines() 메소드 이용
stream = Files.lines(path, Charset.defaultCharset()); //운영체제의 기본 문자셋
stream.forEach(System.out::println); //메소드 참조, (s->System.out.println(s))과 동일
//2. BufferedReader의 lines() 메소드 이용
File file = path.toFile();
FileReader fileReader = new FileReader(file);
BufferedReader br = new BufferedReader(fileReader);
stream = br.lines();
stream.forEach(System.out::println);
디렉토리에서 스트림 얻기
Path path = Paths.get("/Users/ted.sc/Desktop");
Stream<Path> stream = Files.list(path); //서브 디렉토리, 파일 목록을 스트림에 입력
stream.forEach(System.out::println);
스트림 파이프라인
· 파이프라인: 여러 개의 스트림이 연결되어 있는 구조
· 리덕션(Reduction): 대량의 데이터를 가공해서 축소하는 것 ex) 합계, 평균값 ,카운팅, 최대값, 최소값
· 컬렉션의 요소를 리덕션의 결과물로 바로 집계 할 수 없을 경우, 집계하기 용이하도록 중간 처리(필터링,매핑 정렬, 그룹핑 등)가 필요
· 스트림은 중간 처리와 최종 처리(리덕션)를 파이프라인으로 처리
· 중간 스트림이 생성될 때 바로 중간 처리 되지 않고, 최종 처리가 시작되기 전까지 중간 처리는 지연(lazy)됨
· 최종 처리가 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 처리되어 최종 처리까지 오게됨
· 파이프라인에서 최종처리를 제외하고는 모두 중간 처리 스트림
· Stream 인터페이스에는 다양한 중간 처리 메소드(필터링, 매핑, 정렬 등)가 존재하고, 이 메소드들은 중간 처리된 스트림을 리턴함
· 리턴된 중간 처리된 스트림에서 다시 중간 처리 메소드를 호출하여 파이프라인 형성
▶ 예시 - 회원 컬렉션에서 남자 필터링 중간 스트림 + 나이 매핑 중간 스트림 연결 + 평균 나이 집계
public class PipeLineExam {
public static void main(String[] args){
Member[] members = {new Member(20, "남"),
new Member(20, "여"),
new Member(30, "남"),
new Member(30, "여"),
new Member(40, "남")};
//로컬 변수 생략x
Stream<Member> memberStream = Arrays.stream(members);
Stream<Member> maleStream = memberStream.filter(m->m.getSex().equals("남"));
IntStream maleAgeStream = maleStream.mapToInt(Member::getAge);
double ageAvg = maleAgeStream.average().getAsDouble();
System.out.println("나이:"+ageAvg);
//로컬 변수 생략o
double ageAvg2 = Arrays.stream(members) // 오리지널 스트림
.filter(m->m.getSex().equals("남")) // 중간 처리 스트림
.mapToInt(Member::getAge) // 중간 처리 스트림
.average() // 중간 처리 스트림
.getAsDouble(); // 최종 처리
System.out.println("나이:"+ageAvg2);
}
static class Member{
private int age;
private String sex;
public Member(int age, String sex) {
this.age = age;
this.sex = sex;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
}
}
중간 처리 메소드와 최종 처리 메소드 종류
· 중간 처리 메소드와 최종 처리 메소드는 리턴 타입으로 쉽게 구분
- 중간 처리 메소드의 리턴 타입: 스트림
- 최종 처리 메소드의 리턴 타입: 기본 타입 또는 OptionalXXX
· 위 표의 소속된 인터페이스의 공통의 의미는 Stream, IntStream, LongStream, DoubleStream에서 모두 제공된다는 뜻
필터링
· 요소를 걸러내는 중간 처리 기능
· 모든 스트림이 가지고 있는 공통 메소드
· 스트림에서 제공하는 필터링 메소드: distinct(), filter()
리턴타임 | 메서드(매개변수) | 설명 |
Stream IntStream LongStream DoubleStream |
distinct() | 중복 제거 |
filter(Predicate) | 조건 필터링 | |
filter(IntPredicate) | ||
filter(LongPredicate) | ||
filter(DoublePredicate) |
distinct()
· 중복을 제거
· Stream의 경우 Object.equals(Object)가 true이면 중복 제거하고 IntStream, LongStream, DoubleStream의 경우 동일값이면 중복 제거
▶ 예시 - 중복된 야채 이름을 제거
List<String> vegetables = Arrays.asList("가지","무","배추","가지","가지","애호박");
vegetables.stream()
.distinct()
.forEach(System.out::println);
filter()
· 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링
▶ 예시 - 야채 이름에 ‘호박’이 포함된 것만 필터링
List<String> vegetables = Arrays.asList("가지","무","배추","단호박","늙은호박","애호박");
vegetables.stream()
.distinct() // 중복제거
.filter(s->s.contains("호박")) //필터링
.forEach(System.out::println);
매핑
· 스트림의 요소를 다른 요소로 대체하는 중간 처리 기능
· 스트림에서 제공하는 매핑 메소드: flatXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()
flatMapXXX()
· 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴
리턴타입 | 메소드(매개변수) | 요소 -> 대체 요소 |
Stream<R> | flatMap(Function<T, Stream<R>>) | T -> Stream<R> |
DoubleStream | flatMap(DoubleFunction<DoubleStream>) | double -> DoubleStream |
IntStream | flatMap(IntFunction<IntStream>) | int -> IntStream |
LongStream | flatMap(LongFunction<LongStream>) | long -> longStream |
DoubleStream | flatMapToDouble(Function<T, DoubleStream>) | T -> DoubleStream |
IntStream | flatMapToInt(Function<T, IntStream>) | T -> IntStream |
LongStream | flatMapToLong(Function<T, LongStream>) | T -> LongStream |
▶ 예시 - 리스트에 저장된 요소들을 나누어 새로운 스트림을 생성
//문장을 어절로 나누기
List<String> line = Arrays.asList("애호박 무침은 고소하다","노각 무침은 아삭하다");
line.stream()
.flatMap(data -> Arrays.stream(data.split(" ")))
.forEach(System.out::println);
//문장을 숫자로 나누기
List<String> line2 = Arrays.asList("10,20,30","40,50,60");
line2.stream()
.flatMapToInt(data -> {
String[] strArr = data.split(",");
int[] numbers = new int[strArr.length];
for(int i=0;i<strArr.length;i++)
numbers[i] = Integer.parseInt(strArr[i]);
return Arrays.stream(numbers);
}).forEach(System.out::println);
mapXXX()
· 요소를 대체하는 요소로 구성된 새로운 스트림 리턴
리턴타입 | 메소드(매개변수) | 요소 -> 대체 요소 |
Stream<R> | map(Function<T,R>) | T->R |
DoubleStream | mapToDouble(ToDoubleFunction<T>) | T->double |
IntStream | mapToInt(ToIntFunction<T>) | T->int |
LongStream | mapToLong(ToLongFunction) | T->long |
DoubleStream | map(DoubleUnaryOperator) | double->double |
IntStream | mapToInt(DoubleToIntFunction) | double->int |
LongStream | mapToLong(DoubleTOLongFunction) | double->long |
Stream<U> | mapToObj(DoubleFunction<U>) | double->U |
IntStream | map(IntUnaryOperator mapper) | int->int |
DoubleStream | mapToDouble(IntToDoubleFunction) | int->double |
LongStream | mapToLong(IntToLongFunction mapper) | int->long |
Stream<U> | mapToObj(IntFunction<U>) | int->U |
LongStream | map(LongUnaryOperator) | long->long |
DoubleStream | mapToDouble(LongToDoubleFunction) | long->double |
IntStream | mapToInt(LongToIntFunction) | long->int |
Stream<U> | mapToObj(LongFunction<U>) | long->U |
▶ 예시 - 문장을 숫자로 나누기
public class MapDemo {
public static void main(String[] args){
List<String> line = Arrays.asList("10,20,30","40,50,60");
line.stream()
.flatMap(data-> {
String[] strArr = data.split(",");
return Arrays.stream(strArr);
}) // 모든 문장을 ','을 기준으로 나눈 새로운 스트림 생성
.mapToInt(Integer::parseInt) //스트림 각 요소를 Integer로 변경한 새로운 스트림 생성
.forEach(System.out::println);
}
}
▶ 예시 - 문자를 대문자로 변경
List<String> list =
Arrays.asList("a1", "a2", "b1", "b2", "c2", "c1", "c3");
Stream<String> stream2 = list.stream();
stream2.map(s -> s.toUpperCase()).forEach(System.out::println);
asDoubleStream(), asLongStream(), boxed()
리턴타입 | 메소드(매개변수) | 설명 |
DoubleStream | asDoubleStream() | int -> double, long -> double |
LongStream | asLongStream() | int -> long |
Stream<Integer> Stream<Long> Stream<Double> |
boxed() | int -> Integer long -> Long double -> Double |
· asDoubleStream() 메소드는 IntStream의 int 요소 또는 LongStream의 long 요소를 double 요소로 타입 변환하여 DoubleStream 생성
· asLongStream() 메소드는 IntStream의 int 요소를 long 요소로 타입 변환하여 LongStream 생성
· boxed() 메소드는 int, long, double 요소를 Integer, Long, Double 요소로 박싱하여 Stream을 생성
▶ 예시 - 다른 요소로 대체
public class AsDoubleStreamAndBoxedDemo {
public static void main(String[] args){
int[] intArray = {1,2,3,4,5};
IntStream intStream = Arrays.stream(intArray);
intStream.asDoubleStream() //DoubleStream생성
.forEach(System.out::println);
intStream = Arrays.stream(intArray);
intStream.boxed() //Stream<Integer> 생성
.forEach(obj-> System.out.println(obj.intValue()));
}
}
정렬
· 요소가 최종 처리되기 전 중간 단계에서 요소를 정렬하여 최종 처리 순서를 변경하는 기능
· 스트림에서 제공하는 매핑 메소드: sorted()
객체 요소일 경우 클래스가 Comparable을 구현하지 않으면 sorted() 메소드를 호출했을 때 ClassCastException이 발생
리턴타입 | 메소드(매개변수) | 설명 |
Stream<T> | sorted() | 객체를 Comarable 구현 방법에 따라 정렬 |
Stream<T> | sorted(Comparator<T>) | 객체를 주어진 Comparator에 따라 정렬 |
DoubleStream | sorted() | double 요소를 오름차순으로 정렬 |
IntStream | sorted() | int 요소를 오름차순으로 정렬 |
LongStream | sorted() | long 요소를 오름차순으로 정렬 |
▶ 예시 - 야채 객체의 무게를 오름차순으로 정렬
public class SortedExam {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",0.3),
new Vegetable("tomato",0.2),
new Vegetable("eggplant",0.5)
);
Stream<Vegetable> stream = vegetables.stream();
//정렬
stream.sorted().forEach(v -> System.out.println(v.name + ":" + v.kg));
}
static class Vegetable implements Comparable<Vegetable>{
private String name;
private double kg;
Vegetable(String name, double kg){
this.name = name;
this.kg = kg;
}
@Override
public int compareTo(Vegetable o) {
return Double.compare(kg, o.kg);
// score < o.score 음수 리턴
// score == o.score 0 리턴
// score > o.score 양수 리턴
}
}
}
· 객체 요소가 Comparable을 구현한 상태에서 기본 비교(Comparable) 방법으로 정렬하고 싶다면 다음 중 하나를 호출
sorted();
sorted((a,b)->a.compareTo(b));
sorted(Comparator.naturalOrder());
stream.sorted().forEach(v -> System.out.println(v.name + ":" + v.kg));
stream.sorted((a,b)->a.compareTo(b)).forEach(v -> System.out.println(v.name + ":" + v.kg));
stream.sorted(Comparator.naturalOrder()).forEach(v -> System.out.println(v.name + ":" + v.kg));
· 객체 요소가 Comparable을 구현하지만, 정반대로 정렬하고 싶다면 다음 중 하나를 호출
sorted( (a,b) -> b.compareTo(a) );
sorted( Comparator.reverseOrder() );
vegetables.stream().sorted(Comparator.reverseOrder()).forEach(v -> System.out.println(v.name + ":" + v.kg));
vegetables.stream().sorted((a,b)->b.compareTo(a)).forEach(v -> System.out.println(v.name + ":" + v.kg));
· 객체 요소가 Comparable을 구현하지 않았다면 Comparator를 매개값으로 갖는 sorted() 메소드를 사용
- Comparator는 함수형 인터페이스이므로 람다식으로 매개값 작성 가능
sorted((a,b) -> {})
public class SortedDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",0.3),
new Vegetable("tomato",0.2),
new Vegetable("eggplant",0.5)
);
//정렬
vegetables.stream().sorted((a,b) -> {
return a.kg.compareTo(b.kg);
}).forEach(v -> System.out.println(v.name + ":" + v.kg));
}
static class Vegetable{
private String name;
private Double kg;
Vegetable(String name, double kg){
this.name = name;
this.kg = kg;
}
}
}
▶ 예시 - int 기본 자료형 배열을 정렬
//배열 생성
int[] intArr = {1,2,3,4,5};
//오름차순 정렬
Arrays.stream(intArr).sorted().forEach(System.out::println);
//내림차순 정렬, boxed()를 통해 IntStream -> Stream<Integer>로 형변환 필요
Arrays.stream(intArr).boxed().sorted(Comparator.reverseOrder()).forEach(System.out::println);
· 기본 자료형 스트림의 정렬 메서드는 sorted()이므로 내림차순(sorted(Comparator<T>) 활용)을 구현하려면 boxed() 메서드를 통해 XXXStream을 Stream<T>으로 형변환 필요
루핑
· 요소 전체를 반복하는 기능
· 스트림에서 제공하는 매핑 메소드: peek(), forEach()
리턴타입 | 메소드(매개변수) |
Stream<T> | peek(Consumer<? super T> action) |
void | forEach(Consumer<? super T> action) |
peek()
· 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용하는 중간 처리 메소드
· 최종 처리 메소드가 실행하지 않으면 지연되므로 반드시 최종 처리 메소드가 호출되어야 동작
forEach()
· 파이프라인 마지막에 루핑하며 요소를 하나씩 처리하는 최종 처리 메소드
▶ 예시 - 루핑을 통한 요소 출력
int[] intArr = {1,2,3,4,5};
//peek 동작하지 않음
Arrays.stream(intArr)
.filter(a->a%2==0)
.peek(System.out::println);
//peek 동작함
int total = Arrays.stream(intArr)
.filter(a->a%2==0)
.peek(System.out::println)
.sum();
System.out.println("총합:"+total);
//forEach
Arrays.stream(intArr)
.filter(a->a%2==0)
.forEach(System.out::println);
매칭
· 요소들이 특정 조건에 만족하는지 조사하는 최종 처리 메소드
· 스트림에서 제공하는 매칭 메소드: allMatch, anyMatch, noneMatch()
allMatch() - 모든 요소들이 매개값으로 주어진 Predicate 조건을 만족하는지 조사
anyMatch() - 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
noneMatch() - 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사
리턴타입 | 메소드(매개변수) | 제공 인터페이스 |
boolean | allMatch(Predicate predicate), anyMatch(Predicate predicate), noneMatch(Predicate predicate) | Stream |
boolean | allMatch(IntPredicate predicate), anyMatch(IntPredicate predicate), noneMatch(IntPredicate predicate) | IntStream |
boolean | allMatch(LongPredicate predicate), anyMatch(LongPredicate predicate), noneMatch(LongPredicate predicate) | LongStream |
boolean | allMatch(DoublePredicate predicate), anyMatch(DoublePredicate predicate), noneMatch(DoublePredicate predicate) | DoubleStream |
▶ 예시 - 요소들의 배수 조사
int[] arr = {2,4,6};
boolean isTrue = Arrays.stream(arr)
.allMatch(a->a%2==0);
System.out.println("모두 2의 배수인가? " + isTrue);
isTrue = Arrays.stream(arr)
.anyMatch(a->a%3==0);
System.out.println("하나라도 3의 배수가 있는가? " + isTrue);
isTrue = Arrays.stream(arr)
.noneMatch(a->a%3==0);
System.out.println("3의 배수가 없는가? " + isTrue);
기본 집계
· 집계(Aggregate)는 요소를 처리해서 카운팅, 합계, 평균값, 최대값, 최소값과 같이 하나의 값으로 산출하는 것
· 최종 처리 메소드
· 대량의 데이터를 가공해서 출소하는 리덕션(Reduction)으로 볼 수 있음
· 스트림에서 제공하는 기본 집계 메소드: sum(), count(), average(), max(), min()
리턴타입 | 메소드(매개변수) | 설명 |
long | count() | 요소 개수 |
OptionalXXX | findFirst() | 첫 번째 요소 |
Optional<T> | max(Comparator<T>) | 최대 요소 |
OptionalXXX | max() | |
Optional<T> | min(Comparator<T>) | 최소 요소 |
OptionalXXX | min() | |
OptionalDouble | average() | 요소 평균 |
int, long, double | sum() | 요소 총합 |
· Optional 클래스란? -> https://scshim.tistory.com/345
▶ 예시 - 기본 집계 메소드 활용
int[] intArray = new int[] {1,2,3,4,5};
long count = Arrays.stream(intArray).count();
long sum = Arrays.stream(intArray).sum();
Double average = Arrays.stream(intArray).average().getAsDouble();
long max = Arrays.stream(intArray).max().getAsInt();
int first = Arrays.stream(intArray).findFirst().getAsInt();
System.out.println("count:"+count);
System.out.println("sum:"+sum);
System.out.println("average:"+average);
System.out.println("max:"+max);
System.out.println("first:"+first);
커스텀 집계
· 스트림은 기본 집계 메소드 이외에 프로그램화해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드를 제공
· 최종 처리 메소드
인터페이스 | 리턴타입 | 메소드(매개 변수) |
Stream | Optional<T> | reduce(BinaryOperator<T> accumulator) |
T | reduce(T identity, BinaryOperator<T> accumulator) | |
IntStream | OptionalInt | reduce(BinaryOperator op) |
int | reduce(int identity, IntBinaryOperator op) | |
LongStream | OptionalLong | reduce(LongBinaryOperator op) |
long | reduce(long identity, LongBinaryOperator op) | |
DoubleStream | OptionalDouble | reduce(DoubleBinaryOperator op) |
double | reduce(double identity, DoubleBinaryOperator op) |
· 스트림 요소가 전혀 없을 경우 디폴트 값인 identity 매개값이 리턴
▶ 예시 - 야채 무게 더하기
public class ReduceDemo {
public static void main(String[] args){
List<Vegetable> vegetableList = Arrays.asList(
new Vegetable("broccoli",0.3),
new Vegetable("tomato",0.2),
new Vegetable("cabbage",2.0),
new Vegetable("eggplant",0.5)
);
// sum() 이용
Double sum1 = vegetableList.stream()
.mapToDouble(Vegetable::getKg)
.sum();
// reduce(DoubleBinaryOperator op) 이용
Double sum2 = vegetableList.stream()
.map(Vegetable::getKg)
.reduce((a,b)->a+b) //스트림에 요소가 없을 경우 NoSuchElementException 발생
.get();
// reduce(double identity, DoubleBinaryOperator op) 이용
Double sum3 = vegetableList.stream()
.map(Vegetable::getKg)
.reduce(0.0, (a,b)->a+b); //스트림에 요소가 없을 경우 디폴트 값(identity) 리턴
System.out.println(sum1);
System.out.println(sum2);
System.out.println(sum3);
}
static class Vegetable{
String name;
double kg;
Vegetable(String name, double kg){
this.name = name;
this.kg = kg;
}
public double getKg() {
return kg;
}
}
}
수집
· 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드
· 필요한 요소만 컬렉션으로 담거나, 요소들을 그룹핑한 후 집계(리덕션)할 수 있음
· 스트림에서 제공하는 수집 메소드: collect()
· 예시 1) 스트림 collect()를 사용한 List to Map
https://www.baeldung.com/java-list-to-map
· 예시2) 스트림을 사용하여 String 컬렉션을 하나의 String으로 연결하기
List<String> vegetables = new ArrayList<>();
vegetables.add("cucumber");
vegetables.add("carrot");
vegetables.add("eggplant");
String vegetableLine = vegetables.stream()
.collect(Collectors.joining(", "));
System.out.println(vegetableLine);
필터링한 요소 수집
· Stream의 collect(Collector<T,A,R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하여 리턴함
리턴 타입 | 메소드(매개 변수) | 인터페이스 |
R | collect(Collector<T,A,R> collector) | Stream |
· 매개값인 Collector(수집기)는 어떤 요소를 어떤 컬렉션에 수집할 것인지 결정
· Collector의 타입 파타미터 의미: T - 요소, A - 누적기(accumulator) , R - 요소가 저장될 컬렉션
- T 요소를 A 누적기가 R에 저장한다는 의미
· Collector의 구현 객체는 Collectors 클래스의 정적 메소드를 이용해 얻을 수 있음
리턴 타입 | Collectors의 정적 메소드 | 설명 |
Collector<T,?,List<T>> | toList() | T를 List에 저장 |
Collector<T,?,Set<T>> | toSet() | T를 Set에 저장 |
Collector<T,?,Collection<T>> | toCollectio( Supplier<Collection<T>>) |
T를 Supplier가 제공한 Collection에 저장 |
Collector<T,?,Map<K,U>> | toMap( Function<T,K> keyMapper, Function<T,U> valueMapper) |
T를 K와 U로 매핑해서 K를 키로 U를 Map에 저장 |
Collector<T,?, ConcurrentMap<K,U>> |
toConcurrentMap( Function<T,K> keyMapper, Function<T,U> valueMapper) |
T를 K와 U로 매핑해서 K를 키로 U를 ConcurrentMap에 저장 |
· 리턴 타입의 Collector의 A가 ?로 되어 있는 이유: Collector가 R(컬렉션)에 T(요소)에를 저장하는 방법을 알고 있어 A(누적기)가 필요 없기 때문이다.
· ConcurrentMap은 스레드에 안전, Map은 스레드에 안전 x
- 멀티 스레드 환경에서 ConcurrentMap 이용 권장
▶ 예시 - 야채 색상별로 서로 다른 자료구조에 저장하기
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN),
new Vegetable("tomato", Vegetable.Color.RED),
new Vegetable("redPepper",Vegetable.Color.RED),
new Vegetable("cabbage",Vegetable.Color.GREEN),
new Vegetable("eggplant",Vegetable.Color.PURPLE)
);
// 초록색 야채를 List 자료구조에 저장
Stream<Vegetable> greenVegetableStream = vegetables.stream()
.filter(s->s.getColor()==Vegetable.Color.GREEN);
Collector<Vegetable, ?, List<Vegetable>> collector = Collectors.toList();
List <Vegetable> greenVegetables = greenVegetableStream.collect(collector);
// 빨간색 야채를 HashSet 자료구조에 저장
Stream<Vegetable> redVegetableStream = vegetables.stream()
.filter(s->s.getColor()==Vegetable.Color.RED);
Supplier<HashSet<Vegetable>> supplier = HashSet::new;
Collector<Vegetable, ?, HashSet<Vegetable>> collector2 = Collectors.toCollection(supplier);
Set <Vegetable> redVegetables = redVegetableStream.collect(collector2);
greenVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
System.out.println();
redVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
}
public static class Vegetable{
public enum Color {GREEN, RED,PURPLE}
String name;
Color color;
Vegetable(String name, Color color){
this.name = name;
this.color = color;
}
String getName(){
return name;
}
Color getColor(){
return color;
}
}
}
위 코드에서 변수를 생량하여 다음과 같이 간단하게 작성할 수 있다.
public class CollectDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN),
new Vegetable("tomato", Vegetable.Color.RED),
new Vegetable("redPepper",Vegetable.Color.RED),
new Vegetable("cabbage",Vegetable.Color.GREEN),
new Vegetable("eggplant",Vegetable.Color.PURPLE)
);
// 초록색 야채를 List 자료구조에 저장
List <Vegetable> greenVegetables = vegetables.stream()
.filter(s->s.getColor()==Vegetable.Color.GREEN)
.collect(Collectors.toList());
// 빨간색 야채를 HashSet 자료구조에 저장
Set<Vegetable> redVegetables = vegetables.stream()
.filter(s->s.getColor()==Vegetable.Color.RED)
.collect(Collectors.toCollection(HashSet::new));
greenVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
System.out.println();
redVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
}
public static class Vegetable{
public enum Color {GREEN, RED,PURPLE}
String name;
Color color;
Vegetable(String name, Color color){
this.name = name;
this.color = color;
}
String getName(){
return name;
}
Color getColor(){
return color;
}
}
}
사용자 정의 컨테이너에 수집
· List, Set, Map과 같은 컬렉션이 아니라 사용자 정의 컨테이너 객체에 수집하는 방법
· 스트림에서는 필터링, 매핑해서 사용자 정의 컨테이너 객체에 수집할 수 있는 collect() 메소드를 추가적으로 제공
인터페이스 | 리턴 타입 | 메소드(매개 변수) |
Stream | R | Collect(Supplier<R>, BiConsumer<R,? super T>, BiConsumer<R,R>) |
IntStream | R | Collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R,R>) |
LongStream | R | Collect(Supplier<R>, ObjLongConsumer<R>, BiConsumer<R,R>) |
DoubleStream | R | Collect(Supplier<R>, ObjDoubleConsumer<R>, BiConsumer<R,R>) |
· Supplier: 수집될 컨테이너 객체(R)을 생성하는 역할
- 순차 처리(싱글 스레드) 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성
- 병렬 처리(멀티 스레드) 스트림에서는 여러 번 Supplier가 실행되고 스레드별로 여러 개의 컨테이너 객체를 생성
· 두 번째 Consumer: 컨테이너 객체(R)에 요소(T)를 수집하는 역할
- 스트림에서 요소를 컨테이너에 수집할 때마다 실행
· 세 번째 Consumer: 컨테이너 객체(R)를 결합하는 역할
- 병렬 처리 스트림에서 호출되어 스레드별로 생성된 컨테이너 객체를 결합해서 단 하나의 최종 컨테이너 객체를 완성
- 순차 처리 스트림에서는 호출되지 않음
· 리턴 타입 R: 요소들이 최종 수집된 컨테이너 객체
- 순차 처리 스트림에서 리턴 객체: 첫 번째 Supllier가 생성한 객체
- 병렬 처리 스트림에서 리턴 객: 최종 결합된 컨테이너 객체
▶ 예시 - GreenVegetable 사용자 컨테이너에 데이터 수집
public class GreenVegetable {
private List<Vegetable> list; //요소를 저장할 컬렉션
public GreenVegetable(){
list = new ArrayList<Vegetable>();
System.out.println("[" + Thread.currentThread().getName() + "] GreenVegetable()");
}
// 매개값으로 받은 Vegetable을 list 필드에 수집
public void accumulate(Vegetable vegetable){ //요소를 수집하는 메소드
list.add(vegetable);
System.out.println("[" + Thread.currentThread().getName() + "] accumulate()");
}
// 병렬 처리 스트림을 사용할 때 다른 GreenVegetable과 결합할 목적으로 실행
public void combine(GreenVegetable other){
list.addAll(other.getList());
System.out.println("[" + Thread.currentThread().getName() + "] combine()");
}
public List<Vegetable> getList(){ //요소가 저장된 컬렉션 리턴
return list;
}
}
public class UserDefinedContainerCollectDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN),
new Vegetable("tomato", Vegetable.Color.RED),
new Vegetable("redPepper",Vegetable.Color.RED),
new Vegetable("cabbage",Vegetable.Color.GREEN),
new Vegetable("eggplant",Vegetable.Color.PURPLE)
);
Stream<Vegetable> greenVegetableStream = vegetables.stream()
.filter(s->s.getColor() == Vegetable.Color.GREEN);
Supplier<GreenVegetable> supplier = () -> new GreenVegetable(); //GreenVegetable을 공급하는 supplier 얻기
BiConsumer<GreenVegetable, Vegetable> accumulator = (ms, s) -> ms.accumulate(s); //accumulator로 Vegetable을 수집하는 BiConsumer 얻기
//두 개의 Greentable을 매개값으로 받아 combin() 메소드로 결합하는 BiConsumer 얻기. 싱글 스레드에서는 사용 x.
BiConsumer<GreenVegetable, GreenVegetable> combiner = (ms1, ms2)->ms1.combine(ms2);
//GreenVegetable 수집
GreenVegetable greenVegetable = greenVegetableStream.collect(supplier, accumulator, combiner);
//수집한 GreenVegetable 출력
greenVegetable.getList().stream()
.forEach(s->System.out.println(s.getName()));
}
}
위 람다식을 메소드 참조로 변경하면 다음과 같이 간단하게 작성 가능
public class UserDefinedContainerCollectDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN),
new Vegetable("tomato", Vegetable.Color.RED),
new Vegetable("redPepper",Vegetable.Color.RED),
new Vegetable("cabbage",Vegetable.Color.GREEN),
new Vegetable("eggplant",Vegetable.Color.PURPLE)
);
GreenVegetable greenVegetable = vegetables.stream()
.filter(s->s.getColor() == Vegetable.Color.GREEN)
.collect(GreenVegetable :: new, GreenVegetable:: accumulate, GreenVegetable :: combine);
greenVegetable.getList().stream()
.forEach(s->System.out.println(s.getName()));
}
}
요소를 그룹핑해서 수집
· collect() 메소드는 요소를 수집하는 기능 이외에 컬렉션 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공
· 사용법: collect()를 호출할 때 Collectors의 groupingBy() 또는 groupingByConcurrent()가 리턴하는 Collector를 매개값으로 대입
▶ 예시 - 야채를 색상과 크기로 각각 그룹핑
public class Vegetable{
public enum Color {GREEN, RED,PURPLE}
public enum Size {SMALL, MEDIUM, BIG}
private String name;
private Color color;
private Size size;
Vegetable(String name, Color color, Size size){
this.name = name;
this.color = color;
this.size = size;
}
String getName(){
return name;
}
Color getColor(){
return color;
}
Size getSize(){
return size;
}
}
public class GroupingDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN, Vegetable.Size.BIG),
new Vegetable("tomato", Vegetable.Color.RED, Vegetable.Size.MEDIUM),
new Vegetable("redPepper",Vegetable.Color.RED, Vegetable.Size.SMALL),
new Vegetable("cabbage",Vegetable.Color.GREEN, Vegetable.Size.BIG),
new Vegetable("eggplant",Vegetable.Color.PURPLE, Vegetable.Size.MEDIUM)
);
Function<Vegetable, Vegetable.Color> classifier = Vegetable :: getColor; //Vegetable을 Vegetable.Color로 매핑하는 Function 얻기
//Vegetable.Color가 키, 그룹핑된 List<Vegetable>가 값인 Map을 생성하는 Collector를 얻기
Collector<Vegetable, ?, Map<Vegetable.Color, List<Vegetable>>> collector = Collectors.groupingBy(classifier);
Map<Vegetable.Color, List<Vegetable>> mapByColor = vegetables.stream().collect(collector); //Stream의 collect() 메소드로 Vegetable를 Vegetable.Color별로 그룹핑해서 Map을 얻음
System.out.print("초록색 야채:");
mapByColor.get(Vegetable.Color.GREEN).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
System.out.print("빨간색 야채:");
mapByColor.get(Vegetable.Color.RED).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
System.out.print("보라색 야채:");
mapByColor.get(Vegetable.Color.PURPLE).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
Function<Vegetable, Vegetable.Size> classifier2 = Vegetable :: getSize;
Function<Vegetable, String> mapper = Vegetable::getName;
Collector<String, ?, List<String>> collector2 = Collectors.toList();
Collector<Vegetable, ?, List<String>> collector3 = Collectors.mapping(mapper,collector2);
Collector<Vegetable, ?, Map<Vegetable.Size, List<String>>> collector4 = Collectors.groupingBy(classifier2, collector3);
//collect()의 매개값으로 groupingBy(Function<T, K> classifier, Collector<T, A, D> collector) 사용
Map<Vegetable.Size, List<String>> mapBySize = vegetables.stream().collect(collector4);
System.out.print("큰 야채:");
mapBySize.get(Vegetable.Size.BIG).forEach(s-> System.out.print(s + " "));
System.out.println();
System.out.print("중간 야채:");
mapBySize.get(Vegetable.Size.MEDIUM).forEach(s-> System.out.print(s + " "));
System.out.println();
System.out.print("작은 야채:");
mapBySize.get(Vegetable.Size.SMALL).forEach(s-> System.out.print(s + " "));
System.out.println();
}
}
위 코드의 변수를 생략하면 다음과 같이 간단하게 작성 가능
public class GroupingDemo {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",Vegetable.Color.GREEN, Vegetable.Size.BIG),
new Vegetable("tomato", Vegetable.Color.RED, Vegetable.Size.MEDIUM),
new Vegetable("redPepper",Vegetable.Color.RED, Vegetable.Size.SMALL),
new Vegetable("cabbage",Vegetable.Color.GREEN, Vegetable.Size.BIG),
new Vegetable("eggplant",Vegetable.Color.PURPLE, Vegetable.Size.MEDIUM)
);
Map<Vegetable.Color, List<Vegetable>> mapByColor = vegetables.stream()
.collect(Collectors.groupingBy(Vegetable :: getColor));
System.out.print("초록색 야채:");
mapByColor.get(Vegetable.Color.GREEN).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
System.out.print("빨간색 야채:");
mapByColor.get(Vegetable.Color.RED).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
System.out.print("보라색 야채:");
mapByColor.get(Vegetable.Color.PURPLE).forEach(s-> System.out.print(s.getName() + " "));
System.out.println();
Map<Vegetable.Size, List<String>> mapBySize = vegetables.stream()
.collect(
Collectors.groupingBy(
Vegetable::getSize,
Collectors.mapping(Vegetable::getName, Collectors.toList()))
);
System.out.print("큰 야채:");
mapBySize.get(Vegetable.Size.BIG).forEach(s-> System.out.print(s + " "));
System.out.println();
System.out.print("중간 야채:");
mapBySize.get(Vegetable.Size.MEDIUM).forEach(s-> System.out.print(s + " "));
System.out.println();
System.out.print("작은 야채:");
mapBySize.get(Vegetable.Size.SMALL).forEach(s-> System.out.print(s + " "));
System.out.println();
}
}
그룹핑 후 매핑 및 집계
· Collectors.groupingBy() 메소드는 그룹핑 후 매핑이나 집계(평균, 카운팅, 연결, 최대, 최소, 합계)를 할 수 있도록 두 번째 매개값으로 Collector를 가질 수 있음
· Collectors는 매핑과 집계를 위한 Collector를 리턴하는 다양한 메소드를 제공(아래)
리턴 타입 | 메소드(매개 변수) | 설명 |
Collector<T, ?, R> | mapping(Function<T, U> mapper, Collector<U, A, R> collector> | T를 U로 매핑한 후, U를 R에 수집 |
Collector<T, ?, Double> | averagingDouble(ToDoubleFunction<T> mapper) | T를 Double로 매핑한 후, Double의 평균값을 산출 |
Collector<T, ?, Long> | counting() | T의 카운팅 수를 산출 |
Collector<CharSequence, ?, String> | joining(CharSequence delimiter) | CharSequence를 구분자로 연결한 String을 산출 |
Collector<T, ?, Optional<T>> | maxBy(Comparator<T> comparator) | Comparator를 이용해서 최대 T를 산출 |
Collector<T, ?, Optional<T>> | minBy(Comparator<T> comparator) | Comparator를 이용해서 최소 T를 산출 |
Collector<T, ?, Integer> | summingInt(ToIntFunction) summingLong(ToLongFunction) summingDouble(ToDoubleFunction) |
Int, Long, Double 타입의 합계 산출 |
▶ 예시 - 야채의 색상별 무게의 평균과 이름 얻기
public class Vegetable{
public enum Color {GREEN, RED,PURPLE}
private String name;
private double kg;
private Color color;
Vegetable(String name, double kg, Color color){
this.name = name;
this.kg = kg;
this.color = color;
}
String getName(){
return name;
}
Color getColor(){
return color;
}
double getKg() { return kg; }
}
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class GroupingAndReductionExample {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",0.3,Vegetable.Color.GREEN),
new Vegetable("tomato",0.2, Vegetable.Color.RED),
new Vegetable("redPepper",0.1,Vegetable.Color.RED),
new Vegetable("cabbage",2.0,Vegetable.Color.GREEN),
new Vegetable("eggplant",0.5,Vegetable.Color.PURPLE)
);
//1. 그룹핑 절차 그대로 보여주기
//야채의 색상별 평균 무게를 저장하는 Map 얻기
Function<Vegetable, Vegetable.Color> classifier = Vegetable :: getColor;
ToDoubleFunction<Vegetable> mapper = Vegetable::getKg;
Collector<Vegetable, ?, Double> collector1 = Collectors.averagingDouble(mapper);
Collector<Vegetable, ?, Map<Vegetable.Color, Double>> collector2 = Collectors.groupingBy(classifier, collector1);
Map<Vegetable.Color, Double> mapByColor = vegetables.stream().collect(collector2);
System.out.println("초록색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.GREEN));
System.out.println("빨간색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.RED));
System.out.println("보라색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.PURPLE));
//2. 변수 생략으로 간단하게 표현하기
//야채의 색상을 쉽표로 구분한 이름을 저장하는 Map 얻기
Map<Vegetable.Color, String> mapByName = vegetables.stream()
.collect(
Collectors.groupingBy(
Vegetable :: getColor,
Collectors.mapping(Vegetable :: getName, Collectors.joining(","))
)
);
System.out.println("초록색 야채 전체 이름:" + mapByName.get(Vegetable.Color.GREEN));
System.out.println("빨간색 야채 전체 이름:" + mapByName.get(Vegetable.Color.RED));
System.out.println("보라색 야채 전체 이름:" + mapByName.get(Vegetable.Color.PURPLE));
}
}
위 코드의 변수를 생략하면 다음과 같이 간단하게 작성 가능
public class GroupingAndReductionExample {
public static void main(String[] args){
List<Vegetable> vegetables = Arrays.asList(
new Vegetable("broccoli",0.3,Vegetable.Color.GREEN),
new Vegetable("tomato",0.2, Vegetable.Color.RED),
new Vegetable("redPepper",0.1,Vegetable.Color.RED),
new Vegetable("cabbage",2.0,Vegetable.Color.GREEN),
new Vegetable("eggplant",0.5,Vegetable.Color.PURPLE)
);
//야채의 색상별 평균 무게를 저장하는 Map 얻기
Map<Vegetable.Color, Double> mapByColor = vegetables.stream()
.collect(
Collectors.groupingBy(
Vegetable :: getColor,
Collectors.averagingDouble(Vegetable :: getKg)
)
);
System.out.println("초록색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.GREEN));
System.out.println("빨간색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.RED));
System.out.println("보라색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.PURPLE));
//야채의 색상을 쉽표로 구분한 이름을 저장하는 Map 얻기
Map<Vegetable.Color, String> mapByName = vegetables.stream()
.collect(
Collectors.groupingBy(
Vegetable :: getColor,
Collectors.mapping(Vegetable :: getName, Collectors.joining(","))
)
);
System.out.println("초록색 야채 전체 이름:" + mapByName.get(Vegetable.Color.GREEN));
System.out.println("빨간색 야채 전체 이름:" + mapByName.get(Vegetable.Color.RED));
System.out.println("보라색 야채 전체 이름:" + mapByName.get(Vegetable.Color.PURPLE));
}
}
'자바' 카테고리의 다른 글
[Java] 자바의 Scanner 클래스 사용하기 (0) | 2021.09.10 |
---|---|
[Java] 자바 7의 새로운 기능 (0) | 2021.09.10 |
[Java] 자바의 레코드(Record) (0) | 2021.09.09 |
[Java] 자바의 모듈 시스템(module) (0) | 2021.09.09 |
[Java] 컬렉션 프레임워크(Collection Framework) (0) | 2021.09.05 |
댓글