ㆍ Optional<T>

 

T 타입 객체의 래퍼클래스 - Optional<T>

public final class Optional<T> {
	private final T value;
    	...
}

Optional<T>는 제네릭 클래스로, 'T타입의 객체'를 감싸는 래퍼 클래스이다.

=> 모든 종류의 객체를 저장가능. null 포함.

 

1. null을 직접 다루는 것은 위험하다(NullPointerException이 발생함)

2. null 체크를 하려면 if문이 필수(null인지 아닌지 판별), if문을 계속 쓰게 된다면 코드가 지저분해짐.

 

그래서 null을 간접적으로 다루기 위한 클래스가 Optional<T> 이다.

 

 

Optional<T> 객체 생성하기

 

  Optional<T> 객체를 생성하는 다양한 방법

String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal2 = Optional.of("abc");
Optional<String> optVal3 = Optional.of(null); // Null을 직접 넣는건 안 됨.NullPointerException 발생
Optional<String> optVal4 = Optional.ofNullable(null); // OK

(Optional<T> 객체를 사용하게 되면 이런식으로 한 번 주소를 더 거쳐서 객체를 가리키게 됨)

 

  null 대신 빈 Optional<T>객체를 사용하자

Optional<String> optVal = Optional.<String>empty();	 // 빈 객체로 초기화, 뒤의 <String>은 생략가능
Optional<String> optVal = Optional.empty(); // OK

  Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()

String str1 = optVal.get();	// optVal에 저장된 값을 반환. null이면 예외발생
String str2 = optVal.orElse("");	// optVal에 저장된 값이 null일 때는, ""를 반환
String str3 = optVal.orElseGet(String::new); // 람다식 사용가능 ()-> new String()
String str4 = optVal.orElseThrow(NullPointerException::new); // 널이면 예외발생

  isPresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환

if(Optional.ofNullable(str).isPresent() { // if(str!=null) {
	System.out.println(str);
}

ifPresent()를 이용해서 더 간단히 할 수 있다.

Optional.ofNullable(str).ifPresent(System.out::println);

=> str 이 null이 아닐때만 값을 출력한다.

 

OptionalInt, OptionalLong, OptionalDouble

 

  기본형 값을 감싸는 래퍼클래스

public final class OptionalInt {
	...
    private final boolean isPresent;	// 값이 저장되어 있으면 true
    private final int value;	// int 타입의 변수

 

  OptionalInt의 값 가져오기 - int getAsInt()

  빈 Optional객체와의 비교

OptionalInt opt = OptionalInt.of(0);	// OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty();	// OptionalInt에 0을 저장
System.out.println(opt.isPresent());	// true
System.out.println(opt2.isPresent());	// false
System.out.println(opt.equals(opt2));	// false

=> isPresent 메소드를 쓰면 실제로 값이 있는지 없는지 확인할 수 있다.

equals의 결과를 보면, 단순히 값만 비교하는게 아닌 isPresent의 값도 반영하도록 오버라이딩 된 것을 알 수 있다.

 

 

ex)

import java.util.Optional;
import java.util.OptionalInt;

public class Ex14_8 {

	public static void main(String[] args) {
		Optional<String> optStr = Optional.of("abcde");
		Optional<Integer> optInt = optStr.map(String::length);
		System.out.println("optStr="+optStr.get());
		System.out.println("optInt="+optInt.get());
		
		int result1 = Optional.of("123")
						.filter(x->x.length() >0 )
						.map(Integer::parseInt).get();
		
		int result2 = Optional.of("")
						.filter(x->x.length() >0 )
						.map(Integer::parseInt).orElse(-1);
		
		System.out.println("result1="+result1);
		System.out.println("result2="+result2);
		
		Optional.of("456").map(Integer::parseInt)
						.ifPresent(x->System.out.printf("result3=%d%n", x));
		
		OptionalInt optInt1 = OptionalInt.of(0);	// 0을 저장
		OptionalInt optInt2 = OptionalInt.empty();	// 빈 객체를 생성
		
		System.out.println(optInt1.isPresent());	// true
		System.out.println(optInt2.isPresent());	// false
		
		System.out.println(optInt1.getAsInt());	// 0
//		System.out.println(optInt2.getAsInt());	// NoSuchElementException
		System.out.println("optInt1="+optInt1);
		System.out.println("optInt2="+optInt2);
		System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2));
		
		
	}

}

 

ㆍ 스트림의 최종연산 - forEach()

 

▶  스트림의 모든 요소에 지정된 작업을 수행 - forEach(), forEachOrdered()

void forEach(Consumer<? super T> action)	// 병렬스트림인 경우 순서가 보장되지 않음
void forEachOrdered(Consumer<? super T> action) // 병렬스트림인 경우에도 순서가 보장됨
IntStream.range(1, 10).sequential().forEach(System.out::print);	// 123456789
IntStream.range(1, 10).sequential().forEachOrdered(System.out::print); // 123456789

* sequantial() : 직렬스트림, 기본적으로 sequential()이 없어도 직렬스트림으로 취급함

IntStream.range(1, 10).parallel().forEach(System.out::print);	// 682395714 (섞여서나옴)
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print); // 123456789

* parallel() : 병렬스트림

 

 

▶  조건 검사 - allMatch(), anyMatch(), noneMatch()

boolean allMatch (Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키면 true
boolean anyMatch (Predicate<? super T> predicate) // 한 요소라도 조건을 만족시키면 true
boolean noneMatch (Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키지 않으면 true
boolean hasFailedStu = stuStream.anyMatch (s->s.getTotalScore()<=100); // 낙제자가 있는지?

 

▶  조건에 일치하는 요소 찾기 - findFirst(), findAny()

Optional<T> findFirst()	// 첫 번째 요소를 반환. 순차 스트림에 사용
Optional<T> findAny()	// 아무거나 하나를 반환. 병렬 스트림에 사용

* 결과가 null일 수 있으므로 Optional<T>로 받음

Optional<Student> result = stuStream.filter(s->s.getTotalScore() <= 100).findFirst();
Optional<Student> result = parallelStream.filter(s->s.getTotalScore() <= 100).findAny();

* 주로 filter와 같이 사용

 

 

reduce()

 

▶  스트림의 요소를 하나씩 줄여가며 누적연산 수행 - reduce()

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)

Optional<T> reduce(BinaryOperator<T> accumulator) 의 경우,

초기값이 없는 경우 reduce의 결과가 null이 될 수 있으므로 Optional<T>로 결과값을 받는다.

 

* identity - 초기값

* accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산

combiner - 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림)

 

// int reduce(int identity, IntBinaryOperator op)
int count = intStream.reduce(0, (a,b) -> a+1);	// count()
int sum = intStream.reduce(0, (a,b) -> a+b);	// sum()
int max = intStream.reduce(Integer.MIN_VALUE, (a,b)->a>b ? a:b); // max()
int min = intStream.reduce(Integer.MAX_VALUE, (a,b)->a<b ? a:b); // min()

 

* sum()을 향상된 for문으로 표현하면 다음과 같다. 

int a = identity;

for(int b : stream)
	a = a + b; // sum()

즉, 값을 저장할 a의 초기값을 identity로 지정하고

스트림에 있는 값을 하나씩 계속 b에 넣은 후 a에 더해줘서 a의 값을 계속 변화시켜준다. 

 

 

ex)

 

import java.util.Optional;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Ex14_9 {

	public static void main(String[] args) {
		String[] strArr = {
				"Inheritance", "Java", "Lambda", "stream",
				"OptionalDouble", "IntStream", "count", "sum"
		};
		
		Stream.of(strArr)
		.forEach(System.out::println);	// parallel()과 forEachOrdered도 사용해볼것
		
		boolean noEmptyStr = Stream.of(strArr).noneMatch(s->s.length()==0);
		Optional<String> sWord = Stream.of(strArr)
									.filter(s->s.charAt(0)=='s').findFirst();
		// parallel()과 findAny()를 섞어서 사용해볼것
		
		System.out.println("noEmptyStr="+noEmptyStr);
		System.out.println("sWord="+sWord.get());
		
		// Stream<String>을 Stream<Integer>으로 변환
//		Stream<Integer> intStream = Stream.of(strArr).map(String::length);
		
		// Stream<String[]>을 IntStream으로 변환. 기본형 스트림.
		IntStream intStream1 = Stream.of(strArr).mapToInt(String::length);
		IntStream intStream2 = Stream.of(strArr).mapToInt(String::length);
		IntStream intStream3 = Stream.of(strArr).mapToInt(String::length);
		IntStream intStream4 = Stream.of(strArr).mapToInt(String::length);
		

		int count = intStream1.reduce(0, (a,b) -> a + 1);
		int sum = intStream2.reduce(0, (a,b) -> a + b);
		
		OptionalInt max = intStream3.reduce(Integer::max);
		OptionalInt min = intStream4.reduce(Integer::min);
		System.out.println("count="+count);
		System.out.println("sum="+sum);
		System.out.println("max="+max.getAsInt());
		System.out.println("min="+min.getAsInt());
		
		
	}

}

 

 

ㆍ collect()와 Collectors

 

▶ collect()는 Collector를 매개변수로 하는 스트림의 최종연산

Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로 받음
Object collect(Supplier supplier, BiConsumer accumulator, BiconSumer Combiner) // 잘 안쓰임...

 

Collector 인터페이스는 수집(collect)에 필요한 메소드를 정의해 놓은 인터페이스

public interface Collector<T, A, R> { // T(요소)를 A에 누적한 다음, 결과를 R로 변환해서 반환
	Supplier<A> supplier(); // StringBuilder::new	, 누적할 곳
    BiConsumer<A, T> accumulator();  // (sb, s) -> sb.append(s) , 누적 방법
    BinaryOperator<A> combiner(); // (sb1, sb2) -> sb1.append(sb2) , 결합방법(병렬)
    Function<A, R> finisher();	// sb -> sb.toString() , 최종변환
    Set<Characteristics> characteristics();  // 컬렉터의 특성이 담긴 Set을 반환
    ...
    }

* supplier()와 accumulator()가 핵심 , 

 

Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공

변환 - mapping(), toList(), toSet(), toMap(), toCollection(), ...
통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt(), ...
문자열 결합 - joining()
리듀싱 - reducing()
그룹화와 분업 - groupingBy(), partitioningBy(), collectingAndThen()

 

스트림을 컬렉션, 배열로 변환

 

스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()

List<String> names = stuStream.map(Student::getName) // Stream<Student>->Stream<String>
			.collect(Collectors.toList()); // Stream<String>->List<String>
ArrayList<String> list = names.stream()
			.collect(Collectors.toCollection(ArraysList::new)); // Stream<String>->ArrayList<String>

Map<String,Person> map = personStream
	.collect(Collectors.toMap(p->p.getRegId(), p->p)); // Stream<Person>->Map<String,Person>

 

스트림을 배열로 변환 - toArray()

Student[] stuNames = studentStream.toArray(Student[]::new); // OK
Student[] stuNames = studentStream.toArray(); // 에러
Object[] stuNames = studentStream.toArray(); // OK. toArray()의 반환타입은 Object[]

 

스트림의 통계 - counting(), summingInt()

 

▶ 스트림의 통계정보 제공 - counting(), summingInt(), maxBy(), minBy(), ...

long count = stuStream.count(); // 스트림 전체를 counting 함
long count = stuStream.collect(Collectors.counting()); // 스트림에서 그룹을 나눠서 카운팅 할 수 있음
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); // IntStream의 sum()
long totalScore = stuStream.collect(summingInt(Student::getTotalScore)); // 그룹별 sum이 가능
OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max();
Optional<Student> topStudent = stuStream
					.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Student> topStudent = stuStream
			.collect(maxBy(Comparator.comparingInt(Student::getTotalScore)); // 그룹별로 최대값 구할 수 있음

 

 

 스트림을 리듀싱 - reducing() 

reduce와 기능은 같지만 이것 역시 그룹별로 reducing이 가능함

Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
Collector reducing(U identity, Function<T,U> mapper, BinaryOperator<U> op) // map+reduce
IntStream intStream = new Random().ints(1,46).distinct().limit(6);

OptionalInt max = intStream.reduce(Integer::max); // 전체 리듀싱
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max)); // 그룹별 리듀싱 가능
long sum = intStream.reduce(0, (a,b) -> a + b); // (초기값, 누적작업)
long sum = intStream.boxed().collect(reducing(0, (a,b)->a+b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum)); // map+reduce

 

▶ 문자열 스트림의 요소를 모두 연결 - joining()

String studentNames = stuStream.map(Student::getName).collect(Collectors.joining());
String studentNames = stuStream.map(Student::getName).collect(Collectors.joining(",")); // 구분자
String studentNames = stuStream.map(Student::getName).collect(Collectors.joining(",", "[", "]"));
String studentInfo = stuStream.collect(joining(",")); // Student의 toString()으로 결합

 


ㆍ 스트림의 그룹화와 분할

* 그룹화와 분할의 결과는 Map에 담겨 반환된다

 

▶  partitioningBy()는 스트림의 요소를 2분할한다.

Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
Map<Boolean, List<Student>> stuBySex = stuStream
				.collect(partitioningBy(Student::isMale)); // 학생들을 성별로 분할
List<Student> maleStudent = stuBySex.get(true); // Map에서 남학생 목록을 얻는다.
List<Student> femaleStudent = stuBySex.get(false); // Map에서 여학생 목록을 얻는다.
Map<Boolean, Long> stuNumBySex = stuStream
			.collect(partitioningBy(Student::isMale, Collectors.counting())); // 분할 + 통계
System.out.println("남학생 수 :" + stuNumBySex.get(true)); // 남학생 수 : 8
System.out.println("여학생 수 :" + stuNumBySex.get(false)); // 여학생 수 : 10
Map<Boolean, Optional<Student>> topScoreBySex = stuStream // 분할 + 통계
	.collect(partitioningBy(Student::isMale, maxBy(comparingInt(Student::getScore))));
    
System.out.println("남학생 1등 :"+ topScoreBySex.get(true)); // 남학생 1등 : Optional[[나자바,남,1,1,300]]
System.out.println("여학생 1등 :"+ topScoreBySex.get(false)); // 여학생 1등 : Optional[[김지미,여,1,1,250]]
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream	// 다중 분할
	.collect(partitioningBy(Student::isMale, 	// 1. 성별로 분할(남/녀)
    partitioningBy(s ->s.getScore() < 150))); 	// 2. 성적으로 분할(불합격/합격)

List<Student> failedMaleStu = failedStuBySex.get(true).get(true);
List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);

 

▶  groupingBy()는 스트림의 요소를 n분할한다.

Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream // 학생을 반별로 그룹화
		.collect(groupingBy(Student::getBan, toList())); // toList()생략 가능
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream // 다중 그룹화
		.collect(groupingBy(Student::getHak, 	// 1. 학년별 그룹화
        		groupingBy(Student::getBan)		// 2. 반별 그룹화
        ));

 

 

 

ex) partitioningBy 를 활용한 분할

import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;


import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

class Student2 {
	String name;
	boolean isMale; // 성별
	int hak; // 학년
	int ban; // 반
	int score;
	
	Student2 (String name, boolean isMale, int hak, int ban, int score) {
		this.name = name;
		this.isMale = isMale;
		this.hak = hak;
		this.ban = ban;
		this.score = score;
	}
	String getName() { return name; }
	boolean isMale() { return isMale; }
	int getHak() { return hak; }
	int getBan() { return ban; }
	int getScore() { return score; }
	
	public String toString() {
			return String.format("[%s, %s, %d학년 %d반, %3d점]",
					name, isMale ? "남":"여", hak, ban, score);
	}
	// groupingBy() 에서 사용
	enum Level { HIGH, MID, LOW } // 성적을 상, 중, 하 세 단계로 분류
	
}
public class Ex14_10 {

	public static void main(String[] args) {
		Student2[] stuArr = {
				new Student2("나자바", true,  1, 1, 300),	
				new Student2("김지미", false, 1, 1, 250),	
				new Student2("김자바", true,  1, 1, 200),	
				new Student2("이지미", false, 1, 2, 150),	
				new Student2("남자바", true,  1, 2, 100),	
				new Student2("안지미", false, 1, 2,  50),	
				new Student2("황지미", false, 1, 3, 100),	
				new Student2("강지미", false, 1, 3, 150),	
				new Student2("이자바", true,  1, 3, 200),	
				new Student2("나자바", true,  2, 1, 300),	
				new Student2("김지미", false, 2, 1, 250),	
				new Student2("김자바", true,  2, 1, 200),	
				new Student2("이지미", false, 2, 2, 150),	
				new Student2("남자바", true,  2, 2, 100),	
				new Student2("안지미", false, 2, 2,  50),	
				new Student2("황지미", false, 2, 3, 100),	
				new Student2("강지미", false, 2, 3, 150),	
				new Student2("이자바", true,  2, 3, 200)	
			};
		
		System.out.printf("1. 단순분할(성별로 분할)%n");
		Map<Boolean, List<Student2>> stuBySex =  Stream.of(stuArr)
				.collect(partitioningBy(Student2::isMale));
		
		List<Student2> maleStudent = stuBySex.get(true);
		List<Student2> femaleStudent = stuBySex.get(false);
		
		for(Student2 s : maleStudent) System.out.println(s);
		for(Student2 s : femaleStudent) System.out.println(s);
		
		System.out.printf("%n2. 단순분할 + 통계(성별 학생수)%n");
		Map<Boolean, Long> stuNumBySex = Stream.of(stuArr)
				.collect(partitioningBy(Student2::isMale, counting()));
		
		System.out.println("남학생 수 :"+ stuNumBySex.get(true));
		System.out.println("여학생 수 :"+ stuNumBySex.get(false));
		
		System.out.printf("%n3. 단순분할 + 통계(성별 1등)%n");
		Map<Boolean, Optional<Student2>> topScoreBySex = Stream.of(stuArr)
				.collect(partitioningBy(Student2::isMale, 
						maxBy(comparingInt(Student2::getScore))
						));
		System.out.println("남학생 1등 :"+ topScoreBySex.get(true));
		System.out.println("여학생 1등 :"+ topScoreBySex.get(false));
		
		Map<Boolean, Student2> topScoreBySex2 = Stream.of(stuArr)
				.collect(partitioningBy(Student2::isMale,
						collectingAndThen(
								maxBy(comparingInt(Student2::getScore)), Optional::get
						)
				));
		
		System.out.println("남학생 1등 :"+ topScoreBySex2.get(true));
		System.out.println("여학생 1등 :"+ topScoreBySex2.get(false));
		
		System.out.printf("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
		
		Map<Boolean, Map<Boolean, List<Student2>>> failedStuBySex =
				Stream.of(stuArr).collect(partitioningBy(Student2::isMale,
						partitioningBy(s->s.getScore()<=100))
				);
		List<Student2> failedMaleStu = failedStuBySex.get(true).get(true);
		List<Student2> failedFemaleStu = failedStuBySex.get(false).get(true);
		
		for(Student2 s : failedMaleStu) System.out.println(s);
		for(Student2 s : failedFemaleStu) System.out.println(s);
	
	}

}

 

 

ex) groupingBy를 이용한 그룹화

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;

class Student3 {
	String name;
	boolean isMale; // 성별
	int hak;        // 학년
	int ban;        // 반
	int score;

	Student3(String name, boolean isMale, int hak, int ban, int score) { 
		this.name	= name;
		this.isMale	= isMale;
		this.hak   	= hak;
		this.ban	= ban;
		this.score 	= score;
	}

	String	getName() 	 { return name;    }
	boolean isMale()  	 { return isMale;  }
	int		getHak()   	 { return hak;	   }
	int		getBan()  	 { return ban;	   }
	int		getScore()	 { return score;   }

	public String toString() {
		return String.format("[%s, %s, %d학년 %d반, %3d점]", name, isMale ? "남" : "여", hak, ban, score);
	}

	enum Level {
		HIGH, MID, LOW
	}
}

class Ex14_11 {
	public static void main(String[] args) {
		Student3[] stuArr = {
			new Student3("나자바", true,  1, 1, 300),	
			new Student3("김지미", false, 1, 1, 250),	
			new Student3("김자바", true,  1, 1, 200),	
			new Student3("이지미", false, 1, 2, 150),	
			new Student3("남자바", true,  1, 2, 100),	
			new Student3("안지미", false, 1, 2,  50),	
			new Student3("황지미", false, 1, 3, 100),	
			new Student3("강지미", false, 1, 3, 150),	
			new Student3("이자바", true,  1, 3, 200),	
			new Student3("나자바", true,  2, 1, 300),	
			new Student3("김지미", false, 2, 1, 250),	
			new Student3("김자바", true,  2, 1, 200),	
			new Student3("이지미", false, 2, 2, 150),	
			new Student3("남자바", true,  2, 2, 100),	
			new Student3("안지미", false, 2, 2,  50),	
			new Student3("황지미", false, 2, 3, 100),	
			new Student3("강지미", false, 2, 3, 150),	
			new Student3("이자바", true,  2, 3, 200)	
		};

		System.out.printf("1. 단순그룹화(반별로 그룹화)%n");
		Map<Integer, List<Student3>> stuByBan = Stream.of(stuArr)
				.collect(groupingBy(Student3::getBan));

		for(List<Student3> ban : stuByBan.values()) {
			for(Student3 s : ban) {
				System.out.println(s);
			}
		}

		System.out.printf("%n2. 단순그룹화(성적별로 그룹화)%n");
		Map<Student3.Level, List<Student3>> stuByLevel = Stream.of(stuArr)
				.collect(groupingBy(s-> {
						 if(s.getScore() >= 200) return Student3.Level.HIGH;
					else if(s.getScore() >= 100) return Student3.Level.MID;
					else                         return Student3.Level.LOW;
				}));

		TreeSet<Student3.Level> keySet = new TreeSet<>(stuByLevel.keySet());

		for(Student3.Level key : keySet) {
			System.out.println("["+key+"]");

			for(Student3 s : stuByLevel.get(key))
				System.out.println(s);
			System.out.println();
		}

		System.out.printf("%n3. 단순그룹화 + 통계(성적별 학생수)%n");
		Map<Student3.Level, Long> stuCntByLevel = Stream.of(stuArr)
				.collect(groupingBy(s-> {
					     if(s.getScore() >= 200) return Student3.Level.HIGH;
					else if(s.getScore() >= 100) return Student3.Level.MID;
					else                         return Student3.Level.LOW;
				}, counting()));
		for(Student3.Level key : stuCntByLevel.keySet())
			System.out.printf("[%s] - %d명, ", key, stuCntByLevel.get(key));
		System.out.println();

//		for(List<Student3> level : stuByLevel.values()) {
//			System.out.println();
//			for(Student3 s : level) {
//				System.out.println(s);
//			}
//		}

		System.out.printf("%n4. 다중그룹화(학년별, 반별)");
		Map<Integer, Map<Integer, List<Student3>>> stuByHakAndBan =
          Stream.of(stuArr)
				.collect(groupingBy(Student3::getHak,
						 groupingBy(Student3::getBan)
				));

		for(Map<Integer, List<Student3>> hak : stuByHakAndBan.values()) {
			for(List<Student3> ban : hak.values()) {
				System.out.println();
				for(Student3 s : ban)
					System.out.println(s);
			}
		}

		System.out.printf("%n5. 다중그룹화 + 통계(학년별, 반별 1등)%n");
		Map<Integer, Map<Integer, Student3>> topStuByHakAndBan =
          Stream.of(stuArr)
				.collect(groupingBy(Student3::getHak,
						 groupingBy(Student3::getBan,
						     collectingAndThen(
						         maxBy(comparingInt(Student3::getScore))
						         , Optional::get
						     )
						 )
				));

		for(Map<Integer, Student3> ban : topStuByHakAndBan.values())
			for(Student3 s : ban.values())
				System.out.println(s);

		System.out.printf("%n6. 다중그룹화 + 통계(학년별, 반별 성적그룹)%n");
		Map<String, Set<Student3.Level>> stuByScoreGroup = Stream.of(stuArr)
			.collect(groupingBy(s-> s.getHak() + "-" + s.getBan(),
					mapping(s-> {
						 if(s.getScore() >= 200) return Student3.Level.HIGH;
					else if(s.getScore() >= 100) return Student3.Level.MID;
						 else                    return Student3.Level.LOW;
					} , toSet())
			));

		Set<String> keySet2 = stuByScoreGroup.keySet();

		for(String key : keySet2) {
			System.out.println("["+key+"]" + stuByScoreGroup.get(key));
		}
	}  // main의 끝
}

 

 

 

 

표) 스트림의 변환


출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts