스트림

김설영·2022년 5월 3일
0

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

  • 데이터소스란?

    컬렉션이나 배열과 같이, 여러 데이터를 저장하고 있는 것들을 이야기함.

  • 다양한 데이터 소스를 다루는 것이, 진정한 통일을 이룸.

  • 다양한 데이터 소스로부터, "스트림"을 만들 수 있음.
    중간연산(n번) -> 최종연산(1번)

  • 스트림 생성하는 법

  • 스트림 : 데이터의 연속적인 흐름

  • 스트림이 제공하는 기능 : 중간 연산과 최종 연산
    1. 스트림 만들기
    2. 중간 연산 (0번 ~ n번) : 연산 결과가 스트림인 연산. 반복적으로 적용 가능.
    3. 최종 연산 (1번) : 연산 결과가 스트림이 아닌 연산. 단 한번만 적용 가능하다. (스트림 요소를 소모함)
    4. 결과 얻음

// Collection에는 stream() 메서드가 있다.
Stream<T> Collection.stream()  // 스트림으로 반환

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

Stream<Integer> intStream = list.stream();  
Stream<String> strStream = Stream.of(new String[] {"a", "b", "c"});
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2);
Stream<Double> randomStream = Stream.generate(Math::random);
IntStream intStream = new Random().ints(5);  
 
//      중복제거   5개자르기   정렬               출력
stream.distinct().limit(5).sorted().forEach(System.out::println)
//////{----------중간연산-----------}{----------최종연산----------}]

String[] strArr = { "dd", "aaa", "CC", "cc", "b" };

Stream<String> stream = Stream.of(strArr);  // 1. 문자열 배열이 소스인 스트림 생성

													  // 2. 중간연산
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();  // 3. 요소 개수 세기 (최종연산)

스트림의 특징

  • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 변경하지 않는다.

  • 스트림은 Iterator처럼 일회용이다. 필요하면 다시 스트림을 생성해야 한다.
    최종연산 시, 스트림의 요소를 하나하나 꺼내어 "소모"하기 때문에, 최종 연산 작업이 끝나면 스트림이 닫인다.

    strStream.forEach(System.out::println);  // 모든 요소를 화면에 출력 (최종 연산)
    int numOfStr = strStream.count();		 // 에러. 스트림이 이미 닫혔기 때문에, 실행되지 않음.
  • 최종 연산 전 까지, 중간 연산이 수행되지 않는다. - 지연된 연산

    //                    난수발생 스트림 -> 난수를 끝도 없이 줌. "무한 스트림"
    IntStream intStream = new Random().ints(1, 46);    // 1~45 범위의 무한 스트림
    intStream.idtinct().limit(6).sorted()			     // 중간 연산
    		   .forEach(i -> System.out.print(i + ",")); // 최종 연산
             
    -> 중간 연산을 체크만 해두고 있다가, 나중에 연산함.
  • 스트림은 작업을 내부 반복으로 처리한다.

    for (String str : strList)
        System.out.println(str);
    
    // 위 코드 보다는, 아래 한줄로 처리함! 성능은 비 효율적이나, 코드가 간결해진다!
    stream.forEach(System.out::println);
  • 스트림의 작업을 병렬로 처리 : 병렬 스트림

Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel()  // 병렬 스트림으로 전환 (속성만 변경)
				.mapToInt(s -> s.length()).sum();  // 모든 문자열의 길이의 합
  • 기본형 스트림 : IntStream, LongStream, DoubleStream...
    • 오토박싱 & 언박싱의 비효율 제거.
      Stream<Integer> 대신, IntStream 사용
      Stream<Integer>는 기본형이 Integer(참조형)으로 자동으로 변환되는 오토박싱 혹은 반대의 작용인 언박싱이 일어남 -> 비효율적
      대신, IntStream을 사용할 경우, 위와 같은 오토박싱, 언박싱이 발생하지 않기 때문에 효율적임.
    • 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공

스트림 만들기 - 컬렉션

  • 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 startInclusive, int endExclusive)  // 배열의 일부만 사용. from <= ~ < to. index
    
    // ex
    Stream<String> strStream = Stream.of("a", "b", "c");
    Stream<String> strStream = Stream.of(new String[] {"a", "b", "c"});
    Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"});
    Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3);
  • 기본형 배열로부터 스트림 생성하기 (기본형 스트림)

    IntStream IntStream.of(int... values)  // Stream이 아니라 IntStream
    IntStream IntStream.of(int[])
    IntStream Arrays.stream(int[])
    IntStream Arrays.stream(int[] array, int startInclusive, int endExclusive)

스트림 만들기 - 임의의 수

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

    IntStream intStream = new Random().ints();			// 무한 스트림
     intStream.limit(5).forEach(System.out::println);   // 5개의 요소만 출력한다
    
     IntStream intStream = new Random().ints(5);  // 크기가 5인 난수 스트림 반환
    
     // Random class 안에 정의되어 있는 메서드의 범위 (전 범위)
     Integer.MIN_VALUE <=   ints()  <= Integer.MAX_VALUE
        Long.MIN_VALUE <=  longs()  <= Long.MAX_VALUE
                   0.0 <= doubles() < 1.0
                 
     // 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드(Random 클래스)
     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)			// begin <= x < end
    IntStream IntStream.rangeClosed(int begin, int end)  	// begin <= x <= end
    
    IntStream intStream = IntStream.range(1, 5);  // 1, 2, 3, 4
    IntStream intStream = IntStream.rangeClosed(1, 5);  // 1, 2, 3, 4, 5

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

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

  • iterate() : 이전 요소를 seed로 해서, 다음 요소를 계산한다.
    값이 있어야 람다식을 계산 할 수 있기 때문에, 초기값을 설정해줘야 한다.
    계속 값을 생산하는 무한스트림

  • generate() : seed를 사용하지 않는다. (초기값 필요 없음!)
    각 요소가 서로 독립적이다. (이전 요소와 서로 상관이 없음)

출처 : 자바의정석 남궁성 선생님 유튜브

// 람다식을 이용해서 계속 스트림을 만들어낸다. -> 무한스트림!
//                             ↓ 초기값
static<T> Stream<T> iterate(T seed, UnaryOperator<T> f)  // 이전 요소에 종속적
static<T> Stream<T> generate(Supplier<T> s)  // 이전 요소에 독립적

Stream<Integer> evenStream = Stream.iterate(0, n->n+2);

Stream<Double> randomStream = Stream.generate(Math::random);  // 랜덤 값 생성
Stream<Integer> oneStream = Stream.generate(() -> 1);  // 계속 1이 나오는 무한스트림

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

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

  • 로그파일 분석 or 다량의 텍스트 파일 분석

  • 비어있는 스트림 생성

Stream<Path> Files.list(Path dir)  // Path : file or directory

// lines() : 파일 내용을 라인 단위로 읽어서, String 으로 만든 후, String Stream으로 만듦
Stream<String> Files.lines(Path path)
Stream<String> Files.lines(Path path, Cahrset cs)
Stream<String> lines()  // BufferedReader의 메서드

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

스트림의 연산

  • 스트림이 제공하는 기능 : 중간 연산과 최종 연산

    • 중간 연산 : 연산 결과가 스트림인 연산. 반복적으로 적용 가능
    • 최종 연산 : 연산 결과가 스트림이 아닌 연산. 단 한번만 적용 가능 (스트림의 요소를 소모)
  • Comparator : 정렬 기준을 제공해준다. 제공해주지 않으면 객체가 가진 기본 정렬 기준 사용.

  • 중간 연산
    출처 : 자바의정석 남궁성 선생님 유튜브
    - 핵심 : Map & flatMap

  • 최종 연산
    출처 : 자바의정석 남궁성 선생님 유튜브
    - forEachOrdered(Consumer< ? super T > action) : 순서 유지. 병렬 스트림으로 처리할 때에도, 순서를 유지하게 해주는 것
    - Optional<T> : Wrapper 클래스 -> 어떤 타입의 값(작업 결과)을 감싸고 있는 클래스 -> 래퍼클래스 참고자료
    - findAny() : 병렬 스트림에서 사용. // filter(조건)랑 같이 쓰임
    - findFirst() : 직렬 스트림에서 사용. // filter(조건)랑 같이 쓰임
    - 핵심 : reduce(sum, count...) & collect(그룹화하여 계산)


스트림의 중간 연산

  • sorted()에 파라미터로 넘겨줄 수 있는 Comparator들

  • Comparator의 comparing()으로 정렬 기준 제공
    comparing(Function< T, U > keyExtractor)
    comparing(Function< T, U > keyExtractor, Coumparator< U > KeyComparator)

  • comparing()의 반환 타입은 Comparator!

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, Comparaotr< U > keyComp)
studentStream.sorted(Comparator.comparing(Student::getBan)
					.thenComparing(Student::getTotalScore)
                    .thenComparing(Student::getName))
                    .forEach(System.out::println);

map()

  • 스트림의 요소 변환하기

  • ex)
    -------------- map(File::getName) : File 객체의 file 이름을, String으로 변환 후 반환
    Stream< File > ------------------> Stream< String >

  • 타입을 변환할 수 있을 뿐만 아니라, 요소의 값을 하나 하나 변환할 수 있다.

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

// ex
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt");  // file Stream

//												getName이 파일 이름을 문자열로 반환. (File f) -> f.getName() // 결과가 String
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println);  // 스트림의 모든 파일의 이름을 출력

// ex2 : 파일 확장자(대문자)를 중복없이 뽑아내기
fileStream.map(File::getName)	// Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.') != -1)  // '.'이 없는 파일 이름(확장자 x)은 제외. indexOf는 해당 값이 없을 경우, -1을 반환한다.
.map(s -> s.substring(s.indexOf('.') + 1))  // Stream<String> -> Stream<String>. substring(s.indexOf('.') + 1) : '.' 뒤의 문자부터 끝까지 잘라냄. index를 넘겨줘야 함.
.map(String::toUpperCase)  // Stream<String> -> Stream<String> 모두 대문자로 변경
.distinct()  // 중복 제거
.forEach(System.out::print);  // JAVABAKTXT

peek()

  • forEach()랑 똑같은 역할을 하지만, peek()은 중간연산자이기 때문에, 스트림을 소비하지 않는다!

  • 즉, Stream< T > 를 반환함!

  • 작업 중간 중간, 잘 수행이 되었는지 확인할 때 사용. (디버깅 용도)

Stream<T> peek(Consumer<? super T> action) // 스트림 소비 x
void   forEach(Consumer<? super T> action) // 스트림 소비 o

fileStream.map(File::getName)
        .filter(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)) // 확장자 출력
        .map(String::toUpperCase)
        .distinct()
        .forEach(System.out::println);

flatmap()

  • 변환. 스트림의 스트림 -> 스트림으로 변환
    의도한 바와 다르게, Stream<String[]> -> Stream<Stream<String>>이 되어버렸음.
    의도한 내용은 위와 같음. Stream<String[]> -> Stream<String>
    출처 : 자바의정석 남궁성 선생님 유튜브
// 문자열 배열 스트림
Stream<String[]> strArrStrm = Stream.of(new String[] {"abc", "def", "ghi"},
										new String[] {"ABC", "GHI", "JKLMN"});

// 배열을 Stream<String>으로 바꾸고자 함.
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);  // 배열을 스트림으로 바꿈.
// 												   Arrays::stream == (arr) -> Arrays.stream(arr) 
// 배열을 받아, 스트림으로 변환 : String[] -> Stream<String>
// 즉, Stream<String[]> -> Stream<Stream<String>> 이 되어버린다.

// 위와 같이 할 경우, Stream 안의 Stream이 되어버림. 
// 의도한것은 Stream<String[]> -> Stream<String> 이었는데, 
// Stream<String[]> -> Stream<Stream<String>>이 되어버렸음.
// 이럴 경우, flatMap()을 사용해야 함.
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream);  // 여러개의 문자열 배열을, 하나의 문자열 배열인 것 처럼 변환!
// Stream<Stream<String>> -> Stream<String>

Optional<T>

  • T 타입 객체의 래퍼 클래스 : Optional<T>
public final class Optional<T> {
	private final T value;  // T 타입의 참조 변수 : 모든 종류의 객체 저장 가능. null도 가능
}
  • null을 직접 다루는 것은 위험하다. (NullPointerException이 발생할 수 있음)
    -> 객체 안에 담아서 간접적으로 null을 다루기 위해 래퍼클래스 사용.

  • null을 직접 다루려면, null 체크 필수. (if문 필수) -> 코드가 지저분해짐.

  • Optional을 이용해서, null을 Optional 객체에 넣어서 다룸. 값이 null이더라도, 참조 변수에는 null이 아닌, 객체의 주소가 들어가있음. -> 항상 null이 아님 -> 위의 두 가지 문제점이 사라짐.

  • null을 직접 다루지 않는 객체 : String(초기화를 ""로 함. 길이가 0인 char[] 배열), 각종 배열들(멤버 변수. int[] arr = new int[0];)

  • Optional<T> 객체 생성

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

String str = "abc";
Optional<String> optVal = Optional.of(str);  // optVal -> Optional(= str) -> "abc"
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null);  // NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null);  // OK

Optional<String> optVal = null;  // 바람직하지 않은 초기화 방식.
Optional<String> optVal = Optional.<String>empty();  // 빈 객체로 초기화. <String>생략 가능
  • Optional 객체의 값 가져오기
    get(), orElse(), orElseGet(), orElseThrow()

  • isPresent() : Optional 객체의 값이 null이면 false, 아니면 true

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
if(Optional.ofNullable(str).isPresent()) {  // str으로 만든 Optional 객체의 값이 null인가? -> null이 아닐 때만 작업 수행
	System.out.println(str);
}
// ifPresent(Consumer) - null이 아닐 때만 작업 수행, null이면 아무것도 안함.
Optional.ofNullable(str).ifPresent(System.out::println);
  • OptionalInt, OptionalLong, OptionalDouble
    • 기본형 값을 감싸는 래퍼 클래스 (성능 때문에 사용함)
    • 값 가져오기 : getAsInt(), getAsLong(), getAsDouble()
    • 빈 Optional 객체와의 비교 : 아래 코드 참조
public final class OptionalInt {
	...
    private final boolean isPresent;  // 값이 저장되어 있으면 true
    private final int value;  // int 타입의 변수
}

// 빈 Optional 객체와의 비교
OptionalInt opt = OptionalInt.of(0);  // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty();  // OptionalInt에 0을 저장 

// 0이 저장된 객체와, empty()가 적용된 객체 간의 구별을 어떻게 함? -> isPresent 이용
opt.isPresent() : true
opt2.isPresent() : false
opt.equals(opt2) : false

스트림의 최종연산

  • forEach(), forEachOrdered() : 스트림의 모든 요소에 지정된 작업 수행
forEach(Consumer<? super T> action)  // 병렬 스트림인 경우, 순서가 보장되지 않음. 더 빠름.
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()
//			↓ 조건식
allMatch(Predicate<? super T> predicate)   // 모든 요소가 조건을 만족시키면 true
anyMatch(Predicate<? super T> predicate)   // 한 요소라도 조건을 만족시키면 true
noneMatch(Predicate<? super T> predicate)  // 모든 요소가 조건을 만족시키지 않으면 true

// ex
hasFailedStu = stuStream.anyMatch(s -> s.getTotalScore() <= 100);  // 낙제자가 있는지?
  • 조건에 일치하는 요소 찾기 : findFirst(), findAny()
// 결과가 null일 수 있기 때문에, Optional로 반환
Optional<T> findFirst()  // 첫 번째 요소를 반환.  순차 스트림에 사용.
Optional<T> findAny()	 // 아무거나 하나를 반환. 병렬 스트림에 사용.

// ex
Optional<Student> result = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst();  // filter랑 같이 씀. 조건을 만족하는 첫번째 요소 반환.
Optional<Student> result = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny();  // filter랑 같이 씀. 조건을 만족하는 아무거나 하나를 반환.
  • reduce() : 가장 중요!
    • 스트림의 요소를 하나씩 줄여가면서 누적 연산 수행. (accumulator)
    • identity : 초기값
    • accumulator : 이전 연산 결과와 스트림의 요소에 수행할 연산
    • combiner : 병렬 처리된 결과를 합치는 데 사용할 연산(병렬 스트림)
    • count(), sum(), max(), min(), collect() 전부 reduce()를 이용해서 만들어짐
// 복습.
BinaryOperator<T> : Type T의 인자 두개를 받고, 동일한 Type T 객체를 리턴.

Optional<T> reduce(BinaryOperator<T> accumulator)  // identity가 없을 수 있으므로 Optional로 반환. 아래 식과 동일한것.
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> combiner)

// ex : int reduce(int identity, IntBinaryOperator op)
// a = identity, b = stream의 요소
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);

// (a, b) -> a + 1 과 (a, b) -> a + b 의 구동 방식. 내부적으로 다음과 같이 수행됨.
int a = identity;  // 누적 결과 저장 변수. (초기화)

for (int b : stream)
	a = a + 1;  // (a, b) -> a + 1  ==  count()
	a = a + b;  // (a, b) -> a + b  ==  sum()

Collect() & Collectors

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

  • reduce() vs collect()

    • reduce() : 전체를 리듀싱 (identity, accumulator)
    • collect() : 그룹 별로 나눠서 리듀싱 (supplier, accumulator)
  • Collector는 수집(collect)에 필요한 메서드를 정의해놓은 인터페이스이다.

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

    • 변환 : mapping(), toList(), toSet(), toMap(), toCollection(), ...
    • 통계 : counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarzingInt(), ...
    • 문자열 결합 : joining()
    • 리듀싱 : reducing()
    • 그룹화와 분할 : groupingBy(), partitioningBy(), collectingAndThen(), ...
  • 우리가 직접 구현 할 필요가 없다!

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

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

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

  • 복습! : 컬렉션 -> List, ArrayList, Set, HashSet...

  • 스트림을 컬렉션으로 변환 : 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  //  사람 객체 -> 주민번호 -> key   , 사람 객체 -> 사람 객체 -> value (항등 함수)
							.collect(Collectors.toMap(p -> p.getRegId, p-> p)) // Stream<Person> -> Map<String, Person>
                            
// -> key : 주민번호, value : Person p 를 저장 (객체의 주소)
  • 스트림을 배열로 변환 : toArray() -> 기본적으로 Object[]를 반환.
    Student[] stuNames = studentStream.toArray(Student[]::new);
    Student[] stuNames = studentStream.toArray(); -> 에러남
    Object[] stuNames = studentStream.toArray(); -> 가능

스트림의 통계

  • 스트림의 통계 정보 제공 : counting(), summingInt(), maxBy(), minBy()
long count = stuStream.count(); -> 요소의 개수 확인 (전체 카운팅. 스트림의 모든 요소)
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)));
-> Comparator: 비교 기준. 비교 기준으로 Student::getTotalScore를 넘겼음.

Reducing()

  • 스트림을 리듀싱 : reducing() -> 그룹별 리듀싱!
    reduce()는 전체 리듀싱만 가능하다.

  • 리듀싱 : sum(), count()... 등

BinaryOperator<T> op : accumulator

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. 변환 작업이 필요할 경우, 3번째 방식 사용.
// Function<T, U> mapper : 변환 작업

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() = Collectors.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()으로 결합

스트림의 그룹화와 분할

  • partitiongBy() : 스트림을 2분할 한다.
    Collector partitioningBy(Predicate predicate)
    Collector partitioningBy(Predicate predicate, Collector downstream)
  • groupingBy() : 스트림을 n분할 한다.
    Collector groupingBy(Function classifier)
    Collector groupingBy(Function classifier, Collector downstream)
    Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
profile
블로그 이동하였습니당! -> https://kimsy8979.tistory.com/

0개의 댓글