[Java] - Java Stream API

๋ฐ˜์‘ํ˜•

Java ์–ธ์–ด๊ฐ€ ๋ฒŒ์จ 11 ๋ฒ„์ „์ด ๋‚˜์˜ค๊ณ  ์žˆ๋„ค์š”. Java๋Š” ์—ญ์‚ฌ๊ฐ€ ๊นŠ๊ณ , ์˜ค๋ž˜๋œ ์–ธ์–ด์ด์ž ๋น„๋‚œ๋„ ๋งŽ์ด ๋ฐ›์€ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€๋งŒ ์•„์ง๋„ ๋งŽ์€ ๊ณณ์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๊ณ , ๋Œ€์ฒดํ•˜๋Š” ๊ณณ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ค๋Š˜์€ Java 8์—์„œ ๋“ฑ์žฅํ•œ Stream์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

 

Stream API

Stream ? ํ˜น์‹œ ๊ทธ๊ฑฐ, Buffer ๋ณด๋‹ค ์†๋„๊ฐ€ ๊ฒ๋‚˜ ๋น ๋ฅธ ๊ทธ Stream ? ๋„ค, ๊ทธ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค. Java์—์„œ Stream์€ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ˆ  ์ค‘ ํ•˜๋‚˜๋กœ, Java 8์—์„œ ์ƒˆ๋กœ์ด ๋“ฑ์žฅํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Java 8 ์ด์ „์—๋Š” ๋ฐฐ์—ด์ด๋‚˜ Collections์˜ ์ž๋ฃŒ ๊ตฌ์กฐ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด for ๋ฌธ์ด๋‚˜ foreach ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์†Œ๋ฅผ ํ•˜๋‚˜์”ฉ ๊บผ๋‚ด์—ˆ์ง€์š”. ๊ฐ„๋‹จํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์งœ๋Š” ๊ฒƒ์ด๋ผ๋ฉด, ํฐ ์ƒ๊ด€์ด ์—†๊ฒ ์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด, ๋‚œ์žกํ•ด์ง€๊ณ  ์ง€์ €๋ถ„ํ•ด์ง€๋Š” ๊ฑด ์ฝ”๋“œ์˜ ์–‘์ด ํ’์„ ์ฒ˜๋Ÿผ ๋ถ€ํ’€์–ด ์˜ค๋ฅด๊ณ , ๊ทธ๋Ÿฌ๋ฉด ๊ฐ€๋…์„ฑ๋„ ๋–จ์–ด์ง€๊ฒŒ ๋˜์ฃ .

Stream์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ๊ฐœ์„ ํ•˜๊ณ ์ž ๋“ฑ์žฅํ•˜์˜€์Šต๋‹ˆ๋‹ค. Stream์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํŠน์ง•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋‹ค์–‘ํ•œ ์ž๋ฃŒ๊ตฌ์กฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๊ฒฐํ•ฉํ•˜์—ฌ ๊ฒฐ๊ณผ ๋„์ถœ ๊ฐ€๋Šฅ.
  • Lambda (Java์˜ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋ฒ•)์™€ ๋ณ‘ํ–‰ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ ๊ฐ„๊ฒฐํ™”.
  • ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ(Thread)๋ฅผ ์ด์šฉํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ง€์› (Parallel Processing)

ํŠนํžˆ ์ €๋Š” ์ฒซ ๋ฒˆ์งธ ํŠน์ง•์„ ๋งŽ์ด ์ด์šฉํ•˜๋Š” ํŽธ์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ๊ฐ€๋Šฅํ•˜๋ฉด ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ ค๊ณ  ํ•˜์ง€๋งŒ, ๊ทธ ์ด์œ ๋Š” ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์ง€์›ํ•œ๋‹ค ํ•˜๋”๋ผ๋„ ๊ณ„์‚ฐ๋Ÿ‰์ด ๋งŽ์•„์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. 

 

Stream์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

์ด์ œ Stream์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Stream์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์•„๋ž˜์˜ 3๊ฐ€์ง€ ๊ณผ์ •์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

  1. Stream ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ.
  2. ๋ฐ์ดํ„ฐ์˜ ๊ฐ€๊ณต
  3. ์ตœ์ข… ๊ฒฐ๊ณผ ๋„์ถœ.

๊ธ€๋กœ๋งŒ ์„ค๋ช…ํ•˜๋ฉด ์ž˜ ๋ชจ๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ํ•œ ๋ฒˆ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐฐ์—ด๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๊ฐœ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜์ฃ .

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
	list.addAll(List.of(1, 2, 3, 4));
}

ArrayList๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ„์™€ ๊ฐ™์ด 1, 2, 3, 4 ๋ฐ์ดํ„ฐ๋ฅผ 10๋ฒˆ ๋ฐ˜๋ณตํ•˜์—ฌ ๋„ฃ์–ด๋ดค์Šต๋‹ˆ๋‹ค. ์ด ๋ฐ์ดํ„ฐ๋“ค ์ค‘ ์šฐ๋ฆฌ๋Š” ํŠน์ • ์ˆซ์ž ๋ช‡ ๊ฐœ๋งŒ ๋ฝ‘์•„๋ณด๊ณ  ์‹ถ๋‹ค๊ณ  ํ•œ๋‹ค๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ ๊ตฌ์ƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  1. ๋ฐ˜๋ณต๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ, ๋ฆฌ์ŠคํŠธ๋ฅผ 1๋ฒˆ ์ˆœํšŒํ•œ๋‹ค.
  2. ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ์šด ์ž๋ฃŒ ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  3. ๋ฐ˜๋ณต๋ฌธ ๋‚ด์— ํŠน์ • ์กฐ๊ฑด์„ ์ฃผ๊ณ , ํ•ด๋‹น ์กฐ๊ฑด์— ๋ถ€ํ•ฉํ•˜๋ฉด ํ•ด๋‹น ์ž๋ฃŒ๊ตฌ์กฐ์— ๊ฐ’์„ ์‚ฝ์ž…ํ•œ๋‹ค.

๋ฌผ๋ก  ํ˜„์žฌ๋Š” ๊ฐ„๋‹จํ•œ ์ž๋ฃŒ๊ตฌ์กฐ์ด๊ณ , ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์ด ํฌ๊ฒŒ ์ง€์žฅ์„ ๋ฐ›์ง€ ์•Š๊ฒ ์ง€๋งŒ, ์ด ๋กœ์ง์ด ์ปค์ง€๊ณ , ๋ณต์žกํ•ด์ง€๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ์ปค์ง€๋ฉด ๋‚œ์žกํ•ด์ง€๊ฒ ์ฃ ?

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” Stream์„ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. Stream API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋ฉด ์œ„์˜ 3๊ฐ€์ง€ ๊ณผ์ •์„ ์‰ฝ๊ฒŒ ๋งˆ๋ฌด๋ฆฌ ์ง€์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

list.stream().filter(num -> num > 2)

Stream์—์„œ ์ œ๊ณตํ•˜๋Š” filter ๋ฉ”์†Œ๋“œ์— Lambda ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ํ•ด๋‹นํ•˜๋Š” ์ˆซ์ž ์ค‘์—์„œ 2๋ณด๋‹ค ํฐ ์ˆซ์ž๋“ค๋งŒ ํ•„ํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

๊ทธ๋Ÿฐ๋ฐ, ๋‚˜๋Š” Stream์„ ์‚ฌ์šฉํ•˜๊ณ , ์ด๊ฑฐ๋ฅผ ๋ฒ ์—ด๊ณผ ๊ฐ™์€ ์ž๋ฃŒ๊ตฌ์กฐ๋กœ ๋„์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? 

Stream<Integer> s = list.stream().filter(num -> num > 2);
System.out.println(Arrays.toString(s.mapToInt(Integer::intValue).toArray()));

๊ทธ๋Ÿด ๋•Œ๋Š” toArray๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Array๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ, ์šฐ๋ฆฌ๋Š” ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ Integer ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ Java Array์—์„œ Integer ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ๋ฆฌ๋Š” map์„ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฅผ intValue ํ˜•ํƒœ๋กœ ๋ฐ”๊ฟ”์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด Lambda๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ›จ์”ฌ ๊น”๋”ํ•ด์ง‘๋‹ˆ๋‹ค.

์–ด๋–ค๊ฐ€์š”? Stream๊ณผ Lambda๊ฐ€ ์žˆ๊ธฐ ์ „์—๋Š” ์ฝ”๋“œ๊ฐ€ ์—„์ฒญ ๋ณต์žกํ•ด์ง€๋Š” ๊ฑฐ ๋•Œ๋ฌธ์— ๋ง์„ค์˜€์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜๊ฒŒ Stream์„ ์‚ฌ์šฉํ•ด์„œ ์งค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ Java 8 ์ด์ƒ ๋ฒ„์ „์—์„œ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋งค๋ ฅ์ด์ฃ .

 

Stream์„ ์ƒ์„ฑํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•

์ด ํฌ์ŠคํŠธ์—์„œ๋Š” List๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Stream์„ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฑฐ๊นŒ์ง€ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Stream์—๋Š” ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜๊ฐ€ ์žˆ๊ณ , ์ด์— ๋”ฐ๋ผ์„œ ๋‹ค์–‘ํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

1. ๋ฐฐ์—ด ์ŠคํŠธ๋ฆผ (Array Stream)

๋ฐฐ์—ด ์ŠคํŠธ๋ฆผ์€ ์œ„์—์„œ ์‚ฌ์šฉํ•œ ์ŠคํŠธ๋ฆผ๊ณผ ๋น„์Šทํ•˜๊ฒŒ, ๋ฐฐ์—ด์„ ์ƒ์„ฑ ํ›„,  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]

 

2. ์ปฌ๋ ‰์…˜ ์ŠคํŠธ๋ฆผ (Collection Stream)

Java์—์„œ ์ปฌ๋ ‰์…˜์€ Collection, List, Set ๋“ฑ์˜ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ด๋“ค์€ ์ „๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค์— ์ถ”๊ฐ€๋œ ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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(); // ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ŠคํŠธ๋ฆผ

Collections์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์ŠคํŠธ๋ฆผ์€ ์œ„์™€ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉํ•˜์‹ค ๋•Œ ์ฐธ๊ณ ํ•˜๋ฉด ์•„์ฃผ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๋„ค์š”.

 

3. ๋นˆ ์ŠคํŠธ๋ฆผ (Empty Stream)

๊ฐ€๋” ์ฝ”๋”ฉํ•  ๋•Œ, NULL๋กœ ๋˜์–ด ์žˆ๋Š” ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์ƒ๊ธฐ์ฃ . Stream ์—ญ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๊ทธ๋Ÿผ ์ด๋ ‡๊ฒŒ ๋นˆ ๊ฐ’์œผ๋กœ ๋’€๋‹ค๊ฐ€ ๋‚˜์ค‘์— ์–ด๋–ป๊ฒŒ ์ดˆ๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ๊ทธ ๋ฐฉ๋ฒ•๋„ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์กด์žฌํ•˜๋Š”๋ฐ, ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๋„๋ก ํ•˜์ฃ .

Stream<String> builderStream = 
  Stream.<String>builder()
    .add("Eric").add("Elena").add("Java")
    .build(); // [Eric, Elena, Java]

๋จผ์ € Stream์—์„œ ์ œ๊ณตํ•˜๋Š” builder ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์— build ๋ฉ”์†Œ๋“œ๋ฅผ ์ตœ์ข…์ ์œผ๋กœ ํ˜ธ์ถœํ•จ์œผ๋กœ์จ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, add ์ดํ›„์— ๋ฐ˜๋“œ์‹œ ์ฐธ๊ณ ํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค.

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

generate ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, Generic์„ ์‚ฌ์šฉํ•ด์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์˜ ๊ฐ’์„ Lambda ์‹์„ ์ด์šฉํ•˜์—ฌ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

Stream<String> generatedStream = 
  Stream.generate(() -> "gen").limit(5); // [el, el, el, el, el]

์ด ๋•Œ, ์ƒ์„ฑ๋˜๋Š” ์ŠคํŠธ๋ฆผ์€ ํฌ๊ธฐ๊ฐ€ ์ •ํ•ด์ ธ ์žˆ์ง€ ์•Š๊ณ , ๋ฌดํ•œํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํŠน์ • ์‚ฌ์ด์ฆˆ๋ฅผ ์ •์˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ๋ช…์‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

๋งˆ์ง€๋ง‰์œผ๋กœ iterate ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ดˆ๊ธฐ๊ฐ’๊ณผ ํ•ด๋‹น ๊ฐ’์„ ๋‹ค๋ฃจ๋Š” Lambda๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์— ๋“ค์–ด๊ฐˆ ์š”์†Œ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ์ˆซ์ž๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ๊ฐ€์žฅ ํŽธํ•œ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ, ์ด๋„  generate์™€ ๊ฐ™์ด ์ŠคํŠธ๋ฆผ์ด ๋ฌดํ•œ์ด๊ธฐ ๋•Œ๋ฌธ์— ํŠน์ • ์‚ฌ์ด์ฆˆ๋ฅผ ์ •์˜ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

4. ๊ธฐ๋ณธ ํƒ€์ž…ํ˜• ์ŠคํŠธ๋ฆผ (Default-type Stream)

์‚ฌ์‹ค ์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, ๊ธฐ๋ณธ ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” ์ŠคํŠธ๋ฆผ์ด ํ•„์š”์—†์ง€ ์•Š์„๊นŒ ์‹ถ์€๋ฐ์š”. ๋‚˜๋Š” ์ œ๋„ค๋ฆญ์ด ์‹ซ๋‹ค๊ฑฐ๋‚˜ ์“ฐ๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์„œ ๊ธฐํ”ผํ•˜๊ณ  ์žˆ๋‹ค. ํ•˜์‹œ๋Š” ๋ถ„๋“ค(?)์ด ๊ณ„์‹ ๋‹ค๋ฉด, ์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋‹จ, ์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์˜คํ† ๋ฐ•์‹ฑ(auto-boxing)์€ ์ผ์–ด๋‚˜์ง€ ์•Š๊ฒ ์ฃ ? ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•  ๋•Œ๋Š” boxed ๋ฉ”์†Œ๋“œ๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋ฏ€๋กœ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

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

Java 8์—์„œ ์ œ๊ณตํ•˜๋Š” Random ํด๋ž˜์Šค๋Š” ๋‚œ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  3๊ฐ€์ง€ ํƒ€์ž…์˜ ์ŠคํŠธ๋ฆผ (Int, Long, Double)์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด ๊ต‰์žฅํžˆ ์ด์ ์ด ํฐ๋ฐ์š”. ๋ฌด์—‡๋ณด๋‹ค ๋‚œ์žกํ•œ ์ฝ”๋“œ ์—†์ด๋„ ๋‚œ์ˆ˜๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์€ ์ฐธ ๋งค๋ ฅ์žˆ๋Š” ๊ฐœ์„ ์ด์ฃ .

DoubleStream doubles = new Random().doubles(3); // ๋‚œ์ˆ˜ 3๊ฐœ ์ƒ์„ฑ

 

5. ๋ฌธ์ž์—ด ์ŠคํŠธ๋ฆผ (String Stream)

ํ˜น์‹œ C++ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ณ„์‹ ๋‹ค๋ฉด, StringStream์ด ์ข€ ๋งŽ์ด ์ต์ˆ™ํ•˜์‹คํ…๋ฐ, ์‚ฌ์‹ค ์ €๋Š” C++์˜ StringStream์„ StringBuilder ๋Œ€์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ , Java 8์—์„œ ์ œ๊ณตํ•˜๋Š” String Stream์€ ์ด์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

Stream<String> stringStream = 
  Pattern.compile(", ").splitAsStream("Eric, Elena, Java");
  // [Eric, Elena, Java]

๊ฐ„๋‹จํžˆ ์ƒ์„ฑ์— ๋Œ€ํ•ด์„œ๋งŒ ์ด์•ผ๊ธฐ ํ•˜์ž๋ฉด, ์ •๊ทœ์‹์„ ์ด์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด ์ž๋ฅด๊ณ , ๊ฐ ์š”์†Œ๋“ค๋กœ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜น์€ lines ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•˜๊ฑฐ๋‚˜ chars ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ IntStream์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

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

์œ„์˜ ์ฝ”๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” ์•„์‹œ๋‹ค์‹œํ”ผ Java์˜ char ๋ฐ์ดํ„ฐ๊ฐ€ ์•„์Šคํ‚ค ์ฝ”๋“œ๋ฅผ ๋”ฐ๋ฅด๊ณ  ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

 

6. ํŒŒ์ผ ์ŠคํŠธ๋ฆผ (File Stream)

Java NIO์˜ File ํด๋ž˜์Šค์˜ lines ๋ฉ”์†Œ๋“œ๋Š” ํ•ด๋‹น ํŒŒ์ผ์˜ ๊ฐ ๋ผ์ธ์„ ์ŠคํŠธ๋ฆผ ํƒ€์ž…์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

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

์‚ฌ์‹ค Java 8 ์ด์ „ ๋ฒ„์ „์—์„œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ•  ๋•Œ, ์‚ฌ์šฉํ–ˆ๋˜ ๋ฐฉ๋ฒ•์€ ๋ณดํ†ต BufferedReader๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ์Šต๋‹ˆ๋‹ค. BufferedReader๋Š” Scanner์—์„œ ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์„ ๋ฐ›์•„์˜ฌ ๋•Œ๋ณด๋‹ค๋„ ๊ทธ ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋น ๋ฅธ ํŽธ์ด์–ด์„œ ์‚ฌ์šฉํ–ˆ์—ˆ๋Š”๋ฐ์š”.

๊ทธ๋Ÿฐ๋ฐ, BufferedReader๋Š” .try-catch ๋ฌธ์„ ์‚ฌ์šฉํ•ด์„œ Exception ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•˜๋Š” ๋“ฑ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์‹ค์ƒ ์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ํŒŒ์ผ ์ŠคํŠธ๋ฆผ์€ ๊ต‰์žฅํžˆ ์œ ์šฉํ•˜๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

 

7. ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ (Parallel Stream)

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. JVM ๋‚ด์—์„œ๋Š” Java 7์—์„œ ๋„์ž…๋œ Fork/Join Framework๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ ์ƒ์„ฑ
Stream<Product> parallelStream = productList.parallelStream();

// ๋ณ‘๋ ฌ ์—ฌ๋ถ€ ํ™•์ธ
boolean isParallel = parallelStream.isParallel();

๋ฆฌ์ŠคํŠธ์— ๋“ค์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ž‘์œผ๋ฉด ํ”„๋กœ์„ธ์Šค ๋ชจ๋‹ˆํ„ฐ๋กœ ํ™•์ธ์ด ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋•Œ๋Š” Stream์—์„œ ์ œ๊ณตํ•˜๋Š” isParallel ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์›€์ง์—ฌ๋ณด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

Arrays.stream(arr).parallel();

๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์œ„์™€ ๊ฐ™์ด parallelStream์ด ์•„๋‹Œ stream์— parallel ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋ฐ˜๋Œ€๋กœ ์ปฌ๋ ‰์…˜์ด๋‚˜ ๋ฐฐ์—ด ์–ด๋Š์ชฝ์—๋„ ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์œ„ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

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

๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ๋” ์ด์ƒ ํ•˜์ง€ ์•Š๊ณ  ์‹ถ๋‹ค๋ฉด, sequential ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋. ์ƒ๊ฐ๋ณด๋‹ค ์ฒ˜๋ฆฌ๊ฐ€ ์‰ฝ๊ตฐ์š”.

 

Stream์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•.

์—ฌ๋Ÿฌ ๊ฐ€์ง€ Stream์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค. ๊ทธ๋“ค Stream์€ ์šฐ๋ฆฌ๊ฐ€ ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ , ๋˜ํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. 

๊ทธ๋Ÿฐ๋ฐ, ์ŠคํŠธ๋ฆผ์—์„œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋Š” ๊ต‰์žฅํžˆ ์œ ์šฉํ•˜์˜€์ง€๋งŒ, ๋‹ค๋ฅธ ๊ฒƒ์€ Collections๋‚˜ Array์— ๋น„ํ•ด ๋ณ„๋ฐ˜ ๋‹ค๋ฅผ ๊ฒŒ ์—†์—ˆ์ฃ . ํ•˜์ง€๋งŒ Stream์˜ ๊ฝƒ์€ ๋ฐ”๋กœ ๊ฐ€๊ณต๊ณผ ๊ฒฐ๊ณผ ๋„์ถœ์ž…๋‹ˆ๋‹ค. 

ํŠนํžˆ ์šฐ๋ฆฌ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•  ๋•Œ๋Š” ๋งŽ์€ ๋ฐ˜๋ณต๋ฌธ๊ณผ ๋ณต์žกํ•œ ๋กœ์ง๋“ค ๋•Œ๋ฌธ์— ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ์ ์  ๋–จ์–ด๋œจ๋ฆฌ๊ณ , ์–ด๋ ค์šด ์ฝ”๋”ฉ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ–ˆ์ฃ . ํ•˜์ง€๋งŒ Stream์€ ๊ฐ„๋‹จํ•œ ๋ฌธ๋ฒ•๊ณผ ๋‹จ์ˆœํ•˜๊ณ  ์ง๊ด€์ ์ธ Lambda์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋ฐ์ดํ„ฐ์˜ ๊ฐ€๊ณต๋„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

1. Filtering

ํ•„ํ„ฐ๋Š” Collections, Arrays์—์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์š”์†Œ๋งŒ์„ ๋ฝ‘์•„์ค„ ์ˆ˜ ์žˆ๋Š” ์•„์ฃผ ์œ ์šฉํ•œ ๊ฐ€๊ณต ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๊ฒŒ ๋˜๋Š” Predicate๋Š” boolean ๊ฐ’์„ ๋ฆฌํ„ด ๋ฐ›๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

Stream<T> filter(Predicate<? super T> predicate);
Stream<String> stream = 
  names.stream()
  .filter(name -> name.contains("a"));
// [Elena, Java]

๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ ๋ชจ๋“  ์ŠคํŠธ๋ฆผ์— ์ œ๊ณต๋˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„์˜ ์˜ˆ์ œ๊ฐ€ ์•„๋‹ˆ๊ณ ๋„ Array๋‚˜ ArrayList์—์„œ ์›ํ•˜๋Š” ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์— ๊ธฐ์žฌํ–ˆ๋˜ ๋ฐฉ์‹๋Œ€๋กœ Filtering ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

2. Mapping

map ๋˜ํ•œ ๊ธฐ๋ณธ ์˜ˆ์ œ์—์„œ ๋ดค๋‹ค์‹œํ”ผ ํŠน์ • ๋ฐ์ดํ„ฐ ํƒ€์ž…์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ˜•ํ•ด์ฃผ๊ฑฐ๋‚˜ ํŠน์ • ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ˜•์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

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

๊ฐ’์„ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•œ ์ธ์ž๊ฐ’์€ Lambda๋กœ ๋ฐ›๊ฒŒ ๋˜๋ฉฐ ์ŠคํŠธ๋ฆผ์— ๋“ค์–ด์žˆ๋Š” ๊ฐ’์ด input ๊ฐ’์ด ๋˜๊ณ , ํŠน์ • ๋กœ์ง์„ ๊ฑฐ์นœ ํ›„, output ๋˜์–ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ, ์ด ๋•Œ ์ƒˆ๋กœ์šด ์ŠคํŠธ๋ฆผ์ด ์ƒ์„ฑ๋˜์–ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. 

Stream<String> stream = 
  names.stream()
  .map(String::toUpperCase);
// [ERIC, ELENA, JAVA]

๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ๋Š” ์œ„์™€ ๊ฐ™์ด ๋Œ€๋ฌธ์ž๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ String::toUpperCase๋Š” Lambda์—์„œ ํด๋ž˜์Šค์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•œ Lambda ์‹์„ ์“ด ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

๋” ๋‚˜์•„๊ฐ€์„œ๋Š” ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ถˆ๋Ÿฌ์™€์„œ Getter์—๋„ ์‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

map๋ณด๋‹ค๋„ ๋ณต์žกํ•œ flatMap๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์ธ์ž๋กœ mapper๋ฅผ ๋ฐ›๊ณ  ์žˆ์œผ๋ฉฐ, ๋ฆฌํ„ด ํƒ€์ž…์ด Stream์ž…๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜์—ฌ, ๋ฆฌํ„ดํ•˜๋Š” Lambda ์‹์„ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด ๊ธฐ์กด Map๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

flatMap์€ ์ค‘์ฒฉ ๊ตฌ์กฐ๋ฅผ ํ•œ ๋‹จ๊ณ„ ์ œ๊ฑฐํ•˜๊ณ , ๋‹จ์ผ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…์„ Flattening์ด๋ผ ํ•œ๋‹ค.

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

์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ ‡๊ฒŒ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ค‘์ฒฉ๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค๋ฉด,

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

flatMap์„ ์‚ฌ์šฉํ•ด์„œ ์ค‘์ฒฉ ๊ตฌ์กฐ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค Stream์„ ์ค‘์ฒฉ๋œ ์ž๋ฃŒ๊ตฌ์กฐ์—์„œ ์ด์šฉํ•œ ์ ์ด ๋งŽ์ด ์—†์—ˆ๋Š”๋ฐ, ๊ฝค ์œ ์šฉํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

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์œผ๋กœ๋Š” ๊ตฌํ˜„์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, flatMap์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ฃ .

 

3. Sorting

Stream์—์„œ๋Š” ์ •๋ ฌ ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” Java์˜ ์ •๋ ฌ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 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]

๋”ฐ๋ผ์„œ ์ธ์ž ์—†์ด ํ˜ธ์ถœํ•˜๋ฉด ๊ทธ๋ƒฅ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.

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]

์ธ์ž๋ฅผ ๋„˜๊ธธ ๊ฒฝ์šฐ์—๋Š” ์–ด๋–จ๊นŒ์š”? lang ๋ฆฌ์ŠคํŠธ์—์„œ ์•ŒํŒŒ๋ฒณ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ ์ฝ”๋“œ์™€ Comparator๋ฅผ ๋„˜๊ฒจ์„œ ์—ญ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ ์ฝ”๋“œ๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉด, ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ํ•  ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๊ณ , ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•  ๋•Œ๋Š” ๊ทธ๋ ‡๊ฒŒ ๋งŽ์€ ์ฐจ์ด๊ฐ€ ์žˆ์ง€ ์•Š๋„ค์š”.

int compare(T o1, T o2)

๊ทธ๋Ÿผ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณธ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ์š”? ์ผ๋‹จ Comparator์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•๊ณผ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์—, o1๊ณผ o2๋ฅผ ๋น„๊ตํ•˜๋Š” ์‹์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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]

๋ฌธ์ž์—ด์˜ ๊ธธ์ด๋กœ ๊ณ„์‚ฐํ•˜์—ฌ ๊ทธ๊ฒƒ์ด ๊ธด ์ˆœ์œผ๋กœ ๋‚˜์—ดํ•  ๋–„๋Š” ์ด๋ ‡๊ฒŒ ์“ฐ๋ฉด ๋˜๊ฒ ๊ตฐ์š”.

 

4. Iterating

์ŠคํŠธ๋ฆผ ๋‚ด ์š”์†Œ๋“ค ๊ฐ๊ฐ์„ ๋Œ€์ƒ์œผ๋กœ ํŠน์ • ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋กœ๋Š” peek์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋งˆ Java์—์„œ ์ œ๊ณตํ•˜๋Š” Stack์„ ์‚ฌ์šฉํ•ด๋ดค๋‹ค๋ฉด, ์ด peek๊ณผ pop์— ๋Œ€ํ•ด์„œ ํ—ท๊ฐˆ๋ คํ•˜๋Š” ๋ถ„๋“ค์ด ๋งŽ์œผ์‹ค ๊ฒƒ์ž…๋‹ˆ๋‹ค. peek ๋ฉ”์†Œ๋“œ๋Š” 'Just Checking'์ด๋ผ๋Š” ์˜๋ฏธ ๊ทธ๋Œ€๋กœ, ํŠน์ • ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค Consumer๋ฅผ ์ธ์ž๋กœ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

๋”ฐ๋ผ์„œ ์ŠคํŠธ๋ฆผ ๋‚ด ์š”์†Œ๋“ค ๊ฐ๊ฐ์— ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋ฟ ๊ฒฐ๊ณผ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 

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

peek์„ ์ฃผ๊ฒŒ ๋˜๋ฉด, peek ๋‚ด์—๋Š” ์ดํ›„ ์ทจํ•˜๊ฒŒ ๋  Lambda ์‹์„ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์œ„์™€ ๊ฐ™์ด ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ, ์ŠคํŠธ๋ฆผ ๋‚ด์— ์žˆ๋Š” ์š”์†Œ๋ฅผ ๋ชจ๋‘ ์ถœ๋ ฅํ•œ ๋‹ค์Œ, ํ•ฉ๊ณ„๋ฅผ ์ถœ๋ ฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

Stream์œผ๋กœ ๊ฐ€๊ณต๋œ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์‚ฐ.

์ด์ œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋“ค๋กœ๋งŒ ๊ฐ€๊ณตํ•˜์˜€์œผ๋‹ˆ, ๊ฒฐ๊ณผ๋ฅผ ๋„์ถœํ•˜๊ธฐ ์œ„ํ•ด ์ด๋“ค์„ ์ฒ˜๋ฆฌํ•ด๋ด…์‹œ๋‹ค. ์ด ์ดํ›„์—๋Š” ์ŠคํŠธ๋ฆผ์„ ๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ ๋˜๊ณ , ๊ธฐ๋ณธ ์˜ˆ์ œ์—์„œ ๋ดค๋“ฏ์ด Array, List ๋“ฑ ํƒ€ ์ž๋ฃŒ๊ตฌ์กฐ๋“ค๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

1. Calculating

๋‹จ์ˆœ ๊ณ„์‚ฐ์œผ๋กœ ์ตœ์†Œ, ์ตœ๋Œ€, ํ•ฉ, ํ‰๊ท  ๋“ฑ ๊ธฐ๋ณธํ˜• ํƒ€์ž…์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋งŒ์•ฝ ์ŠคํŠธ๋ฆผ์— ์•„๋ฌด๋Ÿฐ ๋ฐ์ดํ„ฐ๋„ ์—†๋Š” ๊ฒฝ์šฐ, ์ด๋“ค์€ 0์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

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

ํ•˜์ง€๋งŒ min, max ๋ฉ”์†Œ๋“œ๋Š” ์ตœ์†Ÿ๊ฐ’, ์ตœ๋Œ“๊ฐ’์„ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์ƒ์ด ์—†์œผ๋ฏ€๋กœ Optional ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•ด ๋ฆฌํ„ดํ•ด์•ผ๊ฒ ๋„ค์š”.

Optional

Java 8์—์„œ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค ์ค‘ ํ•˜๋‚˜๋กœ, NPE(NullPointerException)์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
์ œ๋„ค๋ฆญ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด, ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ์ด์šฉํ•  ์ˆ˜ ์žˆ๊ณ , try-Catch, if ์กฐ๊ฑด๋ฌธ์— ๋น„ํ•ด Lambda ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด, ์ง๊ด€์ ์ธ ๋ฌธ๋ฒ•์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
  .average()
  .ifPresent(System.out::println);

๋”๋ถˆ์–ด Stream์—์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ifPresent ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๊ทธ์•ผ๋ง๋กœ ๊ธˆ์ƒ์ฒจํ™”๋ผ ํ•  ์ˆ˜ ์žˆ์ฃ .

 

2. Reduction

Stream์€ reduce๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š”๋ฐ, reduce๋Š” ์ด 3๊ฐ€์ง€์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • 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๋Š” ํ•„์ˆ˜์ธ๋ฐ, BinaryOperator๋Š” ๊ฐ™์€ ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ๋‘ ๊ฐœ ๋ฐ›์•„, ๊ฐ™์€ ํƒ€์ž…์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋„์ถœํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค.

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

IntStream์—์„œ ์ œ๊ณตํ•˜๋Š” range ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด 1~4๊นŒ์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด๋“ค์„ ๋”ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ, ์ค‘๊ฐ„์— ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— a + b ๊ฐ€ ์ด๋ฃจ์–ด์ง„ ํ•ฉ, ์ด๋ฅผ a๋กœ ์ฃผ๊ณ , ๋‹ค์‹œ a + b ์ด๋ ‡๊ฒŒ ๊ณ„์‚ฐ์ด ๋˜๋Š” ๊ฒƒ์ด์ฃ .

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

๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›๊ฒŒ ๋  ๊ฒฝ์šฐ์—๋Š”, ์ดˆ๊ธฐ๊ฐ’์„ ์ •ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ฐ”๋กœ Identity์ธ๋ฐ์š”. ์—ฌ๊ธฐ์— ์ˆซ์ž๋ฅผ ๋„ฃ์œผ๋ฉด 10 + 1 + 2 + 3์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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 ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค. ๊ฐ์ž ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋งˆ์ง€๋ง‰์— ํ•ฉ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

๋ฐ˜๋“œ์‹œ ์•Œ์•„๋‘ฌ์•ผํ•  ๊ฒƒ์€ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ž‘์€ ๋ฐ์ดํ„ฐ์— ์žˆ์–ด์„œ๋Š” ์˜คํžˆ๋ ค ๋น„ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” R์ด ๋˜์—ˆ๋“  Python์—์„œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋“  ๋˜‘๊ฐ™์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

3. Collecting

๋ฐ”๋กœ ์ด ๋ฉ”์†Œ๋“œ๋“ค์ด ์œ„์—์„œ ์ฒ˜๋ฆฌํ•œ ๊ณ„์‚ฐ์„ List๋‚˜ Array๋กœ ๋ณ€ํ™˜์‹œํ‚ค๋Š” ๋“ฑ์˜ ์—ญํ• ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Collector ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๊ณ , ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์ž‘์—…์€ Collectors ๊ฐ์ฒด์—์„œ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

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

์˜ˆ๋ฅผ ๋“ค์–ด, ์œ„์™€ ๊ฐ™์ด Product ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์—ˆ๊ณ , ์ด ๊ฐ์ฒด์—๋Š” ์ˆ˜๋Ÿ‰(amount)์™€ ์ด๋ฆ„(name)์ด ๋‹ด๊ฒจ์ ธ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ์ด ๊ฐ์ฒด์— ๋Œ€ํ•œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ดค์Šต๋‹ˆ๋‹ค.

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

Stream์—์„œ ์ž‘์—…ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•œ ์ฝ”๋“œ์ธ๋ฐ์š”. ๋จผ์ € ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ถˆ๋Ÿฌ์™€์„œ, ์ด๋ฅผ ์ด๋ฆ„๋งŒ ๊ฐ€์ ธ์™€ ๋ฆฌ์ŠคํŠธ๋กœ ์ €์žฅํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

์ด ์™ธ์—๋„ ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ์ด๋ฆ„๋“ค์„ ์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ String ํ˜•ํƒœ๋กœ ๋ถ™์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, ๊ทธ๋ƒฅ ๋ถ™์ด๋Š” ๊ธฐ๋Šฅ๋งŒ ์žˆ๋Š” ๊ฑธ๊นŒ์š”? ๊ทธ๋ ‡์ง€๋Š” ์•Š๊ณ , joining ๋ฉ”์†Œ๋“œ์—์„œ, 3๊ฐœ์˜ ์ธ์ž๋ฅผ ๋‹ด์„ ์ˆ˜๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ์ด์šฉํ•ด์„œ ์ข€ ๋” ์ด์˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ฃ .

String listToString = 
 productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining(", ", "<", ">"));
// <potatoes, orange, lemon, bread, sugar>
  • delimiter: ๊ฐ ์š”์†Œ ์ค‘๊ฐ„์— ๋“ค์–ด๊ฐˆ ๊ตฌ๋ถ„์ž.
  • prefix: ๊ฒฐ๊ณผ ๋งจ ์•ž์— ๋ถ™๋Š” ์ ‘๋‘์‚ฌ.
  • suffix: ๊ฒฐ๊ณผ ๋งจ ๋’ค์— ๋ถ™๋Š” ์ ‘๋ฏธ์‚ฌ

์ด๋ ‡๊ฒŒ, ํ•œ ์š”์†Œ์— ์žˆ๋Š” ์ด๋ฆ„์„ ๋ฐ์ดํ„ฐ๋กœ ๋„์ถœํ•ด๋‚ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์ˆซ์ž์˜ ํ•ฉ๊ณผ ํ‰๊ท ์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋”์šฑ์ด ๋ฆฌ์ŠคํŠธ์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋ฉด, ์ˆ˜๋Ÿ‰(amount)๋Š” Integer๊ฐ€ ๋˜์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜๋Ÿ‰์— ๋Œ€ํ•œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค๋ฉด, ๋ฐ˜๋“œ์‹œ mapping ๊ณผ์ •์„ ํ†ตํ•ด์„œ Int๋กœ ๋งคํ•‘ํ•œ ํ›„, ์ดํ•ฉ์„ ๊ตฌํ•ด์•ผ๊ฒ ์ฃ ?

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์„ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์ฃ . average๋‚˜ sum, summarizing์€ ๊ฐ ๊ธฐ๋ณธ ํƒ€์ž…์ธ int, long, double๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

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

ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ์š”์†Œ๋“ค์„ ๊ทธ๋ฃน์ง€์„ ์ˆ˜ ์žˆ๋Š” Grouping๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฐ›๋Š” ์ธ์ž๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค Function์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ  groupingBy ํŒŒ๋ผ๋ฏธํ„ฐ์— Lambda ์‹์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋˜๊ฒ ์ฃ .

{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'}]}

๊ฒฐ๊ณผ๋Š” Map์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๊ณ , ๋งŒ์•ฝ, ๊ฐ™์€ ์ˆ˜๋Ÿ‰์ธ ๊ฒฝ์šฐ ๊ทธ value๊ฐ€ ๋ฆฌ์ŠคํŠธ๋กœ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค.

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

groupingBy๊ฐ€ ์–ด๋–ค ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ์˜ํ—ค ๋ฌถ์–ด์กŒ๋‹ค๋ฉด, partitioningBy๋Š” ์–ด๋–ค ์กฐ๊ฑด์— ์˜ํ•ด ๋ฌถ์–ด์ง€๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ GroupingBy๋ž‘ ๋˜‘๊ฐ™์ด Function ์ธํ„ฐํŽ˜์ด์Šค์ด๊ณ , Lambda ์‹์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

{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'}]}

๋ฐ˜ํ™˜ ๊ฒฐ๊ณผ ๋˜ํ•œ GroupingBy์™€ ๊ฐ™์ด Map์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ์ด ๋•Œ, ์กฐ๊ฑด์— ๋ถ€ํ•ฉํ•œ ๊ฒฝ์šฐ, true ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด false ์ชฝ์„ ๋ฐ”๋ผ๋ด…๋‹ˆ๋‹ค.

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

try-Catch ๋‹ค์Œ์—๋Š” finally๊ฐ€ ์žˆ์ฃ . Stream์—์„œ๋„ Collecting ๋‹ค์Œ์— ์ด์–ด์„œ ๋˜ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ถ”๊ฐ€๋กœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ”๋กœ collectingAndThen์ž…๋‹ˆ๋‹ค.

์œ„ ํ•จ์ˆ˜๋ฅผ ๋ณด๋ฉด, collect ์ดํ›„์— finisher๋ผ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์ฃ .

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

Collectors.toSet์„ ์ด์šฉํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ Set์œผ๋กœ collect ํ•œ ํ›„, ์ˆ˜์ • ๋ถˆ๊ฐ€๋Šฅํ•œ Set์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์„ ์ดˆ๊ฐ€๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

public static<T, R> Collector<T, R, R> of(
  Supplier<R> supplier, // new collector ์ƒ์„ฑ
  BiConsumer<R, T> accumulator, // ๋‘ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ๊ณ„์‚ฐ
  BinaryOperator<R> combiner, // ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ํ•จ์ˆ˜.
  Characteristics... characteristics) { ... }

๋งˆ์ง€๋ง‰์œผ๋กœ ์ง์ ‘ Collector๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ์žˆ๋Š” accumulator์™€ combiner๋Š” reduce ๋ฉ”์†Œ๋“œ์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

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

Product๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” Collector๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ์ž˜ ๋ณด๋ฉด, LinkedList๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ add ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€ํ•˜๊ณ , ๋‹ค์Œ์— ๋‚˜์˜ค๋Š” Combiner์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” first๋Š” LinkedList, second๋Š” Product ๊ฐ์ฒด๊ฐ€ ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ LinkedList๋กœ ํ•ด์ฃผ๋ฉด Collector๊ฐ€ ์™„์„ฑ์ด ๋˜๋Š” ๊ฒƒ์ด์ฃ .

๋”ฐ๋ผ์„œ ์ด Collector๋Š” ์ŠคํŠธ๋ฆผ ๊ฐ ์š”์†Œ์— ๋Œ€ํ•ด LinkedList๋ฅผ ๋งŒ๋“ค๊ณ , ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰ Combiner๋ฅผ ํ†ตํ•ด ์ด๋“ค์„ ๊ฒฐํ•ฉํ•˜๊ฒŒ ๋˜์ฃ . 

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

์ด๋ ‡๊ฒŒ collect ๋ฉ”์†Œ๋“œ์— ์œ„์—์„œ ๋งŒ๋“  ์ปค์Šคํ…€ Collector๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด, ๊ฒฐ๊ณผ๊ฐ€ ๋‹ด๊ธด LinkedList๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ฒ ์ฃ . ์ข€ ๋” ํšจ์œจ์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋ฉด, ์ปค์Šคํ…€ Collector๋„ ๋งค์šฐ ์“ธ๋งŒํ•˜๊ฒ ๊ตฐ์š”.

 

4. Matching

Matching์€ ์กฐ๊ฑด์‹์— ์ถฉ์กฑํ•˜๋Š” ๊ฐ’๋งŒ์„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ๋กœ ์กฐ๊ฑด์‹ Lambda์ธ Predicate๋ฅผ ๋ฐ›์•„๋‚ด ํ•ด๋‹น ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์š”์†Œ๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ธ ๊ฐ€์ง€ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ•˜๋‚˜๋ผ๋„ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•  ๊ฒฝ์šฐ (anyMatch)
  • ๋ชจ๋‘ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•  ๊ฒฝ์šฐ (allMatch)
  • ๋ชจ๋‘ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ (noneMatch)
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ฑด๋งŒ ์ž˜ ๋งž์ถฐ์ค€๋‹ค๋ฉด, ์œ ์šฉํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

List<String> names = Arrays.asList("Eric", "Elena", "Java");

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

String์— ์žˆ๋Š” ๊ธฐ๋ณธ ๋ฉ”์†Œ๋“œ๋งŒ์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. 1๋ฒˆ์งธ๋Š” a ์•ŒํŒŒ๋ฒณ์ด ํฌํ•จ๋œ ๋‹จ์–ด๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ๋‘ ๋ฒˆ์งธ๋Š” 3๊ธ€์ž ์ด์ƒ, ์„ธ ๋ฒˆ์งธ๋Š” s๋กœ ๋๋‚˜๋Š” ์•ŒํŒŒ๋ฒณ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์ด์ฃ .

 

5. Iterating

Iterating์€ ์ŠคํŠธ๋ฆผ ๋‚ด์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋“ค์„ foreach ๋ฌธ์„ ๋Œ๋ฉด์„œ ์‹คํ–‰ํ•˜๋Š” ์ตœ์ข… ์ž‘์—…์ž…๋‹ˆ๋‹ค.

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

์Œ ๊ทธ๋Ÿฐ๋ฐ, ์ด ์ž‘์—…์€ peek ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฑฐ๋ž‘ ๋˜‘๊ฐ™์ง€ ์•Š๋‚˜์š”? ์•„๋‹™๋‹ˆ๋‹ค. peek์€ ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ์˜ ์ค‘๊ฐ„ ๊ณผ์ •์„ ๋„์ถœํ•˜๋Š” ์ž‘์—…์ด์—ˆ๋””๋ฉด, ์ด ์ž‘์—…์€ ๋ชจ๋“  ๊ณ„์‚ฐ ๊ฒฐ๊ณผ์—์„œ ์ตœ์ข… ๊ฐ’์„ ๋„์ถœํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ง€ ์•Š๊ณ , ๊ฒฐ๊ณผ๊ฐ€ ์ œ๋Œ€๋กœ ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฌผ๋ก , ์—ฌ๊ธฐ์„œ ๋งˆ์ง€๋ง‰ ์ž‘์—…์œผ๋กœ ํ•œ ๋ฒˆ ๋” ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์œผ๋กœ Finally ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทธ๋ ‡๊ฒŒ๋Š” ๋ณดํ†ต ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

๋งˆ์น˜๋ฉฐ...

์—ฌ๊ธฐ๊นŒ์ง€ Java 8์˜ Stream์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค. ์ •๋ง ๊ธธ๋ฉด์„œ๋„ ์œ ์šฉํ•œ ์ ์ด ๋งŽ์•˜๋˜ ์ŠคํŠธ๋ฆผ์ž…๋‹ˆ๋‹ค๋งŒ ์ž˜ ์•Œ๊ณ  ์“ฐ๋Š” ๊ฒƒ๊ณผ ๋ชจ๋ฅด๊ณ  ์“ฐ๋Š” ๊ฒƒ์€ ์—„์—ฐํžˆ ์ฐจ์ด๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•œ ๋งŽ์€ ๋‚ด์šฉ์„ ๋‹ค๋ค„๋ดค์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ Stream์— ๋Œ€ํ•ด์„œ๋Š” ์˜ˆ์ „๋ถ€ํ„ฐ ๊ณ„์† ์จ์˜ค๊ณ  ์žˆ์—ˆ์ง€๋งŒ ๋„ˆ๋ฌด๋‚˜ ๋งŽ์€ ๋‚ด์šฉ๋“ค์ด ๋‹ด๊ฒจ์ ธ ์žˆ์–ด ์ •๋ฆฌํ•˜๊ธฐ๋„ ์–ด๋ ค์› ๊ณ , ์–ด๋–ป๊ฒŒ ์žฅ์ ์„ ์ž˜ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์„๊นŒ๋„ ๋งŽ์ด ๊ณ ๋ฏผ์ด ๋˜์—ˆ๋˜ ํŒŒํŠธ์˜€์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์„ ํ†ตํ•ด์„œ Java๋ฅผ ์ข€ ๋” ์‰ฝ๊ณ , ์ง๊ด€์ ์œผ๋กœ ์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐ ์ด๋ฐ”์ง€ ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

Ref: Java ์ŠคํŠธ๋ฆผ Stream (1) ์ด์ •๋ฆฌ

๋ฐ˜์‘ํ˜•
TAGS.

Tistory Comments