[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๊ฐ์ง ๊ณผ์ ์ ๋ฐ๋ฆ ๋๋ค.
- Stream ์ธ์คํด์ค ์์ฑ.
- ๋ฐ์ดํฐ์ ๊ฐ๊ณต
- ์ต์ข ๊ฒฐ๊ณผ ๋์ถ.
๊ธ๋ก๋ง ์ค๋ช ํ๋ฉด ์ ๋ชจ๋ฅผ ์ ์์ผ๋, ๋ช ๊ฐ์ง ์์ ๋ฅผ ๊ฐ์ง๊ณ ํ ๋ฒ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ๋จผ์ ๋ค์๊ณผ ๊ฐ์ด ๋ฐฐ์ด๋ก ๋ฐ์ดํฐ๋ฅผ ํ ๊ฐ ๋ง๋ค์ด๋ณด๋๋ก ํ์ฃ .
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๋ฒ ์ํํ๋ค.
- ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ๊ธฐ ์ํด ์๋ก์ด ์๋ฃ ๊ตฌ์กฐ๋ฅผ ์์ฑํ๋ค.
- ๋ฐ๋ณต๋ฌธ ๋ด์ ํน์ ์กฐ๊ฑด์ ์ฃผ๊ณ , ํด๋น ์กฐ๊ฑด์ ๋ถํฉํ๋ฉด ํด๋น ์๋ฃ๊ตฌ์กฐ์ ๊ฐ์ ์ฝ์ ํ๋ค.
๋ฌผ๋ก ํ์ฌ๋ ๊ฐ๋จํ ์๋ฃ๊ตฌ์กฐ์ด๊ณ , ์๊ณ ๋ฆฌ์ฆ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฐ ๋ฐฉ๋ฒ์ด ํฌ๊ฒ ์ง์ฅ์ ๋ฐ์ง ์๊ฒ ์ง๋ง, ์ด ๋ก์ง์ด ์ปค์ง๊ณ , ๋ณต์กํด์ง๊ณ , ๋ฐ์ดํฐ๊ฐ ์ปค์ง๋ฉด ๋์กํด์ง๊ฒ ์ฃ ?
๊ทธ๋์ ์ฐ๋ฆฌ๋ 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๋ฅผ ์ข ๋ ์ฝ๊ณ , ์ง๊ด์ ์ผ๋ก ์ฝ๋ฉํ ์ ์๋๋ก ํ๋ ๋ฐ ์ด๋ฐ์ง ๋์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.
'Programming > Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[RxJava] 2. Reactive ๊ธฐ๋ณธ ์ฐ์ฐ์(Operator) - map, filter, reduce (0) | 2021.01.23 |
---|---|
[RxJava] 1. RxJava์ ๊ธฐ๋ณธ - Observable (0) | 2021.01.10 |
[RxJava] RxJava๋ก ์์ํ๋ Java Reactive ํ๋ก๊ทธ๋๋ฐ (5) | 2021.01.09 |
[GP] Junit5๋ฅผ ์ฌ์ฉํ Java ํ ์คํธ ์ฝ๋ ์์ฑ (0) | 2019.09.29 |
Java Puzzlers - Scraping the Bottom of the Barrel (Google I/O 2011) (0) | 2013.08.07 |