[Java의 정석] 람다와 스트림 (Lambda & Stream)

Shiba·2023년 6월 22일
post-thumbnail

📓 람다와 스트림 (Lambda & Stream)

📕 람다식 (Lambda Expression)

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

⁕ 함수와 메소드의 차이

근본적으로 동일. 함수일반적 용어. 메소드는 객체지향개념 용어
- 클래스 안에 있으면 메소드, 클래스 밖에 존재하면 함수

🔷 람다식 작성법

1. 메소드의 이름과 반환타입을 제거하고 '->'블럭{}앞에 추가

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

2. 반환값이 있는 경우 식이나 값만 적고 return문 생략 가능(끝에 ;안붙임)

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

3. 매개변수의 타입추론 가능하면 생략가능(대부분의 경우 생략가능)

(int a, int b) -> a > b ? a : b    →  	(a, b) -> a > b ? a : b  

◼ 주의사항

1. 매개변수가 하나인 경우 괄호()생략가능 (타입이 없을때만)

(a) -> a * a            →    	(a) -> a * a // ok
(int a) -> a * a		→		(int a) -> a * a // 에러!!

2. 블럭 안의 문장이 하나뿐 일 때, 괄호{}생략가능 (끝에 ' ; ' 안붙임)

(int i) -> {
	System.out.println(i);(int i) -> System.out.println(i)
}

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

										new Object(){ //익명객체
        									int max(int a, int b) {
(a, b) -> a > b ? a : b    		<-> 			return a > b ? a : b;
											}
                                        }

🔸 함수형 인터페이스

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

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 f = (a, b) -> a > b ? a : b; //람다식을 위한 참조변수

int value = f.max(3,5); // ok!!

◼ 예제

//익명객체를 람다식으로 대체
List<String> list = Arrays.asList{"abc", "aaa", "bbb", "ddd", "aaa"};
Collections.sort(list, new Comparator<String>() {
							public int compare(String s1, String s2) {
                            	return s2.compareTo(s1);
                            }
						});
                        
                        
//다음과 같이 쓸수 있음
interface Comparator<T> {
	compare(T o1, T o2);
}

List<String> list = Arrays.asList{"abc", "aaa", "bbb", "ddd", "aaa"};

Collections.sort(list, (s1,s2) -> s2.compareTo(s1)); //간단하게 쓸 수 있다

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

◻ 매개변수
interface MyFunction{
	void myMethod();
}

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;
    
    
    //위 두줄을 한줄로
    return () -> {};
}

🔷 java.util.function패키지

자주 사용되는 다양한 함수형 인터페이스 제공

//ex
Predicate<String> isEmptyStr = s -> s.length () ==0;
String s = "";

if(isEmptyStr.test(s)) //if(s.length() == 0)와 같음 test()가 람다식이름
	System.out.println("This is an empty String.");

매개변수가 2개인 함수형 인터페이스

◼ 매개변수가 3개인 함수형 인터페이스만들기

//매개변수가 2개인 것 까지는 패키지 안에 존재. 3개부턴 직접 만들어야함
//매개변수가 3개인 함수형 인터페이스만들기

interface TriFunction<T,U,V,R> {
	R apply(T t, U u, V v);
}

매개변수의 타입과 반환타입일치하는 함수형 인터페이스

public interface UnaryOperator<T> extends Function<T, T> {
	static <T> UnaryOperator<T> identity() {
    	return t -> t; // t타입이 t타입으로 반환
    }
}

◼ predicate의 결합

and(), or(), negate()두 Predicate하나로 결합(default 메소드)

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) = i>=100
Predicate<Integer> all = notP.and(q).or(r); // 100 <= i && i < 200 || i%2 == 0
Predicate<Integer> all2 = notP.and(q.or(r)); // 100 <= i && (i < 200 || i%2 == 0)


//사용
System.out.println(all.test(2)); //true (2%2 == 0)
System.out.println(all2.test(2)); //false

등가비교시에는 isEqual()사용

Predicate<String> p = Predicate.isEqual(str1);
Boolean res = p.test(str2); //str1.equals(str2) 와 같음

//두줄을 한줄로
boolean res = Predicate.isEqual(str1).test(str2);

◼ 컬렉션 프레임워크와 함수형 인터페이스

함수형 인터페이스를 사용하는 컬렉션 프레임워크의 메소드
(와일드카드 생략)

//ex

list.forEach(i -> System.out.println(i + " ")); //list의 모든 요소 출력
list.removeIf(x -> x%2 == 0 || x%3 == 0); //list의 2 또는 3의 배수 제거
list.replaceAll(i -> i*10);// list의 모든 요소에 10을 곱합.

//map의 모든 요소를 (k, v)의 형식으로 출력
map.forEach((k,v) -> System.out.println("{" + k+","+v+ "},"));

🔷 메소드 참조(method reference)

하나의 메소드만 호출하는 람다식'메소드 참조'로 간단히 할 수 있다.

종류람다메소드 참조
static메소드 참조(x) -> ClassName.method(x)ClassName::method
인스턴스메소드 참조(obj, x) -> obj.method(x)ClassName::method
특정 객체 인스턴스메소드 참조(x) -> obj.method(x)obj::method(잘 안씀)

◼ 예제 - static메소드 참조

Integer method(String s) {
	return Integer.parseInt(s);
}

int res = obj.method("123");

//람다식
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> s = (i) -> new MyClass(i);

//메소드 참조
Function<Integer, MyClass> s = MyClass::new;
◻ 배열과 메소드 참조
Function<Integer, int[]> f = x -> new int[x]; //람다식
Function<Integer, int[]> f2 = int[]::new; //메소드 참조

📙 스트림 (Stream)

다양한 데이터 소스표준화된 방법으로 다루기 위한 것

String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr); //문자열 배열이 소스인 스트림

🔷 스트림 만들기

◼ 컬렉션으로 만들기

Collection인터페이스의 stream()으로 컬렉션을 스트림으로 변환

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

List<Integer> list = Arrays.asList{1, 2, 3, 4, 5};
Stream<Integer> intStream = list.stream(); //list를 스트림으로 변환

//스트림의 모든 요소 출력
intStream.forEach(System.out::print); //12345
intStream.forEach(System.out::print); //에러!! 스트림이 이미 닫힘

◼ 배열로 만들기

객체배열로 스트림 생성하기

Stream<T> Stream.of(T...values) //가변인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int from, int to) //from ~ (to-1)
//예제

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

기본형 배열로 스트림 생성하기

IntStream IntStream.of(int... values) //Stream이 아닌 기본형스트림
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int from, int to) //from ~ (to-1)

🔸 기본형 스트림 - IntStream, LongStream, DoubleStream ...

  • 오토박싱&언박싱비효율제거(Stream<Integer> 대신 IntStream)
  • 숫자와 관련된 유용한 메소드를 Stream<T>보다 더 많이 제공
    - sum(), count(), average()...

◼ 임의의 수(난수)로 스트림 만들기

난수를 요소로 갖는 스트림 생성하기

//예제

IntStream intStream = new Random().ints(); //무한 스트림
intStream.limit(5).forEach(System.out::println); //5개의 요소만 출력

IntStream intStream = new Random().ints(5); //크기가 5인 난수 스트림 반환


//Random클래스에 있는 ints(), longs(), doubles()
//무한 스트림을 생성함

지정된 범위난수를 요소로 갖는 스트림을 생성하는 메소드

//무한 스트림
IntStream ints(int begin, int end)
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, double end)
//유한스트림
IntStream ints(long streamSize, int begin, int end)
LongStream longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)

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

특정 범위의 정수요소로 갖는 스트림 생성 (IntStream, LongStream)

IntStream IntStream.range(int begin, int end)
IntStream IntStream.rangeClosed(int begin, int end) //end도 포함
//예제

IntStream IntStream.range(1, 5) // 1, 2, 3, 4
IntStream IntStream.rangeClosed(1, 5) // 1, 2, 3, 4, 5  (end도 포함)

◼ 람다식으로 스트림 만들기

람다식을 소스로 하는 스트림 생성

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) //이전 요소에 종속적
static <T> Stream<T> generate(Supplier<T> s) //이전 요소에 독립적
//예제

//iterate - 초기값이 0(seed)이고 2씩 더해지는 등차수열 
Stream<Integer> evenStream = Stream.iterate(0, n -> n+2); //0, 2, 4, 6, ...

//generate - 1로만 이루어지는 무한 스트림
Stream<Integer> oneStream = Stream.generate(() -> 1); // 1, 1, 1, 1, 1, 1 ....

◼ 파일로 스트림 만들기

파일을 소스로 하는 스트림 생성

Stream<Path> Files.list(Path.dis) //Pathh는 파일 또는 디렉토리
//line단위
Stream<String> Files.lines(Path path)
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클레스의 메소드

◼ 비어있는 스트림 만들기

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

🔷 스트림의 연산

⬜ 중간연산

연산결과가 스트림인 연산. - 반복사용가능

◼ 스트림 자르기

◻ skip()
Stream<T> skip(long n) //앞에서부터 n개 건너뛰기

IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910
intStream.skip(3).foreach(System.out::print); // 45678910
◻ limit()
Stream<T> limit(long maxSize) //maxSize 이후의 요소는 잘라냄

IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910
intStream.limit(5).foreach(System.out::print); // 12345

◼ 스트림 걸러내기

◻ filter()
Stream<T> filter(Predicate<? super T> predicate) //조건에 맞지 않는 요소 제거

IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910
intStream.filter(i -> i%2==0).foreach(System.out::print); // 246810
◻ distinct()
Stream<T> distinct() //중복 제거

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

◼ sorted()

스트림 정렬하기

Stream<T> sorted() //스트림 요소의 기본 정렬(Comparable)로 정렬
Stream<T> sorted(Comparator<T> comparator) //지정된 Comparator로 정렬
문자열 스트림 메소드 정렬 방법

🔸 comparing()

Comparatorcomparing()으로 정렬 기준 제공

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

studentStream.sorted(Comparator.comparing(Student::getBan)) //반별로 정렬
			 .forEach(System.out::println);

추가 정렬 기준을 제공할 때는 thenComparing()을 사용

thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExtractor)
thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)

studentStream.sorted(Comparator.comparing(Student::getBan) //반별로 정렬
					.thenComparing(Student::getTotalScore) //총점별로 정렬
                    .thenComparing(Student::getName)) //이름별로 정렬
                    .forEach(System.out::println);

◼ peek()

스트림 요소 엿보기

Stream<T> peek(Consumer<? super T> action)    //중간 연산(스트림의 요소를 소비x)
void      forEach(Consumer<? super T> action) //최종 연산(스트림의 요소를 소비o)


fileStream.map(FileName::getName) //Stream<File> → Stream<String>
		  .filter(s -> s.indexOf('.')!=-1) //확장자가 없는 것은 제외
          .peek(s->System.out.printf("filename=%s%n", s)) //파일명 출력-소비x
          .map(s->s.substring(s.indexOf('.')+1)) //확장자만 추출
          .peek(s->System.out.printf("extension=%s%n", s)) //확장자를 출력-소비x
          .forEach(System.out::println); //최종연산 스트림을 소비

◼ map()

스트림 요소 변환

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




Stream<File> fileStream = Stream.of(new File("Ex1.java"), new file("Ex1")
         new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt")); //파일스트림 생성
  
Stream<String> filenameStream = fileStream.map(File::getName); //파일스트림 → 파일이름스트림
filenameStream.forEach(System.out::println); //스트림의 모든 파일의 이름을 출력

◼ flatmap()

스트림의 스트림스트림으로 변환

Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi" },
										new String[]{"ABC", "GHI", "JKL"})
                                        
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream); //스트림의 스트림

//스트림으로 변환 - 여러개의 문자열 배열을 하나의 문자열배열로 변환!
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); // Arrays.stream(T[])

◼ 최종연산

연산결과스트림이 아닌 연산. - 단 한번만 적용가능

🔸 Optional<T>

T타입 객체래퍼클래스

public final class Optional<T>{
	private final T value; //T타입의 참조변수
    	...
}
◻ Optional<T> 객체 생성하기
String str = "abc";
Optional<String> optval = Optional.of(str);
Optional<String> optval = Optional.of("abc");
Optional<String> optval = Optional.of(null);		  //NullpointerException 발생
Optional<String> optval = Optional.ofNullable(null);  // ok

null대신 빈 Optional<T> 객체를 사용권장

Optional<String> optVal = null; //널로 초기화하지 말고
Optional<String> optVal = Optional.empty(); //빈 객체로 초기화!
◻ get(), orElse(), orElseGet(), orElseThrow()

Optional객체 값 가져오기

Optional<String> optVal = Optional.of("abc");
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);  //null이면 예외발생
◻ isPresent()

null값인지 알아내기 (null이 아니면 true, null이면 false)

ifPresent(Consumer) //null이 '아닐때'만 작업 수행. null이면 아무 일도 안함
	Optional.ofNullable(str).ifPresent(System.out::println);
◻ 기본형 값을 감싸는 래퍼클래스
public final class OptionalInt { //OptionalInt, OptionalLong, OptionalDouble
	...
    private final boolean isPresent; //값이 저장되어 있으면 true
    private final int value; //기본형타입의 변수
}
◻ Optional기본형 값 가져오기
Optional클래스값을 반환하는 메소드
Optional<T>T get()
OptionalIntint getAsInt()
OptionalLonglong getAsLong()
OptionalDoubledouble getAsDouble()
◻ 빈 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

◼ forEach(), forEachOrdered()

모든 요소지정된 작업 수행

void forEach(Consumer<? super T> action)            //병렬스트림인 경우 순서 보장x
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

//병렬로 처리
IntStream.range(1, 10).parallel().forEach(System.out::print);  //683295714 - 순서보장x
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print); //123456789 - 순서보장o

◼ 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 allFailedStu = stuStream.allMatch(s -> s.getTotalScore() <= 100); //모든 학생이 낙제자인가
boolean hasFailedStu = stuStream.anyMatch(s -> s.getTotalScore() <= 100); //낙제자가 한명이라도 존재하는가
boolean noneFailedStu = stuStream.noneMatch(s -> s.getTotalScore() <= 100); //낙제자가 한명도 없는가

◼ findFirst(), findAny()

조건에 일치하는 요소 찾기

//null값을 반환할 수도 있어서 Optional객체 사용
Optional<T> findFirst() //첫 번째 요소 반환. 순차 스트림에 사용
Optional<T> findAny() //아무거나 하나 반환. 병렬 스트림에 사용


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

◼ reduce()

요소줄여가며 누적연산 수행하기

/*
identity - 초기값
accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산
combiner - 병렬처리된 결과를 합치는데 사용할 연산 (병렬 스트림)
*/


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)



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()
◻ 종합 예제
String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr); //문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(); //걸러내기 (중간연산)
Stream<String> distinctedStream = stream.distinct(); //중복제거 (중간연산)
Stream<String> sortedStream = stream.sort(); //정렬 (중간연산)
Stream<String> limitedStream = stream.limit(5); //스트림 자르기 (중간연산)

int total = stream.count(); //요소 개수 세기 (최종연산)

🔸 collect()와 Collectors

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

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

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

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을 반환
    ...
}

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

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

◼ toList(), toSet(), toMap(), toCollection(), toArray()

스트림을 컬렉션으로 변환
- 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(ArrayList::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(); //에러. 자동형변환이 안됨. (Student[])사용
Object[] stuNames = studentStream.toArray(); // ok

◼ counting(), summingInt(), maxBy(), minBy(), ...

스트림의 통계정보 제공

//counting()
long count = stuStream.count(); //항상 전체를 카운팅
long count = stuStream.collect(counting()); //그룹별로 카운팅이 가능

//summingInt()
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); // IntStream의 sum()
long totalScore = stuStream.collect(summingInt(student::getTotalScore));

//maxBy()
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()

스트림을 리듀싱 (Collectors클래스의 메소드)

Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<T> op) //mapper로 변환후 리듀싱


//1~46까지의 수를 중복없이 무작위로 6개만 추출한 int스트림
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)); //그룹별 리듀싱 가능

◼ joining()

문자열 스트림의 요소를 모두 연결

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

◼ 스트림의 그룹화와 분할

◻ 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), counting()); //학생들을 성별로 분할후 그룹별 카운팅
System.out.println("남학생 수 : " + stuNumBySex.get(true));
System.out.println("여학생 수 : " + stuNumBySex.get(false));


//다중분할
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream
					.collect(partitioningBy(Student::isMale), //성별로 분할
                    partitioningBy(s->s.getScore() < 150)); //성적으로 한번더 분할
                    
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>> stuByBam = stuStream //학생을 반별로 그룹화
				.collect(groupingBy(Student::getBan, toList())); //toList() 생략가능
                
                
//다중 그룹화
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream    //다중그룹화
			.collect(groupingBy(Student::getHak,		//학년별 그룹화
            		 	groupingBy(Student::getBan)) 	//반별 그룹화
            );

🔷 스트림의 특징

  • 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.
List<Integer> list = Arrays.asList{3, 1, 5, 4, 2};
List<Integer> sortedList = list.stream().sorted()
								.collect(Collection.toList());
System.out.println(list); // 3, 1, 5, 4, 2 - 변경x
System.out.println(sortedList); // 1, 2, 3, 4, 5
  • 스트림은 Iterator처럼 일회용이다.(필요하면 다시 스트림 생성)
strStream.forEach(System.out.println); //모든 요소 화면에 출력(최종연산)
int numOfStr = strStream.count(); //에러!! 스트림은 이미 닫힘
  • 최종연산 전까지 중간연산이 수행되지 않는다. - 지연된 연산
IntStream intStream = new Random().ints(1,46); // 1 ~ 45범위의 무한 스트림
IntStream.distinct().limit(6).sorted() //중간연산 - 바로실행x(지연된 연산)
		.forEach(i -> System.out.println(i + ",")); //최종연산
  • 스트림은 작업을 내부 반복으로 처리한다.
for(String str : strList)
	System.out.println(str);     →  stream.forEach(System.out::println);


//스트림은 다음과 같이 내부에 넣어서 처리
void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action); //매개변수의 null 체크
    
    for(T t : src) //내부 반복(for문을 메소드 안으로 넣음)
    	action.accept(T);
}
  • 스트림의 작업을 병렬로 처리 - 병렬스트림
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬스트림으로 전환
					.mapToInt(s -> s.length()).sum(); //모든 문자열 길이 합
profile
모르는 것 정리하기

0개의 댓글