Skip to content

Latest commit

Β 

History

History
714 lines (508 loc) Β· 27.4 KB

Java 8 Stream.md

File metadata and controls

714 lines (508 loc) Β· 27.4 KB

Java 8 Stream

Assembled by GimunLee (2020-01-06)


Goal

  • java 8 Stream에 λŒ€ν•΄ μ„€λͺ…ν•  수 μžˆλ‹€.

Stream

java 8 μ—μ„œ μΆ”κ°€ν•œ Stream은 λžŒλ‹€λ₯Ό ν™œμš©ν•  수 μžˆλŠ” 기술 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€. Java 8 μ΄μ „μ—λŠ” λ°°μ—΄ λ˜λŠ” μ»¬λ ‰μ…˜ μΈμŠ€ν„΄μŠ€λ₯Ό λ‹€λ£¨λŠ” 방법은 for λ˜λŠ” foreach 문을 λŒλ©΄μ„œ μš”μ†Œ ν•˜λ‚˜μ”©μ„ κΊΌλ‚΄μ„œ λ‹€λ£¨λŠ” λ°©λ²•μ΄μ—ˆμŠ΅λ‹ˆλ‹€. κ°„λ‹¨ν•œ 경우라면 μƒκ΄€μ—†μ§€λ§Œ 둜직이 λ³΅μž‘ν•΄μ§ˆμˆ˜λ‘ μ½”λ“œμ˜ 양이 λ§Žμ•„μ Έ μ—¬λŸ¬ 둜직이 μ„žμ΄κ²Œ 되고, λ©”μ†Œλ“œλ₯Ό λ‚˜λˆŒ 경우 루프λ₯Ό μ—¬λŸ¬ 번 λ„λŠ” κ²½μš°κ°€ λ°œμƒν•©λ‹ˆλ‹€.

Stream은 λ°μ΄ν„°μ˜ 흐름 μž…λ‹ˆλ‹€. λ°°μ—΄ λ˜λŠ” μ»¬λ ‰μ…˜ μΈμŠ€ν„΄μŠ€μ— ν•¨μˆ˜ μ—¬λŸ¬ 개λ₯Ό μ‘°ν•©ν•΄μ„œ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό ν•„ν„°λ§ν•˜κ³  κ°€κ³΅λœ κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ λžŒλ‹€λ₯Ό μ΄μš©ν•΄μ„œ μ½”λ“œμ˜ 양을 쀄이고 κ°„κ²°ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 즉, λ°°μ—΄κ³Ό μ»¬λ ‰μ…˜μ„ ν•¨μˆ˜ν˜•μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

또 ν•˜λ‚˜μ˜ μž₯점은 κ°„λ‹¨ν•˜κ²Œ λ³‘λ ¬μ²˜λ¦¬(multi-threading) κ°€ κ°€λŠ₯ν•˜λ‹€λŠ” μ μž…λ‹ˆλ‹€. ν•˜λ‚˜μ˜ μž‘μ—…μ„ λ‘˜ μ΄μƒμ˜ μž‘μ—…μœΌλ‘œ 잘게 λ‚˜λˆ μ„œ λ™μ‹œμ— μ§„ν–‰ν•˜λŠ” 것을 병렬 처리(parallel processing)라고 ν•©λ‹ˆλ‹€. 즉 μ“°λ ˆλ“œλ₯Ό μ΄μš©ν•΄ λ§Žμ€ μš”μ†Œλ“€μ„ λΉ λ₯΄κ²Œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Stream에 λŒ€ν•œ λ‚΄μš©μ€ 크게 μ„Έ κ°€μ§€λ‘œ λ‚˜λˆŒ 수 μžˆμŠ΅λ‹ˆλ‹€.

  1. μƒμ„±ν•˜κΈ° : Stream instance 생성
  2. κ°€κ³΅ν•˜κΈ° : 필터링(filtering) 및 맡핑(mapping) λ“± μ›ν•˜λŠ” κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄κ°€λŠ” 쀑간 μž‘μ—…(intermediate operations)
  3. κ²°κ³Ό λ§Œλ“€κΈ° : μ΅œμ’…μ μœΌλ‘œ κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄λ‚΄λŠ” μž‘μ—…(terminal operations)

μƒμ„±ν•˜κΈ°

보톡 λ°°μ—΄κ³Ό μ»¬λ ‰μ…˜μ„ μ΄μš©ν•΄μ„œ μŠ€νŠΈλ¦Όμ„ λ§Œλ“€μ§€λ§Œ 이 외에도 λ‹€μ–‘ν•œ λ°©λ²•μœΌλ‘œ μŠ€νŠΈλ¦Όμ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ°°μ—΄ 슀트림

μŠ€νŠΈλ¦Όμ„ μ΄μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¨Όμ € 생성을 ν•΄μ•Ό ν•©λ‹ˆλ‹€. μŠ€νŠΈλ¦Όμ€ λ°°μ—΄ λ˜λŠ” μ»¬λ ‰μ…˜ μΈμŠ€ν„΄μŠ€λ₯Ό μ΄μš©ν•΄μ„œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. 배열은 λ‹€μŒκ³Ό 같이 Arrays.stream λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3); // 1~2 μš”μ†Œ [b, c]

μ»¬λ ‰μ…˜ 슀트림

μ»¬λ ‰μ…˜ νƒ€μž…(Collection, List, Set)의 경우 μΈν„°νŽ˜μ΄μŠ€μ— μΆ”κ°€λœ Default Method Stream 을 μ΄μš©ν•΄μ„œ μŠ€νŠΈλ¦Όμ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

public interface Collection<E> extends Iterable<E> {
  default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
  } 
  // ...
}

그러면 λ‹€μŒκ³Ό 같이 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream(); // 병렬 처리 슀트림

λΉ„μ–΄ μžˆλŠ” 슀트림

λΉ„μ–΄ μžˆλŠ” 슀트림(empty stream)도 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. 빈 μŠ€νŠΈλ¦Όμ€ μš”μ†Œκ°€ 없을 λ•Œ null λŒ€μ‹  μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

public Stream<String> streamOf(List<String> list) {
	return list == null || list.isEmpty()
		? Stream.empty()
		: list.stream();
}

Stream.builder()

λΉŒλ”(Builder)λ₯Ό μ‚¬μš©ν•˜λ©΄ μŠ€νŠΈλ¦Όμ— μ§μ ‘μ μœΌλ‘œ μ›ν•˜λŠ” κ°’ 을 넣을 수 μžˆμŠ΅λ‹ˆλ‹€. λ§ˆμ§€λ§‰μ— build λ©”μ†Œλ“œλ‘œ μŠ€νŠΈλ¦Όμ„ λ¦¬ν„΄ν•©λ‹ˆλ‹€.

Stream<String> builderStream = Stream.<String>builder()
	.add("str1").add("str2").add("str3")
	.build();

Stream.generate()

generate λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ Supplier<T> 에 ν•΄λ‹Ήν•˜λŠ” λžŒλ‹€λ‘œ 값을 넣을 수 μžˆμŠ΅λ‹ˆλ‹€. Supplier λŠ” μΈμžλŠ” μ—†κ³  λ¦¬ν„΄κ°’λ§Œ μžˆλŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ μž…λ‹ˆλ‹€. λžŒλ‹€μ—μ„œ λ¦¬ν„΄ν•˜λŠ” 값이 λ“€μ–΄κ°‘λ‹ˆλ‹€.

public static <T> Stream<T> generate(Supplier<T> s) { ... }

이 λ•Œ μƒμ„±λ˜λŠ” μŠ€νŠΈλ¦Όμ€ 크기가 μ •ν•΄μ Έμžˆμ§€ μ•Šκ³  λ¬΄ν•œ(infinite)ν•˜κΈ° λ•Œλ¬Έμ— νŠΉμ • μ‚¬μ΄μ¦ˆλ‘œ μ΅œλŒ€ 크기λ₯Ό μ œν•œν•΄μ•Ό ν•©λ‹ˆλ‹€.

Stream<String> generatedStream = Stream.generate(() -> "gen").limit(5); 

5개의 β€œgen” 이 λ“€μ–΄κ°„ 슀트림이 μƒμ„±λ©λ‹ˆλ‹€.

Stream.iterate()

iterate λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ μ΄ˆκΈ°κ°’κ³Ό ν•΄λ‹Ή 값을 λ‹€λ£¨λŠ” λžŒλ‹€λ₯Ό μ΄μš©ν•΄μ„œ μŠ€νŠΈλ¦Όμ— λ“€μ–΄κ°ˆ μš”μ†Œλ₯Ό λ§Œλ“­λ‹ˆλ‹€. λ‹€μŒ μ˜ˆμ œμ—μ„œλŠ” 30이 μ΄ˆκΈ°κ°’μ΄κ³  값이 2μ”© μ¦κ°€ν•˜λŠ” 값듀이 λ“€μ–΄κ°€κ²Œ λ©λ‹ˆλ‹€. 즉 μš”μ†Œκ°€ λ‹€μŒ μš”μ†Œμ˜ μΈν’‹μœΌλ‘œ λ“€μ–΄κ°‘λ‹ˆλ‹€. 이 방법도 슀트림의 μ‚¬μ΄μ¦ˆκ°€ λ¬΄ν•œν•˜κΈ° λ•Œλ¬Έμ— νŠΉμ • μ‚¬μ΄μ¦ˆλ‘œ μ œν•œν•΄μ•Ό ν•©λ‹ˆλ‹€.

Stream<Integer> iteratedStream = 
  Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]

κΈ°λ³Έ νƒ€μž…ν˜• 슀트림

λ¬Όλ‘  μ œλ„€λ¦­μ„ μ‚¬μš©ν•˜λ©΄ λ¦¬μŠ€νŠΈλ‚˜ 배열을 μ΄μš©ν•΄μ„œ κΈ°λ³Έ νƒ€μž…(int, long, double) μŠ€νŠΈλ¦Όμ„ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ œλ„€λ¦­μ„ μ‚¬μš©ν•˜μ§€ μ•Šκ³  μ§μ ‘μ μœΌλ‘œ ν•΄λ‹Ή νƒ€μž…μ˜ μŠ€νŠΈλ¦Όμ„ λ‹€λ£° μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. range 와 rangeClosed λŠ” λ²”μœ„μ˜ μ°¨μ΄μž…λ‹ˆλ‹€. 두 번째 인자인 μ’…λ£Œμ§€μ μ΄ ν¬ν•¨λ˜λŠλƒ μ•ˆλ˜λŠλƒμ˜ μ°¨μ΄μž…λ‹ˆλ‹€.

IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]

μ œλ„€λ¦­μ„ μ‚¬μš©ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— λΆˆν•„μš”ν•œ μ˜€ν† λ°•μ‹±(auto-boxing)이 μΌμ–΄λ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•„μš”ν•œ 경우 boxed λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄μ„œ λ°•μ‹±(boxing)ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();

Java 8 의 Random ν΄λž˜μŠ€λŠ” λ‚œμˆ˜λ₯Ό 가지고 μ„Έ 가지 νƒ€μž…μ˜ 슀트림(IntStream, LongStream, DoubleStream)을 λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ‰½κ²Œ λ‚œμˆ˜ μŠ€νŠΈλ¦Όμ„ μƒμ„±ν•΄μ„œ μ—¬λŸ¬κ°€μ§€ 후속 μž‘μ—…μ„ μ·¨ν•  수 μžˆμ–΄ μœ μš©ν•©λ‹ˆλ‹€.

λ¬Έμžμ—΄ 슀트링

μŠ€νŠΈλ§μ„ μ΄μš©ν•΄μ„œ μŠ€νŠΈλ¦Όμ„ μƒμ„±ν• μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒμ€ 슀트링의 각 문자(char)λ₯Ό IntStream 으둜 λ³€ν™˜ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€. char λŠ” λ¬Έμžμ΄μ§€λ§Œ λ³Έμ§ˆμ μœΌλ‘œλŠ” 숫자이기 λ•Œλ¬Έμ— κ°€λŠ₯ν•©λ‹ˆλ‹€.

IntStream charsStream = 
  "Stream".chars(); // [83, 116, 114, 101, 97, 109]

λ‹€μŒμ€ μ •κ·œν‘œν˜„μ‹(RegEx)을 μ΄μš©ν•΄μ„œ λ¬Έμžμ—΄μ„ 자λ₯΄κ³ , 각 μš”μ†Œλ“€λ‘œ μŠ€νŠΈλ¦Όμ„ λ§Œλ“  μ˜ˆμ œμž…λ‹ˆλ‹€.

Stream<String> stringStream = 
  Pattern.compile(", ").splitAsStream("str1, str2, str3");

파일 슀트림

μžλ°” NIO의 Files 클래슀의 lines λ©”μ†Œλ“œλŠ” ν•΄λ‹Ή 파일의 각 라인을 슀트링 νƒ€μž…μ˜ 슀트림으둜 λ§Œλ“€μ–΄μ€λ‹ˆλ‹€.

Stream<String> lineStream = Files.lines(Paths.get("file.txt"), Charset.forName("UTF-8"));

병렬 슀트림 Parallel Stream

슀트림 생성 μ‹œ μ‚¬μš©ν•˜λŠ” stream λŒ€μ‹  parallelStream λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ 병렬 μŠ€νŠΈλ¦Όμ„ μ‰½κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€μ μœΌλ‘œλŠ” μ“°λ ˆλ“œλ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ μžλ°” 7λΆ€ν„° λ„μž…λœ Fork/Join framework λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

// 병렬 슀트림 생성
Stream<Product> parallelStream = productList.parallelStream();

// 병렬 μ—¬λΆ€ 확인
boolean isParallel = parallelStream.isParallel();

λ”°λΌμ„œ λ‹€μŒ μ½”λ“œλŠ” 각 μž‘μ—…μ„ μ“°λ ˆλ“œλ₯Ό μ΄μš©ν•΄ 병렬 μ²˜λ¦¬λ©λ‹ˆλ‹€.

boolean isMany = parallelStream
  .map(product -> product.getAmount() * 10)
  .anyMatch(amount -> amount > 200);

λ‹€μŒμ€ 배열을 μ΄μš©ν•΄μ„œ 병렬 μŠ€νŠΈλ¦Όμ„ μƒμ„±ν•˜λŠ” κ²½μš°μž…λ‹ˆλ‹€.

Arrays.stream(arr).parallel();

μ»¬λ ‰μ…˜κ³Ό 배열이 μ•„λ‹Œ κ²½μš°λŠ” λ‹€μŒκ³Ό 같이 parallel λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄μ„œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();

λ‹€μ‹œ μ‹œν€€μ…œ(sequential) λͺ¨λ“œλ‘œ 돌리고 μ‹Άλ‹€λ©΄ λ‹€μŒμ²˜λŸΌ sequential λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. λ’€μ—μ„œ ν•œλ²ˆ 더 λ‹€λ£¨κ² μ§€λ§Œ λ°˜λ“œμ‹œ 병렬 슀트림이 쒋은 것은 μ•„λ‹™λ‹ˆλ‹€.

IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();

슀트림 μ—°κ²°ν•˜κΈ°

Stream.concat λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄ 두 개의 μŠ€νŠΈλ¦Όμ„ μ—°κ²°ν•΄μ„œ μƒˆλ‘œμš΄ μŠ€νŠΈλ¦Όμ„ λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]

κ°€κ³΅ν•˜κΈ°

전체 μš”μ†Œ μ€‘μ—μ„œ λ‹€μŒκ³Ό 같은 API λ₯Ό μ΄μš©ν•΄μ„œ λ‚΄κ°€ μ›ν•˜λŠ” κ²ƒλ§Œ 뽑아낼 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ 가곡 단계λ₯Ό 쀑간 μž‘μ—…(intermediate operations)이라고 ν•˜λŠ”λ°, μ΄λŸ¬ν•œ μž‘μ—…μ€ μŠ€νŠΈλ¦Όμ„ λ¦¬ν„΄ν•˜κΈ° λ•Œλ¬Έμ— μ—¬λŸ¬ μž‘μ—…μ„ 이어 λΆ™μ—¬μ„œ(chaining) μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

List<String> names = Arrays.asList("str1", "str2", "java");

μ•„λž˜ λ‚˜μ˜€λŠ” 예제 μ½”λ“œλŠ” μœ„μ™€ 같은 리슀트λ₯Ό λŒ€μƒμœΌλ‘œ ν•©λ‹ˆλ‹€.

Filtering

ν•„ν„°(filter)은 슀트림 λ‚΄ μš”μ†Œλ“€μ„ ν•˜λ‚˜μ”© ν‰κ°€ν•΄μ„œ κ±ΈλŸ¬λ‚΄λŠ” μž‘μ—…μž…λ‹ˆλ‹€. 인자둜 λ°›λŠ” Predicate λŠ” boolean 을 λ¦¬ν„΄ν•˜λŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œ 평가식이 λ“€μ–΄κ°€κ²Œ λ©λ‹ˆλ‹€.

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

κ°„λ‹¨ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€.

Stream<String> stream = names.stream()
  .filter(name -> name.contains("t")); // ["str1", "str2"]

슀트림의 각 μš”μ†Œμ— λŒ€ν•΄μ„œ 평가식을 μ‹€ν–‰ν•˜κ²Œ 되고 β€˜t’ κ°€ λ“€μ–΄κ°„ μ΄λ¦„λ§Œ λ“€μ–΄κ°„ 슀트림이 λ¦¬ν„΄λ©λ‹ˆλ‹€.

Mapping

맡(map)은 슀트림 λ‚΄ μš”μ†Œλ“€μ„ ν•˜λ‚˜μ”© νŠΉμ • κ°’μœΌλ‘œ λ³€ν™˜ν•΄μ€λ‹ˆλ‹€. 이 λ•Œ 값을 λ³€ν™˜ν•˜κΈ° μœ„ν•œ λžŒλ‹€λ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€.

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

μŠ€νŠΈλ¦Όμ— λ“€μ–΄κ°€ μžˆλŠ” 값이 input 이 λ˜μ–΄μ„œ νŠΉμ • λ‘œμ§μ„ 거친 ν›„ output 이 λ˜μ–΄ (λ¦¬ν„΄λ˜λŠ”) μƒˆλ‘œμš΄ μŠ€νŠΈλ¦Όμ— λ‹΄κΈ°κ²Œ λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ μž‘μ—…μ„ 맡핑(mapping)이라고 ν•©λ‹ˆλ‹€.

κ°„λ‹¨ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€. 슀트림 λ‚΄ String 의 toUpperCase λ©”μ†Œλ“œλ₯Ό μ‹€ν–‰ν•΄μ„œ λŒ€λ¬Έμžλ‘œ λ³€ν™˜ν•œ 값듀이 λ‹΄κΈ΄ μŠ€νŠΈλ¦Όμ„ λ¦¬ν„΄ν•©λ‹ˆλ‹€.

Stream<String> stream = names.stream()
	.map(String::toUpperCase); // ["STR1", "STR2", "STR3"]

λ‹€μŒμ²˜λŸΌ μš”μ†Œ λ‚΄ λ“€μ–΄μžˆλŠ” Product 개체의 μˆ˜λŸ‰μ„ κΊΌλ‚΄μ˜¬ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 각 β€˜μƒν’ˆβ€™μ„ β€˜μƒν’ˆμ˜ μˆ˜λŸ‰β€™μœΌλ‘œ λ§΅ν•‘ν•˜λŠ”κ±°μ£ .

Stream<Integer> stream = productList.stream()
  .map(Product::getAmount); // [23, 14, 13, 23, 13]

map 이외에도 쑰금 더 λ³΅μž‘ν•œ flatMap λ©”μ†Œλ“œλ„ μžˆμŠ΅λ‹ˆλ‹€.

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

인자둜 mapperλ₯Ό λ°›κ³  μžˆλŠ”λ°, 리턴 νƒ€μž…μ΄ Stream μž…λ‹ˆλ‹€. 즉, μƒˆλ‘œμš΄ μŠ€νŠΈλ¦Όμ„ μƒμ„±ν•΄μ„œ λ¦¬ν„΄ν•˜λŠ” λžŒλ‹€λ₯Ό λ„˜κ²¨μ•Όν•©λ‹ˆλ‹€. flatMap 은 쀑첩 ꡬ쑰λ₯Ό ν•œ 단계 μ œκ±°ν•˜κ³  단일 μ»¬λ ‰μ…˜μœΌλ‘œ λ§Œλ“€μ–΄μ£ΌλŠ” μ—­ν•  을 ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ μž‘μ—…μ„ ν”Œλž˜νŠΈλ‹(flattening) 이라고 ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 μ€‘μ²©λœ λ¦¬μŠ€νŠΈκ°€ μžˆμŠ΅λ‹ˆλ‹€.

List<List<String>> list = 
  Arrays.asList(Arrays.asList("a"), 
                Arrays.asList("b")); // [["a"], ["b"]]

이λ₯Ό flatMap을 μ‚¬μš©ν•΄μ„œ 쀑첩 ꡬ쑰λ₯Ό μ œκ±°ν•œ ν›„ μž‘μ—…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

List<String> flatList = 
  list.stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList()); // [a, b]

μ΄λ²ˆμ—” 객체에 μ μš©ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

students.stream()
  .flatMapToInt(student -> 
                IntStream.of(student.getKor(), 
                             student.getEng(), 
                             student.getMath()))
  .average().ifPresent(avg -> 
                       System.out.println(Math.round(avg * 10)/10.0));

μœ„ μ˜ˆμ œμ—μ„œλŠ” 학생 객체λ₯Ό 가진 μŠ€νŠΈλ¦Όμ—μ„œ ν•™μƒμ˜ ꡭ영수 점수λ₯Ό 뽑아 μƒˆλ‘œμš΄ μŠ€νŠΈλ¦Όμ„ λ§Œλ“€μ–΄ 평균을 κ΅¬ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€. μ΄λŠ” map λ©”μ†Œλ“œ μžμ²΄λ§ŒμœΌλ‘œλŠ” ν•œλ²ˆμ— ν•  수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.

Sorting

μ •λ ¬μ˜ 방법은 λ‹€λ₯Έ μ •λ ¬κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ Comparator λ₯Ό μ΄μš©ν•©λ‹ˆλ‹€.

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

인자 없이 κ·Έλƒ₯ ν˜ΈμΆœν•  경우 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•©λ‹ˆλ‹€.

IntStream.of(14, 11, 20, 39, 23)
  .sorted()
  .boxed()
  .collect(Collectors.toList()); // [11, 14, 20, 23, 39]

인자λ₯Ό λ„˜κΈ°λŠ” κ²½μš°μ™€ λΉ„κ΅ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. 슀트링 λ¦¬μŠ€νŠΈμ—μ„œ μ•ŒνŒŒλ²³ 순으둜 μ •λ ¬ν•œ μ½”λ“œμ™€ Comparator λ₯Ό λ„˜κ²¨μ„œ μ—­μˆœμœΌλ‘œ μ •λ ¬ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

List<String> lang = 
  Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

lang.stream()
  .sorted()
  .collect(Collectors.toList()); 
// [Go, Groovy, Java, Python, Scala, Swift]

lang.stream()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());
// [Swift, Scala, Python, Java, Groovy, Go]

Comparator 의 compare λ©”μ†Œλ“œλŠ” 두 인자λ₯Ό λΉ„κ΅ν•΄μ„œ 값을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.

int compare(T o1, T o2)

기본적으둜 Comparator μ‚¬μš©λ²•κ³Ό λ™μΌν•©λ‹ˆλ‹€. 이λ₯Ό μ΄μš©ν•΄μ„œ λ¬Έμžμ—΄ 길이λ₯Ό κΈ°μ€€μœΌλ‘œ μ •λ ¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

lang.stream()
  .sorted(Comparator.comparingInt(String::length))
  .collect(Collectors.toList());
// [Go, Java, Scala, Swift, Groovy, Python]

lang.stream()
  .sorted((s1, s2) -> s2.length() - s1.length())
  .collect(Collectors.toList());
// [Groovy, Python, Scala, Swift, Java, Go]

Iterating

슀트림 λ‚΄ μš”μ†Œλ“€ 각각을 λŒ€μƒμœΌλ‘œ νŠΉμ • 연산을 μˆ˜ν–‰ν•˜λŠ” λ©”μ†Œλ“œλ‘œλŠ” peek 이 μžˆμŠ΅λ‹ˆλ‹€. β€˜peek’ 은 κ·Έλƒ₯ ν™•μΈν•΄λ³Έλ‹€λŠ” 단어 뜻처럼 νŠΉμ • κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠλŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ Consumer λ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€.

Stream<T> peek(Consumer<? super T> action);

λ”°λΌμ„œ 슀트림 λ‚΄ μš”μ†Œλ“€ 각각에 νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•  뿐 결과에 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ‹€μŒμ²˜λŸΌ μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” 쀑간에 κ²°κ³Όλ₯Ό 확인해볼 λ•Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

int sum = IntStream.of(1, 3, 5, 7, 9)
  .peek(System.out::println)
  .sum();

κ²°κ³Ό λ§Œλ“€κΈ°

κ°€κ³΅ν•œ μŠ€νŠΈλ¦Όμ„ 가지고 λ‚΄κ°€ μ‚¬μš©ν•  κ²°κ³Όκ°’μœΌλ‘œ λ§Œλ“€μ–΄λ‚΄λŠ” λ‹¨κ³„μž…λ‹ˆλ‹€. λ”°λΌμ„œ μŠ€νŠΈλ¦Όμ„ λλ‚΄λŠ” μ΅œμ’… μž‘μ—…(terminal operations)μž…λ‹ˆλ‹€.

Calculating

슀트림 API λŠ” λ‹€μ–‘ν•œ μ’…λ£Œ μž‘μ—…μ„ μ œκ³΅ν•©λ‹ˆλ‹€. μ΅œμ†Œ, μ΅œλŒ€, ν•©, 평균 λ“± κΈ°λ³Έν˜• νƒ€μž…μœΌλ‘œ κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();

λ§Œμ•½ 슀트림이 λΉ„μ–΄ μžˆλŠ” 경우 count 와 sum 은 0을 좜λ ₯ν•˜λ©΄ λ©λ‹ˆλ‹€. ν•˜μ§€λ§Œ 평균, μ΅œμ†Œ, μ΅œλŒ€μ˜ κ²½μš°μ—λŠ” ν‘œν˜„ν•  μˆ˜κ°€ μ—†κΈ° λ•Œλ¬Έμ— Optional을 μ΄μš©ν•΄ λ¦¬ν„΄ν•©λ‹ˆλ‹€.

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();

μŠ€νŠΈλ¦Όμ—μ„œ λ°”λ‘œ ifPresent λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄μ„œ Optional 을 μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
  .average()
  .ifPresent(System.out::println);

이 외에도 μ‚¬μš©μžκ°€ μ›ν•˜λŠ”λŒ€λ‘œ κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄λ‚΄κΈ° μœ„ν•΄ reduce 와 collect λ©”μ†Œλ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. 이 두 가지 λ©”μ†Œλ“œλ₯Ό μ’€ 더 μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

Reduction

μŠ€νŠΈλ¦Όμ€ reduceλΌλŠ” λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄μ„œ κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€. λ‹€μŒμ€ reduce λ©”μ†Œλ“œλŠ” 총 μ„Έ κ°€μ§€μ˜ νŒŒλΌλ―Έν„°λ₯Ό 받을 수 μžˆμŠ΅λ‹ˆλ‹€.

  • accumulator : 각 μš”μ†Œλ₯Ό μ²˜λ¦¬ν•˜λŠ” 계산 둜직. 각 μš”μ†Œκ°€ 올 λ•Œλ§ˆλ‹€ 쀑간 κ²°κ³Όλ₯Ό μƒμ„±ν•˜λŠ” 둜직
  • identity : 계산을 μœ„ν•œ μ΄ˆκΈ°κ°’μœΌλ‘œ 슀트림이 λΉ„μ–΄μ„œ 계산할 λ‚΄μš©μ΄ 없더라도 이 값은 리턴
  • combiner : 병렬(parallel) μŠ€νŠΈλ¦Όμ—μ„œ λ‚˜λˆ  κ³„μ‚°ν•œ κ²°κ³Όλ₯Ό ν•˜λ‚˜λ‘œ ν•©μΉ˜λŠ” λ™μž‘ν•˜λŠ” 둜직
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);

// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);

// 3개 (combiner)
<U> U reduce(U identity,
  BiFunction<U, ? super T, U> accumulator,
  BinaryOperator<U> combiner);

λ¨Όμ € μΈμžκ°€ ν•˜λ‚˜λ§Œ μžˆλŠ” κ²½μš°μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ BinaryOperator λŠ” 같은 νƒ€μž…μ˜ 인자 두 개λ₯Ό λ°›μ•„ 같은 νƒ€μž…μ˜ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€μž…λ‹ˆλ‹€. λ‹€μŒ μ˜ˆμ œμ—μ„œλŠ” 두 값을 λ”ν•˜λŠ” λžŒλ‹€λ₯Ό λ„˜κ²¨μ£Όκ³  μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ κ²°κ³ΌλŠ” 6(1 + 2 + 3)이 λ©λ‹ˆλ‹€.

OptionalInt reduced = 
  IntStream.range(1, 4) // [1, 2, 3]
  .reduce((a, b) -> {
    return Integer.sum(a, b);
  });

μ΄λ²ˆμ—” 두 개의 인자λ₯Ό λ°›λŠ” κ²½μš°μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ 10은 μ΄ˆκΈ°κ°’μ΄κ³ , 슀트림 λ‚΄ 값을 λ”ν•΄μ„œ κ²°κ³ΌλŠ” 16(10 + 1 + 2 + 3)이 λ©λ‹ˆλ‹€. μ—¬κΈ°μ„œ λžŒλ‹€λŠ” λ©”μ†Œλ“œ μ°Έμ‘°(method reference)λ₯Ό μ΄μš©ν•΄μ„œ λ„˜κΈΈ 수 μžˆμŠ΅λ‹ˆλ‹€.

int reducedTwoParams = 
  IntStream.range(1, 4) // [1, 2, 3]
  .reduce(10, Integer::sum); // method reference

λ§ˆμ§€λ§‰μœΌλ‘œ μ„Έ 개의 인자λ₯Ό λ°›λŠ” κ²½μš°μž…λ‹ˆλ‹€. Combiner κ°€ ν•˜λŠ” 역할을 μ„€λͺ…λ§Œ 봀을 λ•ŒλŠ” 잘 이해가 μ•ˆκ°ˆ 수 μžˆλŠ”λ°μš”, μ½”λ“œλ₯Ό ν•œλ²ˆ μ‚΄νŽ΄λ΄…μ‹œλ‹€. 그런데 λ‹€μŒ μ½”λ“œλ₯Ό 싀행해보면 μ΄μƒν•˜κ²Œ λ§ˆμ§€λ§‰ 인자인 combiner λŠ” μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

Integer reducedParams = Stream.of(1, 2, 3)
  .reduce(10, // identity
          Integer::sum, // accumulator
          (a, b) -> {
            System.out.println("combiner was called");
            return a + b;
          });

Combiner λŠ” 병렬 처리 μ‹œ 각자 λ‹€λ₯Έ μ“°λ ˆλ“œμ—μ„œ μ‹€ν–‰ν•œ κ²°κ³Όλ₯Ό λ§ˆμ§€λ§‰μ— ν•©μΉ˜λŠ” λ‹¨κ³„μž…λ‹ˆλ‹€. λ”°λΌμ„œ 병렬 μŠ€νŠΈλ¦Όμ—μ„œλ§Œ λ™μž‘ν•©λ‹ˆλ‹€.

Integer reducedParallel = Arrays.asList(1, 2, 3)
  .parallelStream()
  .reduce(10,
          Integer::sum,
          (a, b) -> {
            System.out.println("combiner was called");
            return a + b;
          });

κ²°κ³ΌλŠ” λ‹€μŒκ³Ό 같이 36이 λ‚˜μ˜΅λ‹ˆλ‹€. λ¨Όμ € accumulator λŠ” 총 μ„Έ 번 λ™μž‘ν•©λ‹ˆλ‹€. μ΄ˆκΈ°κ°’ 10에 각 슀트림 값을 λ”ν•œ μ„Έ 개의 κ°’(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 κ³„μ‚°ν•©λ‹ˆλ‹€. Combiner λŠ” identity 와 accumulator λ₯Ό 가지고 μ—¬λŸ¬ μ“°λ ˆλ“œμ—μ„œ λ‚˜λˆ  κ³„μ‚°ν•œ κ²°κ³Όλ₯Ό ν•©μΉ˜λŠ” μ—­ν• μž…λ‹ˆλ‹€. 12 + 13 = 25, 25 + 11 = 36 μ΄λ ‡κ²Œ 두 번 ν˜ΈμΆœλ©λ‹ˆλ‹€.

combiner was called
combiner was called
36

병렬 슀트림이 무쑰건 μ‹œν€€μ…œλ³΄λ‹€ 쒋은 것은 μ•„λ‹™λ‹ˆλ‹€. 였히렀 κ°„λ‹¨ν•œ κ²½μš°μ—λŠ” μ΄λ ‡κ²Œ 뢀가적인 μ²˜λ¦¬κ°€ ν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— 였히렀 느릴 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

Collecting

collect λ©”μ†Œλ“œλŠ” 또 λ‹€λ₯Έ μ’…λ£Œ μž‘μ—…μž…λ‹ˆλ‹€. Collector νƒ€μž…μ˜ 인자λ₯Ό λ°›μ•„μ„œ 처리λ₯Ό ν•˜λŠ”λ°μš”, 자주 μ‚¬μš©ν•˜λŠ” μž‘μ—…μ€ Collectors κ°μ²΄μ—μ„œ μ œκ³΅ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

이번 μ˜ˆμ œμ—μ„œλŠ” λ‹€μŒκ³Ό 같은 κ°„λ‹¨ν•œ 리슀트λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. Product κ°μ²΄λŠ” μˆ˜λŸ‰(amout)κ³Ό 이름(name)을 가지고 μžˆμŠ΅λ‹ˆλ‹€.

List<Product> productList = 
  Arrays.asList(new Product(23, "potatoes"),
                new Product(14, "orange"),
                new Product(13, "lemon"),
                new Product(23, "bread"),
                new Product(13, "sugar"));

Collectors.toList()

μŠ€νŠΈλ¦Όμ—μ„œ μž‘μ—…ν•œ κ²°κ³Όλ₯Ό 담은 리슀트둜 λ°˜ν™˜ν•©λ‹ˆλ‹€. λ‹€μŒ μ˜ˆμ œμ—μ„œλŠ” map 으둜 각 μš”μ†Œμ˜ 이름을 κ°€μ Έμ˜¨ ν›„ Collectors.toList λ₯Ό μ΄μš©ν•΄μ„œ 리슀트둜 κ²°κ³Όλ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.

List<String> collectorCollection =
  productList.stream()
    .map(Product::getName)
    .collect(Collectors.toList());
// [potatoes, orange, lemon, bread, sugar]

Collectors.joining()

μŠ€νŠΈλ¦Όμ—μ„œ μž‘μ—…ν•œ κ²°κ³Όλ₯Ό ν•˜λ‚˜μ˜ 슀트링으둜 이어 뢙일 수 μžˆμŠ΅λ‹ˆλ‹€.

String listToString = 
 productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining());
// potatoesorangelemonbreadsugar

Collectors.joining 은 μ„Έ 개의 인자λ₯Ό 받을 수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μ΄μš©ν•˜λ©΄ κ°„λ‹¨ν•˜κ²Œ μŠ€νŠΈλ§μ„ μ‘°ν•©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • delimiter : 각 μš”μ†Œ 쀑간에 λ“€μ–΄κ°€ μš”μ†Œλ₯Ό κ΅¬λΆ„μ‹œμΌœμ£ΌλŠ” κ΅¬λΆ„μž
  • prefix : κ²°κ³Ό 맨 μ•žμ— λΆ™λŠ” 문자
  • suffix : κ²°κ³Ό 맨 뒀에 λΆ™λŠ” 문자
String listToString = 
 productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining(", ", "<", ">"));
// <potatoes, orange, lemon, bread, sugar>

Collectors.averageingInt()

숫자 κ°’(Integer value )의 평균(arithmetic mean)을 λƒ…λ‹ˆλ‹€.

Double averageAmount = 
 productList.stream()
  .collect(Collectors.averagingInt(Product::getAmount));
// 17.2

Collectors.summingInt()

μˆ«μžκ°’μ˜ ν•©(sum)을 λƒ…λ‹ˆλ‹€.

Integer summingAmount = 
 productList.stream()
  .collect(Collectors.summingInt(Product::getAmount));
// 86

IntStream 으둜 λ°”κΏ”μ£ΌλŠ” mapToInt λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ μ’€ 더 κ°„λ‹¨ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Integer summingAmount = 
  productList.stream()
  .mapToInt(Product::getAmount)
  .sum(); // 86

Collectors.summarizingInt()

λ§Œμ•½ 합계와 평균 λͺ¨λ‘ ν•„μš”ν•˜λ‹€λ©΄ μŠ€νŠΈλ¦Όμ„ 두 번 생성해야 ν• κΉŒμš”? 이런 정보λ₯Ό ν•œλ²ˆμ— 얻을 수 μžˆλŠ” λ°©λ²•μœΌλ‘œλŠ” summarizingInt λ©”μ†Œλ“œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

IntSummaryStatistics statistics = 
 productList.stream()
  .collect(Collectors.summarizingInt(Product::getAmount));

μ΄λ ‡κ²Œ λ°›μ•„μ˜¨ IntSummaryStatistics κ°μ²΄μ—λŠ” λ‹€μŒκ³Ό 같은 정보가 담겨 μžˆμŠ΅λ‹ˆλ‹€.

IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23}
  • 개수 getCount()
  • 합계 getSum()
  • 평균 getAverage()
  • μ΅œμ†Œ getMin()
  • μ΅œλŒ€ getMax()

이λ₯Ό μ΄μš©ν•˜λ©΄ collect 전에 이런 톡계 μž‘μ—…μ„ μœ„ν•œ map 을 ν˜ΈμΆœν•  ν•„μš”κ°€ μ—†κ²Œ λ©λ‹ˆλ‹€. μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ averaging, summing, summarizing λ©”μ†Œλ“œλŠ” 각 κΈ°λ³Έ νƒ€μž…(int, long, double)λ³„λ‘œ μ œκ³΅λ©λ‹ˆλ‹€.

Collectors.groupingBy()

νŠΉμ • 쑰건으둜 μš”μ†Œλ“€μ„ 그룹지을 수 μžˆμŠ΅λ‹ˆλ‹€. μˆ˜λŸ‰μ„ κΈ°μ€€μœΌλ‘œ κ·Έλ£Ήν•‘ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ λ°›λŠ” μΈμžλŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ Function μž…λ‹ˆλ‹€.

Map<Integer, List<Product>> collectorMapOfLists =
 productList.stream()
  .collect(Collectors.groupingBy(Product::getAmount));

κ²°κ³ΌλŠ” Map νƒ€μž…μœΌλ‘œ λ‚˜μ˜€λŠ”λ°μš”, 같은 μˆ˜λŸ‰μ΄λ©΄ 리슀트둜 λ¬Άμ–΄μ„œ λ³΄μ—¬μ€λ‹ˆλ‹€.

{23=[Product{amount=23, name='potatoes'}, 
     Product{amount=23, name='bread'}], 
 13=[Product{amount=13, name='lemon'}, 
     Product{amount=13, name='sugar'}], 
 14=[Product{amount=14, name='orange'}]}

Collectors.partitioningBy()

μœ„μ˜ groupingBy ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ Function 을 μ΄μš©ν•΄μ„œ νŠΉμ • 값을 κΈ°μ€€μœΌλ‘œ 슀트림 λ‚΄ μš”μ†Œλ“€μ„ λ¬Άμ—ˆλ‹€λ©΄, partitioningBy 은 ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ Predicate λ₯Ό λ°›μŠ΅λ‹ˆλ‹€. Predicate λŠ” 인자λ₯Ό λ°›μ•„μ„œ boolean 값을 λ¦¬ν„΄ν•©λ‹ˆλ‹€.

Map<Boolean, List<Product>> mapPartitioned = 
  productList.stream()
  .collect(Collectors.partitioningBy(el -> el.getAmount() > 15));

λ”°λΌμ„œ 평가λ₯Ό ν•˜λŠ” ν•¨μˆ˜λ₯Ό ν†΅ν•΄μ„œ 슀트림 λ‚΄ μš”μ†Œλ“€μ„ true 와 false 두 κ°€μ§€λ‘œ λ‚˜λˆŒ 수 μžˆμŠ΅λ‹ˆλ‹€.

{false=[Product{amount=14, name='orange'}, 
        Product{amount=13, name='lemon'}, 
        Product{amount=13, name='sugar'}], 
 true=[Product{amount=23, name='potatoes'}, 
       Product{amount=23, name='bread'}]}

Collectors.collectingAndThen()

νŠΉμ • νƒ€μž…μœΌλ‘œ κ²°κ³Όλ₯Ό collect ν•œ 이후에 μΆ”κ°€ μž‘μ—…μ΄ ν•„μš”ν•œ κ²½μš°μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 λ©”μ†Œλ“œμ˜ μ‹œκ·Έλ‹ˆμ³λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€. finisher κ°€ μΆ”κ°€λœ λͺ¨μ–‘인데, 이 ν”Όλ‹ˆμ…”λŠ” collect λ₯Ό ν•œ 후에 μ‹€ν–‰ν•  μž‘μ—…μ„ μ˜λ―Έν•©λ‹ˆλ‹€.

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
  Collector<T,A,R> downstream,
  Function<R,RR> finisher) { ... }

λ‹€μŒ μ˜ˆμ œλŠ” Collectors.toSet 을 μ΄μš©ν•΄μ„œ κ²°κ³Όλ₯Ό Set 으둜 collect ν•œ ν›„ μˆ˜μ •λΆˆκ°€ν•œ Set 으둜 λ³€ν™˜ν•˜λŠ” μž‘μ—…μ„ μΆ”κ°€λ‘œ μ‹€ν–‰ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€.

Set<Product> unmodifiableSet = 
 productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),         Collections::unmodifiableSet));

Collector.of()

μ—¬λŸ¬κ°€μ§€ μƒν™©μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λ©”μ†Œλ“œλ“€μ„ μ‚΄νŽ΄λ΄€μŠ΅λ‹ˆλ‹€. 이 외에 ν•„μš”ν•œ 둜직이 μžˆλ‹€λ©΄ 직접 collector λ₯Ό λ§Œλ“€ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. accumulator 와 combiner λŠ” reduce μ—μ„œ μ‚΄νŽ΄λ³Έ λ‚΄μš©κ³Ό λ™μΌν•©λ‹ˆλ‹€.

public static<T, R> Collector<T, R, R> of(
  Supplier<R> supplier, // new collector 생성
  BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
  BinaryOperator<R> combiner, // κ³„μ‚°ν•œ κ²°κ³Όλ₯Ό μˆ˜μ§‘ν•˜λŠ” ν•¨μˆ˜.
  Characteristics... characteristics) { ... }

μ½”λ“œλ₯Ό λ³΄μ‹œλ©΄ 더 이해가 μ‰¬μš°μ‹€ κ²λ‹ˆλ‹€. λ‹€μŒ μ½”λ“œμ—μ„œλŠ” collector λ₯Ό ν•˜λ‚˜ μƒμ„±ν•©λ‹ˆλ‹€. 컬렉터λ₯Ό μƒμ„±ν•˜λŠ” supplier 에 LinkedList 의 μƒμ„±μžλ₯Ό λ„˜κ²¨μ€λ‹ˆλ‹€. 그리고 accumulator μ—λŠ” λ¦¬μŠ€νŠΈμ— μΆ”κ°€ν•˜λŠ” add λ©”μ†Œλ“œλ₯Ό λ„˜κ²¨μ£Όκ³  μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 이 μ»¬λ ‰ν„°λŠ” 슀트림의 각 μš”μ†Œμ— λŒ€ν•΄μ„œ LinkedList λ₯Ό λ§Œλ“€κ³  μš”μ†Œλ₯Ό μΆ”κ°€ν•˜κ²Œ λ©λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ combiner λ₯Ό μ΄μš©ν•΄ κ²°κ³Όλ₯Ό μ‘°ν•©ν•˜λŠ”λ°, μƒμ„±λœ λ¦¬μŠ€νŠΈλ“€μ„ ν•˜λ‚˜μ˜ 리슀트둜 ν•©μΉ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Collector<Product, ?, LinkedList<Product>> toLinkedList = 
  Collector.of(LinkedList::new, 
               LinkedList::add, 
               (first, second) -> {
                 first.addAll(second);
                 return first;
               });

λ”°λΌμ„œ λ‹€μŒκ³Ό 같이 collect λ©”μ†Œλ“œμ— μš°λ¦¬κ°€ λ§Œλ“  μ»€μŠ€ν…€ 컬렉터λ₯Ό λ„˜κ²¨μ€„ 수 있고, κ²°κ³Όκ°€ λ‹΄κΈ΄ LinkedList κ°€ λ°˜ν™˜λ©λ‹ˆλ‹€.

LinkedList<Product> linkedListOfPersons = 
  productList.stream()
  .collect(toLinkedList);

Matching

맀칭은 쑰건식 λžŒλ‹€ Predicate λ₯Ό λ°›μ•„μ„œ ν•΄λ‹Ή 쑰건을 λ§Œμ‘±ν•˜λŠ” μš”μ†Œκ°€ μžˆλŠ”μ§€ μ²΄ν¬ν•œ κ²°κ³Όλ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€. λ‹€μŒκ³Ό 같은 μ„Έ 가지 λ©”μ†Œλ“œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

  • ν•˜λ‚˜λΌλ„ 쑰건을 λ§Œμ‘±ν•˜λŠ” μš”μ†Œκ°€ μžˆλŠ”μ§€(anyMatch)
  • λͺ¨λ‘ 쑰건을 λ§Œμ‘±ν•˜λŠ”μ§€(allMatch)
  • λͺ¨λ‘ 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠλŠ”μ§€(noneMatch)
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

κ°„λ‹¨ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€. λ‹€μŒ 맀칭 κ²°κ³ΌλŠ” λͺ¨λ‘ true μž…λ‹ˆλ‹€.

List<String> names = Arrays.asList("str1", "str2", "str3");

boolean anyMatch = names.stream()
  .anyMatch(name -> name.contains("s"));
boolean allMatch = names.stream()
  .allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream()
  .noneMatch(name -> name.endsWith("2"));

Iterating

foreach λŠ” μš”μ†Œλ₯Ό λŒλ©΄μ„œ μ‹€ν–‰λ˜λŠ” μ΅œμ’… μž‘μ—…μž…λ‹ˆλ‹€. 보톡 System.out.println λ©”μ†Œλ“œλ₯Ό λ„˜κ²¨μ„œ κ²°κ³Όλ₯Ό 좜λ ₯ν•  λ•Œ μ‚¬μš©ν•˜κ³€ ν•©λ‹ˆλ‹€. μ•žμ„œ μ‚΄νŽ΄λ³Έ peek κ³ΌλŠ” 쀑간 μž‘μ—…κ³Ό μ΅œμ’… μž‘μ—…μ˜ 차이가 μžˆμŠ΅λ‹ˆλ‹€.

names.stream().forEach(System.out::println);

Reference & Additional Resources