14장 람다식

slee2·2021년 10월 4일
0

Java의 정석

목록 보기
26/28
post-thumbnail
post-custom-banner

람다식

함수(메서드)를 간단한 식으로 표현하는 방법

int max(int a, int b) {
	return a > b ? a : b;
}
--- 람다식 ---
(a, b) -> a > b ? a : b

익명 함수(이름이 없는 함수, anonymous function)

int max(int a, int b) {
	return a > b ? a : b;
}
--- 익명 함수 ---
int max(int a, int b) -> {
	return a > b ? a : b;
}

함수와 메서드의 차이

근본적으로 동일. 함수는 일반적 용어, 메서드는 객체지향개념 용어
함수는 클래스에 독립적, 메서드는 클래스에 종속적

람다식 작성하기

  1. 메서드의 이름과 반환타입을 제거하고 '->'를 블록{} 앞에 추가한다.
int max(int a, int b) {
	return a > b ? a : b;
}
--- 람다식 ---
int max(int a, int b) -> {
	return a > b ? a : b;
}
  1. 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능(끝에';' 안붙임)
(int a, int b) -> {
	return a > b ? a : b;
}
--- 람다식 ---
(int a, int b) -> a > b ? a : b
  1. 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)
(int a, int b) -> a > b ? a : b
--- 람다식 ---
(a, b) -> a > b ? a : b

람다식 작성하기 - 주의사항

  1. 매개변수가 하나인 경우, 괄호() 생략가능(타입이 없을때만)
(a) -> a * a
(int a) -> a * a
--- 람다식 ---
a -> a * a // OK
int a -> a * a // 에러
  1. 블록 안의 문장이 하나뿐 일 때, 괄호{} 생략가능(끝에 ';'안 붙임)
(int i) -> {
	System.out.println(i);
}
--- 람다식 ---
(int i) -> System.out.println(i)

단, 하나뿐인 문장이 return문이면 괄호{} 생략불가

(int a, int b) -> { return a > b ? a : b; }	// OK
(int a, int b) -> return a > b ? a : b	// 에러

예시

메서드람다식
int max(int a, int b) {
return a > b ? a : b;
}
(a, b) -> a > b ? a : b
int printVar(String name, int i) {
System.out.println(name+"="+i);
}
(name, i) -> System.out.println(name+"="+i)
int squars(int x) {
return x * x;
}
x -> x * x
int roll() {
return (int)(Math.random() * 6};
}
() -> (int)(Math.random() * 6)

람다식은 익명함수가 아니라 익명 객체이다.

(a, b) -> a > b ? a : b
-------------
new Object() {
	int max(int a, int b) {
   		return a > b ? a : b;
   	}
}

람다식(익명 객체)을 다루기 위한 참조변수가 필요.

Object obj = new Object() {
	int max(int a, int b) {
   		return a > b ? a : b;
   	}
};
--- 람다식 ---
타입 obj = (a, b) -> a > b ? a : b ;	// 어떤타입?
int value = obj.max(3, 5);	// 에러. Object클래스에 max()가 없음

함수형 인터페이스

단 하나의 추상 메서드만 선언된 인터페이스

interface Myfunction {
	public abstract int max(int a, int b);
}
----------------------------
MyFunction f = new Myfunction() {
                      public int max(int a, int b) {
                          return a > b ? a : b;
                      }
                  }

int value = f.max(3, 5);	// OK. MyFunction에 max()가 있음

함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음.

MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3, 5);	// 실제로는 람다식(익명 함수)이 호출됨

함수형 인터페이스 타입의 매개변수, 반환타입

void aMethod(MyFunction f) {
	f.myMethod();	// MyFunction에 정의된 메서드 호출
}

MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);
--- 위 두식을 하나로 합치면 ---
aMethod( () -> System.out.println("myMethod()"));

반환타입

MyFunction myMethod() {	// 무려 람다식이 반환이 된다고 ㄷㄷ
	MyFunction f = ()->{};
   	return f;
}
--- 위를 줄여서 표현 ---
MyFunction myMethod() {
	return ()->{};
}

java.util.function

일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의함

함수형 인터페이스메서드설명
java.lang.
Runnable
void run()매개변수도 없고, 반환값도 없음.
Supplier<T>T get()매개변수는 없고, 반환값만 있음.
Consumer<T>void accept(T t)Supplier와 반대로 매개변수만 있고, 반환값이 없음
Function<T,R>R apply(T t)일반적인 함수. 하나의 매개변수를 받아서 결과를 반환
Predicate<T>boolean test(T t)조건식을 표현하는데 사용됨.
매개변수는 하나, 반환 타입은 boolean

쓰는법

// Predicate는 조건식을 쓸때 사용하는 함수인터페이스
Predicate<String> isEmptyStr = s -> s.length()==0;
Strings = "";

if(isEmptyStr.test(s))	// if(s.length()==0)
	System.out.println("This is an empty String.");

매개변수 두개인거

함수형 인터페이스메서드설명
BiConsumer<T,U>void accept(T t, U u)두개의 매개변수만 있고,
반환값이 없음
BiPredicate<T,U>boolean test(T t, U u)조건식을 표현하는데 사용됨.
매개변수는 둘, 반환값은 boolean
BiFunction<T,U,R>R apply(T t, U u)두 개의 매개변수를 받아서 하나의 결과를 반환

매개변수가 2개면 앞에 Bi가 붙는다.
두 개보다 많은 매개변수는 직접 만들어야 한다.

// 요렇게
@FunctionalInterface
interface HolyMyGod<T,U,V,R> {
	R apply(T t, U u, V v);
}

이건머야

매개변수의 타입과 반환타입의 타입이 모두 일치하는 함수도 있다.

함수형 인터페이스메서드설명
UnaryOperator<T>T apply(T t)Function의 자손, Function과 달리
매개변수와 결과의 타입이 같다.
BinaryOperator<T>T apply(T t, T t)BiFunction의 자손, BiFunction과
달리 매개변수와 결과의 타입이 같다.

Predicate의 결합

여러개의 조건식을 합쳐서 하나로 가능!

Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i%2 == 0;
Predicate<Integer> notP = p.negate();	//i >= 100 반대의 경우인가. 음

// 100 <= i && (i < 200 || i%2==0)
Predicate<Integer> all = notP.and(q.or(r));
System.out.println(all.test(150));	// true

--- 아래처럼 람다식 직접 꾸역꾸역 넣어도 됨 ---
Predicate<Integer> all = notP.and(i -> i < 200).or(i -> i%2 == 0);

그리고 뜬금없긴한데 isEqual()라는것도 사용할 수 있음

// str1과 str2가 같은지 비교
boolean result = Predicate.isEqual(str1).test(str2);

컬렉션 프레임웍과 함수형 인터페이스

자, 컬렉션 프레임웍에서도 함수형 인터페이스가 있다. 내용은 대충 어떤 함수가 있고 어떤 기능을 하는지 나온게 끝. 그리고 소개 안할 예정이다. 이유는 아직 쓰는법을 모르고, 필요한 이유도 모르기 때문에 괜히 머리에 집어넣기 그렇기 때문에. 그냥 넘어가겠다 이건. 호옥시나 나중에 찾아와서 이걸 찾는다면 책 564페이지에 있다.

메서드 참조

Function<String, Integer> f = (String s) -> Integer.parseInt(s);

// 위는 람다식이고 이를 좀더 편하게 메서드 참조로 나타낼 수 있다는 뜻
Function<String, Integer> f = Integer::parseInt;	// 메서드 참조

생성자의 메서드 참조

Supplier<MyClass> s = () -> new MyClass();	//람다식
Supplier<MyClass> s = MyClass::new;		//메서드 참치

Function<Integer, MyClass> f = (i) -> new MyClass(i);	// 람다식
Function<Integer, MyClass> f2 = MyClass::new;		// 메서드 참조

// 람다식
BiFunction<Integer, String, MyClass> bf = (i, s) -> new MyClass(i, s);
// 메서드 참조
BiFunction<Integer, String, MyClass> bf2 = MyClass::new;

// 배열 람다식
Function<Integer, int[]> f = x -> new int[x];
// 배열 메서드 참조
Function<integer, int[]> f2 = int[]::new

Stream

이게 뭘까 간단히 말해본다. 각 클래스들에게는 비슷한 기능들이 중복되어 있는 경우가 많다. 예를들어 List를 정렬할 때는 Collections.sort(), 배열을 정렬할때는 Arrays.sort()를쓴다. 이걸 모두 같은 방식으로 다루기 위해 나온게 스트림.

String[] strArr = {"aaa", "ddd", "ccc"};
List<String> strList = Arrays.asList(strArr);

Stream<String> strStream1 = strList.stram();	// 스트림 생성
Stream<String> strStream2 = Arrays.stream(strArr);	// 스트림 생성

strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);

스트림은 데이터 소스를 변경하지 않는다.

읽기만하고 변경은 하지않는다. 필요하다면 밑에처럼 해야된다.

List<String> sortedList = strSteram2.sorted().collect(Collectors.toList());

일회용이다.

한번 사용하면 닫혀서 다시 못쓴다. 필요하면 다시 생성해야됨.

작업을 내부 반복으로 처리한다.

stream.forEach(System.out::println);
--- 모든 요소를 거쳐간다. ---
for(String str : strList)
	System.out.println(str);

아무튼 그래서 간결하다는 뜻.

병렬 스트림

int sum = strStream.parallel()	// strStream을 병렬 스트림으로 전환
		   .mapToInt(s -> s.length())
           	   .sum();

스트림 만들기 - 컬렉션

Stream<E> stream()	// Collection 인터페이스의 메서드

--- 예시 ---
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);	// 가변인자
Stream<Integer> intStream = list.stream();		// list를 소스로 하는 스트림 생성

intStream.forEach(System.out::println);	// 스트림의 모든 요소를 출력한다.
intStream.forEach(System.out::println);	// 에러 스트림 닫힘.

스트림 만들기 - 배열

Stream<T> Stream.of(T... values)	// 가변인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startIncludesive, int endExclusive)

Stream<T> strStream=Streamof("a", "b", "c");	// 가변인자
Stream<T> strSteram=Streamof(new String[]{"a", "b", "c"});
Stream<T> strStream=Arrays.stream(new String[]{"a", "b", "c"});
Stream<T> strStream=Arrays.stream(new String[]{"a", "b", "c"}, 0, 3);

스트림 만들기 - 임의의 수

Random클래스에는 아래와 같은 인스턴스 메서드들이 포함되어 있다.

IntStream	ints()
LongStream	longs()
DoubleStream	doubles()

--- 무한 스트림은 limit()을 같이 데리고 다닌다 ---
IntStream intStream = new Random().ints();	// 한무 스트림
intStream.limit(5).forEach(System.out::println);	// 5개의 요소만 출력한다.

--- 유한 스트림은 그냥 쓴다. ---
IntStream intStream = new Random().ints(5);	// 크기가 5인 난수 스트림을 반환

long도되고 double도 되고

스트림 만들기 - 특정 범위의 정수

시작 부터 끝까지 연속으로 수를 생성한다. range()는 end를 포함하지 않고 rangeClosed()는 end까지 포함.

IntStream	IntStream.range(int begin, int end)
IntStream	IntStream.rangeClosed(int begin, int end)

IntStream	intStream = IntStream.range(1, 5)	// 1, 2, 3, 4
IntStream	intStream = IntStream.rangeClosed(1, 5)	// 1, 2, 3, 4, 5

이것도 long도 되고 double도 되고

스트림 만들기 - 람다식 iterate(), generate()

람다식을 매개변수로 받아서, 이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림 생성

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stram<T> generate(Supplier<T> s)

// seed값을 람다식에 넣고 나온값을 다시 람다식에 넣는 것을 반복
Stream<Integer> evenStream = Stream.iterate(0, n->n+2);	// 0, 2, 4, 6, ...

//generate도 람다식에 무한 스트림을 생성하지만 이전값을 사용하지 않는다.
Stream<Double> randomStram = Stream.generate(Math::random);
Stream<Integer> oneStream = Stream.generate(()->1);

// 그리고 generate는 매개변수 못받음
IntStream evenStream = Stream.iterate(0, n->n+2);	// 에러
DoubleStream randomStream = Stream.generate(Math::random);	// 에러

스트림 만들기 - 파일과 빈 스트림

list()는 지정된 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환한다.

Stream<Parh> Files.list(Path dir)

Stream<String> Files.lines(Parh parh)
Stream<String> Files.lines(Path path, Charst cs)

스트림에 연산을 수행한 결과가 하나도 없을 때, null보다 빈 스트림을 반환하는 것이 낫다.

Sream emptyStream = Stream.empty();	// empty()는 빈 스트림을 생성해서 반환한다.
long cout = emptyStream.count();	// count의 값은 0

스트림의 중간연산과 최종연산

중간 연산 : 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산할 수 있음
최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능

스트림의 중간연산 - skip(), limit()

skip(3)은 처음 3개의 요소를 건너뛰고 limit(5)는 요소를 5개로 제한한다.

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

IntStream intStream = IntSream.rangeClosed(1, 10);	// 1~10의 요소를 가진 스트림
intStream.skip(3).limit(5).forEach(System.out::print);	// 45678

스트림의 중간연산 - filter(), distinct()

distinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건에 맞지 않는 요소를 걸러낸다.

Stream<T> filter(Predicate<? super T> predicate)
Stream<T> distinct()

IntStream intStream = IntStream.of(1, 2, 2, 3, 3, 3, 4, 5, 5, 6);
intStream.distinct().forEach(System.out::print);	// 123456

IntStream intStream = IntStream.rangeClosed(1, 10);	// 1~10
intStream.filter(i -> i%2 == 0).forEach(System.out::print);	// 246810

스트림의 중간연산 - sorted()

정 렬!

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

Sream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
strStream.sorted().forEach(System.out::print);	// CCaaabccdd
문자열 스트림 정렬 방법출력결과
strStream.sorted() // 기본 정렬
strStream.sorted(Comparator.naturalOrder()) // 기본 정렬
strStream.sorted((s1, s2) -> s1.compareTo(s2)) // 람다식도 가능
strStream.sorted(String::compareTo) // 위의 문장과 동일
CCaaabccdd
strStream.sorted(Comparator.reverseOrder()) // 기본 정렬의 역순
strStream.sorted(Comparator.naturalOrder().reversed())
ddccbaaaCC
strStream.sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분안함aaabCCccdd
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed()) // 역순ddCCccbaaa
strStream.sorted(Comparator.comparing(String::length)) // 길이 순 정렬
strStream.sorted(Comparator.comparingInt(String::length)) // no 오토박싱
bddCCccaaa
strStream.sorted(Comparator.comparing(String::length).reversed())aaaddCCccb

스트림의 중간연산 - Comparator의 메서드

이 메서드들은 모두 Comparator<T>를 반환

comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

--- 정렬조건 추가 ---
thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyEtractor)
thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)

--- 예시 ---
// 학생 스트림을 반, 성적순, 그리고 이름순으로 정렬하여 출력하려면
studentStream.sorted(Comparator.comparing(Student::getBan)
			.thenComparing(Student::getTotalScore)
			.thenComparing(Student::getName))
			.forEach(System.out::println);

스트림의 중간연산 - map()

요소에 저장된 값중에서 원하는 필드만 뽑아내거나 특정형태로 변환

Stream<R> map(Function<? super T, ? extends R> mapper)

--- File에서 파일의 이름만 추출하고 싶을때 ---
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"),
	new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));

// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println);	// 스트림의 모든 파일이름을 출력

--- 아니 이게 된다고? ---
fileStream.map(File::getName)	// Stream<File> -> Stream<String>
	.filter(s -> s.indexOf('.')!=-1)		// 확장자가 없는 것은 제외
	.map(s -> s.substring(s.indexOf('.')+1))	// Stream<String> -> Sream<String>
	.map(String::toUpperCase)			// 모두 대문자로 전환
	.distinct()					// 중복 제거
	.forEach(System.out::print);			// JAVABAKTXT

스트림의 중간연산 - peek()

연산과 연산 사이에 올바르게 처리되었는지 확인가능스.

fileStream.map(File::getName)
	.fileter(s -> s.indexOf('.')!=-1)	// 확장자가 없는 것은 제외
	.peek(s->System.out.printf("filename=%s%n", s))	// 파일명을 출력한다.
	.map(s -> s.substring(s.indexOf('.')+1))	// 확장자만 추출
	.peek(s->System.out.printf("extension=%s%n", s))	// 확장자를 출력한다.
	.forEach(System.out::println);

스트림의 중간연산 - flatMap()

아니 이거 왜쓰는거임 화나네. Stream<String[]>를 Stream 로 바꾸기 위해 쓰는거임

Stream<String[]> strArrStrm = Stream.of(
	new String[]{"abc", "def", "ghi" },
	new String[]{"ABC", "GHI", "JKLMN"}
};

// map 쓰면 일케됨
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
// flatMap쓰면 일케됨
Steam<String> strStrm = strArrStrm.flatMap(Arrays::stream);

Optional<T>

아니 이거 갑자기 왜나옴

public final class Optional<T> {
	priavte final T value;	// T타입의 참조변수
		...
}

--- 객체 생성하기 ---
String str = "abc";
// 생성할 때는 of() 또는 ofNullable()을 사용한다.
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(new String("abc"));

// 참조변수 값이 null일 가능성이 있다면 ofNullable()
Optional<String> optVal = Optional.of(null);	// NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null);	// OK

// 빈 객체로 초기화하는 것이 바람직하다.
Optional<String> optVal = Optional.<String>empty();	// 빈 객체로 초기화

Optional 객체의 값 가져오기

// get()을 사용하면 저장된 값을 가져올 수 있다.
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get();	// optVal에 저장된 값을 반환. null이면 예외발생
String str2 = optVal.orElse("");	// optVal에 저장된 값이 null일 때는, ""를 반환

orElse()의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()과 null일 때 지정된 예외를 발생시키는 orElseThrow()가 있다.

T orElseGet(Supplier<? extends T> other)
T orElseThrow(Supplier<? extends X> exceptionSupplier)

String str3=optVal2.orElseGet(String::new);	// () -> new String()와 동일
String str4=optVal2.orElseThrow(NullPointerException::new);	// 널이면 예외발생

ifPresent()는 Optional객체의 값이 null이면 false를 아니면 true 반환

// str이 null이 아닐 때만 값을 출력하고, null이면 아무 일도 일어나지 않는다.
Optional.ofNullable(str).ifPresent(System.out::println);

OptionalInt, OptionalLong, OptionalDouble

아니 이거 뜬금없이 갑자기 왜 나오는거야 아아악

OptionalInt	findAny()
OptionalInt	findFirst()
OptionalInt	reduce(InBinaryOperator op)
OptionalInt	max()
OptionalInt	min()
OptionalDouble	average()

--- 클래스에 따라 메서드가 달라요 ---
Optional<T>		T get()
OptionalInt		int getAsInt()
OptionalLong		long getAsLong()
OptionalDouble		double getAsDouble()

OptionalInt opt = OptionalInt.of(0);	// OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty();	// OptionalInt에 0을 저장

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

반환타입이 void이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.

void forEach(Consumer<? super T> action)

스트림의 최종연산 - 조건검사

boolean allMatch (Predicate<? super T> predicate) // 모든 요소가 일치하면 참
boolean anyMatch (Predicate<? super T> predicate) // 하나의 요소라도 일치하면 참
boolean noneMatch (Predicate<? super T> predicate) // 모든 요소가 불일치하면 참

// 총점이 100이하인 학생이 있는지 확인
boolean noFailed = stuStream.anyMatch(s->s.getTotalScore()<=100)

//filter()와 함께 첫 번째 것을 반환하는 findFirst()사용
Optional<Student> stu = stuStream.filter(s->s.getTotalScore()<= 100).findFirst();
// 병렬 스트림인 경우에는 findFirst()대신 findAny()
Optional<Student> stu = parallelStream.filter(s->s.getTotalScore()<=100).findAny();

스트림의 최종연산 - reduce()

요소를 하나씩 소모해버리기

Optional<T> reduce(BinaryOperator<T> accumulator)

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

스트림의 최종연산 - collect()와 Collectors

최종 연산 중에서 가장 복잡하면서도 유용하게 활용될 수 있는 것이 collect(). collect()는 스트림의 요소를 수집하는 최종 연산으로 reduce()과 유사하다. collect()가 스트림의 요소를 수집하려면, 어떻게 수집할 것인가에 대한 방법이 정의되어있어야 하는데, 이 방법을 정의한 것인 바로 Collector이다.

collect()	스트림의 최종연산, 매개변수로 컬렉터를 필요로 한다.
Collector	인터페이스, 컬렉터는 이 인터페이스를 구현해야한다.
Collectors	클래스, static메서드로 미리 작성된 컬렉터를 제공한다.

Object collect(Collector collector)

잘 사용되지 않는데요.

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

스트림의 모든 요소를 컬렉션에 수집하려면, Collectors클래스의 toList()와 같은 메서드를 사용하면 된다. List나 Set이 아닌 특정 컬렉션을 지정하려면, toCollection()에 원하는 컬렉션의 생성자 참조를 매겨변수로 넣어주면 된다.

List<String> names = stuStream.map(Student::getName)
ArrayList<String> list = names.stream()
			.collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = persionStream
			.collect(Collectors.toMap(p->p.getRegId(), p->p));

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

이쯤되니 정신이 나가버려엇 통계를 collect()로 얻을수도 있고 다른방법도 있는데 둘다 보여줌.

long count = stuStream.count();
long count = stuStream.collect(counting());	// Collectors.counting()

long totalScore = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore = stuStream.collect(summingInt(Student::getTotalScore));

Optional<Student> topStudent = stuStream
		.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Student> topStudent = stuStream
	.collect(maxBy(comparator.comparingInt(Student::getTotalScore)));

IntSummaryStatistics stat = stuStream
			.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream
			.collect(summarizingInt(Student::getTotalScore));

스트림을 리듀싱 - reducing()

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));

스트림을 문자열로 결합 - joining()

스트림의 요소가 문자열이 아닌 경우에는 먼저 map()을 이용해서 스트림의 요소를 문자열로 변환해야 한다.

String studentNames = stuStream.map(Student::getName).collect(joining());
String studentNames = stuStream.map(Student::getName).collect(joining(","));
String studentNames = stuStream.map(Student::getName).collect(
						joining(",", "[", "]"));

스트림의 그룹화와 분할

collect()의 유용함이 여기서 나타난다고 하네요 어디 두고봅시다.

스트림의 분할 - partitioningBy()

// 1. 기본 분할
Map<Boolean, List<Student>> stuBySex = stuStream
		.collect(partitioningBy(Student::isMale));	// 학생들을 성별로 분할

List<Student> maleStudent = stuBySex.get(true);	// Map에서 남학생 목록을 얻는다.
List<Student> femaleStudent = stuBySex.get(false);	// Map에서 여학생 목록을 얻는다.

// 2. 기본 분할 + 통계 정보
Map<Boolean, Long> stuNumBySex = stuStream
		.collect(partitioningBy(Student::isMale, counting()));

System.out.println("남학생 수 :" + stuNumBySex.get(true));	// 남학생 수 :8
System.out.println("여학생 수 :" + stuNumBySex.get(false));	// 여학생 수 :10

--- 남학생1등 여학생1등 구하기 ---
Map<Boolean, Optional<Student>> topScoreBySex = stuStream
	.collect(
		partitioningBy(Student::isMale,
			maxBy(comparingInt(Student::getScore))
		)
	);
System.out.println("남학생1등 :"+ topScoreBySex.get(true));
System.out.println("여학생1등 :"+ topScoreBySex.get(false));
// 남학생 1등 :Optional[[나자바, 남, 1, 1, 300]]
// 여학생 1등 :Optional[[김지미, 여, 1, 1, 250]]

--- 만약에 150점 아래를 불합격처리하고싶다 ---
Map<Boolean, Optional<Student>> topScoreBySex = stuStream
	.collect(
		partitioningBy(Student::isMale,
			partitioningBy(s -> s.getScore() < 150)
		)
	);
List<Student> faileMaleStu = failedStuBySex.get(true).get(true);
List<Student> faileFemaleStu = failedStuBySex.get(false).get(true);

스트림의 그룹화 - groupingBy()

Map<Integer, List<Student>> stuByBan = stuStream
	.collect(groupingBy(student::getBan, toList()));	// toList()가 생략가능

Map<Integer, HashSet<Student>> stuByBan = stuStream
	.collect(groupingBy(student::getBan, toCollection(HashSet::new)));

--- HIGH, MID, LOW 분류하기 ---
Map<Student.Level, Long> stuByLevel = stuStream
	.collect(groupingBy(s-> {
		if(s.getScore() >= 200) return Student.Level.HIGH;
		else if(s.getScore() >= 100) return Student.Level.MID;
		else return Student.Level.LOW;
		}, counting())
	);	// [MID] - 8명, [HIGH] - 8명, [LOW] - 2명

---  groupingBy()를 여러번 쓰면 다수준 그룹화 가능 ---
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
 	.collect(groupingBy(Student::getHak, // 학년별 그룹화
		groupingBy(Student::getBan) // 반별 그룹화
	));

--- 각 반의 1등을 출력하고 싶다면 ---
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
 	.collect(groupingBy(Student::getHak,
		groupingBy(Student::getBan,
			collectingAndThen(
				maxBy(comparingInt(Student::getScore)),
				Optional::get
			)
		)
	));

--- 각 학년별과 반별로 그룹화한 후 에 성적그룹을 Set에 저장 ---
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
 	.collect(groupingBy(Student::getHak,
		groupingBy(Student::getBan,
			mapping(s-> {
				if(s.getScore() >= 200) return Student.Level.HIGH;
				else if(s.getScore() >= 100) return Student.Level.MID;
				else return Student.Level.LOW;
				}, toSet())
			)
		)
	);
post-custom-banner

0개의 댓글