-
코드의 간결 성, 함수를 이해하는 개인 간의 가독 성 상승
-
Collection 의 요소를 순회하여 Filtering , Mapping 가능
-
Collection 의 요소를 간결한 코드로 순회하여 결과를 만들어 낼 수 있다.
-
@FunctionalInterface , 함수형 인터페이스에서 람다식 사용 허용
-
Anonymous Function , 익명 함수를 만들어내는 방식으로 작성된다.
-
즉, Runtime 시점에 Anonymous Implementation Object 를 생성하는 형태
기본문법
-
람다식 -> 매개변수를 가진 코드블록 -> 익명 구현 객체
-
(매개변수) -> { … }
-
(매개변수) 란은 Lambda 에서 사용될 값을 제공
-
하나의 매개변수를 가진다면 괄호를 생략가능
-
하나의 Statement 로 끝난다면 중괄호 및 return 키워드 생략가능
-
리턴 타입을 명시적으로 표현하기 위해 (Return Type) 을 기입 가능
Predicate predicate = (Predicate) o -> "@java.lang.FunctionalInterface()".equals(Predicate.class.getAnnotations()[0].toString());
//구현 메서드 실행
boolean test = predicate.test(predicate);
System.out.println(test); // $1 => true
@FunctionalInterface
-
람다식을 사용할 때 핵심은 [어떤 함수적 인터페이스를 구현할 것인가]
-
Java 에서 허용된 Lambda 작성 가능 인터페이스가 @FunctionalInterface
-
하나의 구현 메서드만이 정의 되며 JVM 은 하나의 함수 정보만을 확인
-
Lambda Expression 에서 사용된 Parameter , Return Type 등을 이와 비교
-
Java 에서는 단독으로 메서드를 선언하는 것이 불가능, 이에 따라 람다식은 메서드를 가진 [객체]를 생성한다.
-
Lambda 를 사용할 때 대입되는 인터페이스를 [람다식의 Target Type] 이라고 부른다.
-
하나의 Abstract Method 를 가진 Interface 만이 람다식의 Target Type 이 될 수 있으며 , 이를 함수적 인터페이스 라고 부른다는 결론이 도출
-
@FunctionalInterface 어노테이션 선언 시 컴파일러가 두개 이상의 추상메서드가 선언되지 않게 체크한다.
Closure 와 Lambda
-
Java 의 Closure 는 자신을 둘러싼 context 내의 변수, 즉 외부 범위 변수를 함수 내부로 바인딩 시킨다.
-
Lambda 는 표현식에 잡힌 매개변수를 구현 함수 내부로 바인딩 시킨다.
-
그렇다면 Lambda 에서 외부 Context의 변수를 가져다가 쓰면 어떻게 되는가?
-
이 경우 Lambda 인 동시에 Closure 가 된다.
-
클래스 멤버인가, 로컬 변수인가에 따라 제약 사항이 갈린다.
-
람다식에서 선언되는 [this] 는 [람다식을 실행한 객체] 이다.
public class ClosureTest {
static <T> void waitFor(T input, Predicate<T> predicate) throws InterruptedException {
// 인자로 넘겨주는 변수 외 외부 변수가 없기 때문에 효율적으로 연산
while (predicate.test(input)) {
System.out.println("Blocking");
Thread.sleep(2500);
}
}
public static void main(String[] args) throws InterruptedException {
//Blocking... Blocking.... Blocking...
Optional<Object> obj_opt = Optional.of(new Object());
waitFor(obj_opt, Optional::isPresent);
}
}
java.uitl.function
-
Java 8 부터 빈번하게 사용되는 함수형 인터페이스들을 정의
-
목적 : 메소드 인자, 생성자의 인자로 Lambda Expression 을 사용하기 위함
-
Consumer<T> | Supplier<T> | Function<T> | Operator<T> | Predicate<T>
-
매개 값, 리턴 값의 유무에 따른 차이점을 가진다.
Consumer<T>
-
매개 값이 있으나 리턴 타입은 void , 소비하는 함수형 인터페이스
-
XXXConsumer 로 나뉘며 [accept()] 메서드를 구현
-
Prefix 로 Long, Int, Obj, Bi 등이 있으며 직관적 구성을 가진다.
public class ConsumerTest {
public static void main(String[] args) {
Consumer<String> stringConsumer = s -> System.out.print(s);
stringConsumer.accept("Hello ");
BiConsumer<String,String> biConsumer = (s, s2) -> System.out.printf("%s %s ! ",s,s2);
biConsumer.accept("Java","8");
ObjIntConsumer<String> objIntConsumer = (s, value) -> System.out.printf("%s , %d.",s,value);
objIntConsumer.accept("Lambda Expression",1);
//Hello Java 8 ! Lambda Expression , 1.
}
}
Supplier<T>
-
매개 값이 없으며, 리턴 타입이 존재, 공급하는 함수형 인터페이스
-
XXXSupplier 로 나뉘며 [getXXX()] 메서드를 구현
-
Boolean, Double, Int, Long, <T> 로 직관적 구성을 가진다.
public class SupplierTest {
static class Dice {
private int num;
public void setDiceNumber() {
num = (int)(Math.random() * 6) + 1;
}
public int getNum() {
return num;
}
}
public static void main(String[] args) {
Supplier<Dice> diceSupplier = Dice::new;
Dice dice = diceSupplier.get();
dice.setDiceNumber();
IntSupplier intSupplier = () -> dice.getNum();
int asInt = intSupplier.getAsInt();
//Random Number
System.out.println(asInt);
}
}
Function<T,R>
-
매개값 , 리턴값을 모두 가짐, [매핑]시키는 함수형 인터페이스
-
XXXFunction 으로 나뉘며, [applyXXX()] 메서드를 구현
-
Bi , A to B | To B, A 의 prefix 를 가지며 직관적 구성을 가진다.
-
T 는 보통 매개 값, R은 리턴 값을 나타내며, 여하에 따라 제네릭이 추가될 수 있다.
public class FunctionTest {
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Soo min",29),
new Person("Jung jin", 29)
);
Function<Person,String> stringFunction = Person::getName;
ToIntFunction<Person> toIntFunction = Person::getAge;
// Soo min
// Jung jin
// 29
// 29
people.forEach(person -> System.out.println(stringFunction.apply(person)));
people.forEach(person -> System.out.println(toIntFunction.applyAsInt(person)));
}
}
Operator<T>
-
Function 과 동일, [매핑] 이 아니라 [연산] 값을 도출하는 함수적 인터페이스
-
XXXOperator 로 나뉘며, [applyXXX()] 메서드를 구현
-
Function 과는 다르게 리턴타입이 매개 값과 동일하다.
-
Bi, Unary, DoubleBinary, DoubleUnary 등의 prefix 를 가짐. Unary 는 1 | Binary 는 2 로서 연산 매개 값을 의미
public class OperatorTest {
private static int sum = 0;
public static void main(String[] args) {
int[] scores = {20,45,60,55,80};
//Local Class 문제로 로컬 변수로 지정시 합산 불가
IntUnaryOperator intUnaryOperator = operand -> sum += operand;
Arrays.stream(scores).forEach(intUnaryOperator::applyAsInt);
System.out.println(sum); // 260
}
}
Predicate<T>
-
매개 값, 리턴 값을 모두 가짐, boolean 값을 도출하는 함수형 인터페이스
-
XXXPredicate 로 나뉘며 [testXXX()] 메서드를 구현
-
Bi , <T> , Int , Double, Long 의 Prefix 를 가지며 직관적 구성을 가진다.
-
BiPredicate 의 경우 <T,U> 의 제네릭 선언 객체를 [비교]하는데 사용
public class PredicateTest {
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Soo min",29),
new Person("Jung jin", 29)
);
BiPredicate<Person,Person> personPredicate = (person1,person2) -> person1.getAge() == person2.getAge();
BiPredicate<Person,Person> ageBiPredicate = (person, person2) -> person.getName().equals(person2.getName());
BiPredicate<Person, Person> compositePredicate = personPredicate.and(ageBiPredicate);
System.out.println(compositePredicate.test(people.get(0),people.get(1))); //false
//isEqual
Predicate condition = Predicate.isEqual("Java 8");
System.out.println(condition.test("Java 8")); // true
}
}
andThen , compose
-
Operator , Consumer , Function 에는 andThen() compose() default 메서드가 선언
-
복수의 함수형 인터페이스 구현체를 이어서 일종의 PipeLine 을 형성하는 것
-
andThen() 은 정순 , compose() 는 역순을 나타낸다.
public class AndThenTests {
static class Account {
private int age;
private String name;
private String address;
public Account(int age, String name, String address) {
this.age = age;
this.name = name;
this.address = address;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
public static void main(String[] args) {
Function<Account,String> function1 = Account::getName;
Function<String ,String> function2 = s -> s.substring(s.lastIndexOf(" ")+1 , s.length());
// 파이프라인으로 이어놓은 메서드 재정의
Function<Account,String > compositeFunction = function1.andThen(function2);
//실행
String apply = compositeFunction.apply(new Account(29, "SooMin Jung", "address"));
//Jung
System.out.println(apply);
}
}
and, or, negate, isEqual
-
Predicate 를 이어 최종적인 Predicate 객체를 리턴하는 일종의 Pipe Line
-
&& , || , ! 와 동일
-
isEqual 은 Static Method, Target 과 test() 의 매개변수가 같은 지 [검사] 한다.
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Soo min",29),
new Person("Jung jin", 29)
);
BiPredicate<Person,Person> personPredicate = (person1,person2) -> person1.getAge() == person2.getAge();
BiPredicate<Person,Person> ageBiPredicate = (person, person2) -> person.getName().equals(person2.getName());
BiPredicate<Person, Person> compositePredicate = personPredicate.and(ageBiPredicate);
System.out.println(compositePredicate.test(people.get(0),people.get(1))); //false
//isEqual
Predicate condition = Predicate.isEqual("Java 8");
System.out.println(condition.test("Java 8")); // true
}
minBy, maxBy
-
BiOperator 에서 제공하는 정적 메서드
-
두 매개 값을 비교하여 높거나 낮은 값을 리턴하는 Operator 를 리턴
public class MinByMaxBy {
static class Name {
String word;
int primary;
public Name(String word, int primary) {
this.word = word;
this.primary = primary;
}
public String getWord() {
return word;
}
public int getPrimary() {
return primary;
}
}
public static void main(String[] args) {
BinaryOperator<Name> lastNameOperator = BinaryOperator.maxBy((o1, o2) -> Integer.compare(o1.primary, o2.primary));
Name apply = lastNameOperator.apply(new Name("Jung", 1), new Name("SooMin", 0));
System.out.println(apply.getWord()); // Jung
BinaryOperator<Name> firstNameOperator = BinaryOperator.minBy(Comparator.comparingInt(o -> o.primary));
apply = firstNameOperator.apply(new Name("Jung", 1), new Name("SooMin", 0));
System.out.println(apply.getWord()); // SooMin
}
}
메서드 참조, 생성자 참조
-
간단한 람다식에서 불필요한 매개변수 기입을 생략하기 위해 사용
-
매개변수와 리턴타입을 해석하여 메서드의 매개변수와 리턴타입에 그대로 대입
-
:: 기호를 사용, 클래스 :: 메서드 | 참조변수 :: 메서드 | 클래스 :: new
-
생성자 오버로딩의 경우 매개 값 수에 따라 자동으로 생성 됨
public class ConstructorReference {
static class Account {
private String name;
private int age;
public Account(String name) {
System.out.println("OneArgsConstructor");
this.name = name;
}
public Account(String name, int age) {
System.out.println("TwoArgsConstructor");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Function<String,Account> oneArgFunction = Account::new;
Account account = oneArgFunction.apply("SooMin");
System.out.println(account.toString()); // Account{name='SooMin', age=0}
BiFunction<String, Integer, Account> twoArgsFunction = Account::new;
Account sooMin = twoArgsFunction.apply("SooMin", 29);
System.out.println(sooMin.toString()); // Account{name='SooMin', age=29}
}
}
Stream Pipe Line 을 위한 선수 지식
-
Collection Framework , Lambda , java.util.function 에 대한 이해가 선수지식
-
Stream Pipe Line 작성 시 Lambda 를 사용, java.util.function 의 인터페이스 사용
-
선수 지식을 통해 알게 된 객체를 이용하여 Stream 의 Pipe Line 을 이어주는 것이 Stream Pipe Line
-
Iterator , Collections, Optional 등이 사용 됨
Stream?
-
많은 데이터를 다룬다 => Array , Collection
-
순회 한다 => for , Iterator
-
데이터 소스마다 다른 방식으로 반복자를 돌려 데이터를 다루게 됨
-
또한 코드의 가독성이 떨어지며, 재사용성은 감소한다.
-
데이터 소스의 추상화. 즉, 어떠한 데이터를 다루던 같은 방식으로 다루게한다.
public class Basic {
public static void main(String[] args) {
String[] strArr = {"aaa", "bbb", "ccc"};
List<String> strList = Arrays.asList(strArr);
// 다른 데이터 소스도 정렬 -> 출력하는 방식이 동일하고, 간결하다.
Arrays.stream(strArr).sorted().forEach(System.out::println);
strList.stream().sorted().forEach(System.out::println);
}
}
Stream 의 특징
-
데이터 소스를 읽을 뿐, 변경하지 않음. 새로운 결과를 리턴
-
한번 열린 스트림은 닫히기 때문에 일회용
-
메서드 내부에 반복문을 숨기고 있어 가독성이 상승
public class Specify {
public static void main(String[] args) {
String[] strArr = {"ccc", "bbb", "aaa"};
Stream<String> stream = Arrays.stream(strArr);
List<String> collectList = stream.sorted().collect(Collectors.toList());
//IllegalStateException : stream has already been operated upon or closed
long count = stream.sorted().count();
// void forEach(Consumer<? super T> action){
// Objects.requireNonNull(action);
//
// for(T t : src) {
// action.accept(t);
// }
// }
}
}
Stream - 연산
-
Stream 의 연산과정은 [중간] [최종] 으로 나뉨
-
중간 연산은 리턴타입이 Stream<T> 이므로 메서드 체인이 가능
-
최종연산은 요소를 소모 후 다른 리턴타입을 가지므로 단 한번 사용
-
최종 연산이 정의되지 않는다면 중간 연산도 실행하지 않음
-
핵심 메서드는 map() , flatMap() / reduce(), collect() . 이외 메서드는 직관적이다.
-
Wrapper 클래스를 박싱하고 언박싱하는 [비효율성 감소]를 위해 IntStream, LongStream, DoubleStream 이 제공
-
parallel() <-> sequential() 으로 병렬처리를 하거나 취소 가능
-
distinct , filter, limit, skip, peek, sorted, map, flatMap ... -> forEach, count, find, match, toArray, reduce, collect ...
Stream - 생성
-
Array => Arrays.stream
-
Collection => 조상클래스 Collection 에 stream 이 정의
-
Stream.of(T... value)
-
PrimitiveStream => IntStream.of, DoubleStream.of …
-
범위 정수 => rangeClosed
-
Lambda => Stream.iterate(T , UnaryOperator) , Stream.generate(Supplier)
-
File => Files.list(Path)
-
Empty => Stream.empty
-
Concat => Stream.concat( Stream<T>, Stream<T> )
Stream - 중간연산
-
skip, limit : 매개값은 long, 요소를 건너뛰거나, 자름
-
distinct, filter(Predicate<T>) : 중복제거(equals), Predicate 연산 값이 false 경우 제거
-
sorted(Comparator<T>) : comparing, comparingInt 등의 메서드에 thenComparing 을 이어 추가 정렬 조건을 제시 데이터소스가 Comparable 을 구현하면 간결하게 작성 가능
-
peek : 중간 결과를 로깅하는데에 유용, 중간 연산이므로 Stream을 닫지 않음
public class MiddleOper {
static class Student {
private int score;
private String name;
public Student(int score, String name) {
this.score = score;
this.name = name;
}
public int getScore() {
return score;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"score=" + score +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);
// 1, 5, 5
intStream.filter(i -> i%2 != 0).filter(i -> i%3 !=0).forEach(System.out::println);
Stream<Student> studentStream = Stream.of(
new Student(50,"정수민"),
new Student(90,"신용권"),
new Student(92, "남궁성"),
new Student(96, "최범균")
);
// Student{score=96, name='최범균'}
// Student{score=92, name='남궁성'}
// Student{score=90, name='신용권'}
// Student{score=50, name='SooMin'}
studentStream.sorted(Comparator.comparingInt(Student::getScore).reversed()).forEach(System.out::println);
}
}
Stream - map
-
java.util.function 의 Function<T,R> 을 사용
-
원하는 데이터조작을 체인으로 역할에 따라 분배
-
단일 책임형식의 Method Chain 이 가독성을 상승 시킴
public class Map {
public static void main(String[] args) {
String[] strArr = {"/etc/apt/sources.list","/etc/network/interfaces","main/java/Map.class","File.txt","None",};
List<String> collect = Arrays.stream(strArr).filter(s -> (s.lastIndexOf("/") != -1) || (s.lastIndexOf(".") != -1))
.map(s -> {
if (s.lastIndexOf("/") != -1) {
s = s.substring(s.lastIndexOf("/") + 1);
}
return s;
})
.map(s -> {
if(s.lastIndexOf(".") != -1 ){
s = s.substring(0, s.lastIndexOf("."));
}
return s;
})
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
//[File, Map, interfaces, sources]
System.out.println(Arrays.toString(collect.toArray()));
}
}
Stream - flatMap
-
연산 결과, 또는 각 요소가 배열일 경우 사용
-
즉 Stream<T[]>, Stream<Stream<T>> -> Stream<T> 로 변환
-
각 요소의 원소들을 다시 나열하는 기능을 가졌다.
public class FlatMap {
public static void main(String[] args) {
Stream<String[]> streamArr = Stream.of(
new String[]{"Java ", "Oracle ", "JavaScript"},
new String[]{"Vue.js ", "SpringBoot ", "Ubuntu"}
);
// Stream<Stream<String>> 이 되어버린다.
Stream<Stream<String>> streamStream = streamArr.map(Arrays::stream);
Stream<String> stringStream = streamArr.flatMap(Arrays::stream);
Stream<String> strStream = Stream.of(
"Java Oracle JavaScript",
"Vue.js SpringBoot Ubuntu");
//원하는 결과가 도출되지 못함
Stream<String[]> stream = strStream.map(s -> s.split(" "));
Stream<Stream<String>> streamStream1 = strStream.map(s -> Stream.of(s.split(" ")));
// 원하는 결과 도출
Stream<String> flatStream = strStream.flatMap(s -> Stream.of(s.split(" ")));
}
}
Stream - 최종연산
-
forEach : 내부 반복으로 Consumer 의 accept 를 실행
-
match : Predicate 에 조건에 맞는지를 검사, all, any, none, findFirst, findAny
-
count, sum, average, max, min : sum(), average() 는 Primitive Stream 에 포함
-
reduce : BinaryOperator<T> , 원소간의 연산 결과를 다음 원소와 계산
-
collect : Collectors 사용하여 원소를 수집하는 방법을 기술
reduce ( T identity , BinaryOperator<T> )
-
identity 는 [초기 값]을 지정시켜 유연하게 연산
-
identity 를 생략하면 스트림의 2개의 원소로 연산을 시작
-
계산결과를 순차적으로 연산 하기 위해 사용
public class Reduce {
public static void main(String[] args) {
IntStream intStream = IntStream.of(5,4,2,17,10,20,31,12,4,1);
int count = intStream.reduce(0, (identity, element) -> identity + 1); // 10
int max = intStream.reduce(Integer.MIN_VALUE, (identity, element) -> identity > element ? identity : element); // 31
int min = intStream.reduce(Integer.MAX_VALUE, (identity, element) -> identity < element ? identity : element);//1
}
}
collect(Collector)
-
연산 중 가장 복잡하고, 가장 유용
-
매개변수는 Collector 의 구현체
-
Collector 는 요소를 어떻게 수집할 것인지 정의
-
Collector 를 얼마나 유연하게 사용하는지가 핵심
-
toList , toSet , toMap, toCollection, toArray
-
counting, summingInt , averagingInt, maxBy, minBy
-
reducing
-
joining
-
groupBy , partitionBy
to..
-
스트림의 원소들을 원하는 데이터소스로 변경
-
toCollection 으로 원하는 컬렉션 지정
public class CollectToDataSource {
static class Student {
private String name;
private String className;
private int score;
public Student(String name, String className, int score) {
this.name = name;
this.className = className;
this.score = score;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public int getScore() {
return score;
}
}
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("정수민","D",50),
new Student("신용권","C",92),
new Student("남궁성","B",95),
new Student("최범균","A", 97)
);
List<Student> toList = studentStream.collect(toList());
Set<Student> toSet = studentStream.collect(toSet());
LinkedList<Student> toLink = studentStream.collect(toCollection(LinkedList::new));
Map<String, Student> collect = studentStream.collect(toMap(Student::getName, student -> student));
Student[] students = studentStream.toArray(Student[]::new);
}
}
counting , summingInt , averagingInt , maxBy , minBy
-
Stream 의 최종연산에 포함된 메서드와 기능 동일
-
collect 에서 제공하는 이유는 groupingBy 와 함께 사용하기 위함
public class CollectCount {
static class Student {
private String name;
private String className;
private int score;
public Student(String name, String className, int score) {
this.name = name;
this.className = className;
this.score = score;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public int getScore() {
return score;
}
}
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("정수민","D",50),
new Student("신용권","C",92),
new Student("남궁성","B",95),
new Student("최범균","A", 97)
);
studentStream.collect(counting()); // 4
studentStream.collect(summingInt(Student::getScore)); // 334
studentStream.collect(maxBy(Comparator.comparingInt(Student::getScore))); // 97
studentStream.collect(averagingInt(Student::getScore)); // 83.5
}
}
groupingBy , partitioningBy
-
Collector 에 Stream 최종연산 메서드와 같은 기능을 제공하는 이유
-
groupingBy 는 요소를 기준으로 그룹화 => Function
-
partitioningBy 는 조건을 기준으로 분할 => Predicate
-
결과는 동일하게 <Flag, T> 로 리턴
-
Collector 를 추가하여 조건 추가 가능
partitioningBy
public class CollectionGroupPartition {
static class Student {
private String name;
private String className;
private int score;
private boolean graduated;
public Student(String name, String className, int score, boolean graduated) {
this.name = name;
this.className = className;
this.score = score;
this.graduated = graduated;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public int getScore() {
return score;
}
public boolean isGraduated() {
return graduated;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", className='" + className + '\'' +
", score=" + score +
", graduated=" + graduated +
'}';
}
}
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("정수민","D",50,false),
new Student("신용권","C",92,true),
new Student("남궁성","B",95,true),
new Student("최범균","A", 97,true)
);
Map<Boolean, Long> partitioningCount = studentStream.collect(partitioningBy(Student::isGraduated, counting()));
partitioningCount.get(true); // 3
partitioningCount.get(false); // 1
Map<Boolean, Map<Boolean, List<Student>>> collect = studentStream
.collect(partitioningBy(Student::isGraduated, partitioningBy(student -> student.getScore() > 95)));
//Student{name='최범균', className='A', score=97, graduated=true}
collect.get(true).get(true).forEach(System.out::println);
}
}
groupingBy
public class CollectionGroup {
static class Student {
private String name;
private String className;
private int score;
private boolean graduated;
public Student(String name, String className, int score, boolean graduated) {
this.name = name;
this.className = className;
this.score = score;
this.graduated = graduated;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public int getScore() {
return score;
}
public boolean isGraduated() {
return graduated;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", className='" + className + '\'' +
", score=" + score +
", graduated=" + graduated +
'}';
}
}
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("정수민","D",50,false),
new Student("신용권","C",92,true),
new Student("남궁성","B",95,true),
new Student("최범균","A", 97,true)
);
Map<String, Map<String, Map<Boolean, List<Student>>>> collect =
studentStream
.collect(
groupingBy(Student::getClassName,
groupingBy(Student::getName,
partitioningBy(Student::isGraduated))));
//Student{name='정수민', className='D', score=50, graduated=false}
collect.get("D").get("정수민").get(false)
.forEach(System.out::println);
Map<String, Map<Boolean, Map<Boolean, List<Student>>>> collect2 = studentStream
.collect(
groupingBy(Student::getClassName,
partitioningBy(s -> s.getScore() > 92,
partitioningBy(Student::isGraduated))));
//Student{name='남궁성', className='B', score=95, graduated=true}
collect2.get("B").get(true).get(true).forEach(System.out::println);
}
}
'JAVA-Basic' 카테고리의 다른 글
JVM 할당 CPU, Memory 확인하기 (0) | 2021.01.01 |
---|---|
다시보는 객체지향언어의 특징 (0) | 2020.09.24 |
클래스 생성자에 추가사항이 있을때의 관리방법. (0) | 2020.09.11 |
제네릭을 이용한 필요한 클래스들의 모듈화 (0) | 2020.09.10 |
설...계? (0) | 2020.08.28 |