#11 Stream API
by JiwonDev์คํธ๋ฆผ์ ์ฌ์ฉํ๋๋ฐ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก 1. Stream ์์ฑ 2. ์ค๊ฐ์ฐ์ฐ 3. ์ต์ข ์ฐ์ฐ(Terminal Op) ์ผ๋ก ๋๋ ์ ์๋ค.
# Stream API ํน์ง
- Java์ ๋ชจ๋ Collection์ ๋ด๋ถ์ stream()์ด๋ผ๋ ๊ธฐ๋ณธ ๋ฉ์๋๊ฐ ์กด์ฌํ๋ค. ์ปฌ๋ ์ ์ด ์๋ ๊ฐ๋ค์ Stream.of(~) ๋ฉ์๋๋ก ์์ฑํ ์ ์๋ค.
- ์ค๊ฐ์ฐ์ฐ ๋ฉ์๋๋ ์ต์ข ์ฐ์ฐ์ด ๋์ค๊ธฐ ์ ๊น์ง ์คํ๋์ง ์๋๋ค. ๋ํ ์ค๊ฐ์ฐ์ฐ ๋ฉ์๋๋ Stream์ ๋ฐํํ๋ฏ๋ก Stream.funcA(~).funcB(~).funcC(~) ๊ฐ์ด ๋ฉ์๋ ์ฒด์ด๋์ด ๊ฐ๋ฅํ๋ค.
- ํ๋ฒ ์ฌ์ฉํ ์คํธ๋ฆผ์ ์ฌ์ฌ์ฉ ํ ์ ์๋ค. ์ฆ ์ต์ข ์ฐ์ฐ ์ดํ stream API ์ฐ์ฐ์ ๋ค์ ํ ์ ์๋ค.
- Stream API๋ ํจ์ํ ์ธํฐํ์ด์ค์ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ข๊ฒ ์ค๊ณ๋์ด์๋ค.
๋ฌผ๋ก ํ์์ ๋ฐ๋ผ (value) -> {~} ์ผ๋ก ์ง์ ๊ตฌํํด๋ ๋๋ค.
# 0. Boxing ์ด๋
Wrapper Class๋ค์ ์ด์ฉํด์ ๊ธฐ๋ณธํ์ ๋ค(Primitive Type)๋ฅผ ๊ฐ์ฒด(Reference Type)์ผ๋ก ๋ณํํ๋ ๊ฒ์ ๋ฐ์ฑ(Boxing), ๋ฐ๋๋ก ๊ธฐ๋ณธ ํ์ ์ผ๋ก ๋๋๋ฆฌ๋ ๊ฒ์ ์ธ๋ฐ์ฑ(unBoxing)์ด๋ผ ํ๋ค. ์ฐธ๊ณ ๋ก ์๋ฐ ์ด๊ธฐํ ๋ฌธ๋ฒ์์ ์คํ ๋ฐ์ฑ์ ์ง์ํด์ค๋ค.
Integer num = new Integer(17); // ๋ฐ์ฑ int n = num.intValue(); // ์ธ๋ฐ์ฑ // ์ด๋ ๊ฒ ์ฌ์ฉํด๋ ๋ด๋ถ์ ์ผ๋ก ๋ฐ์ฑ/์ธ๋ฐ์ฑ์ด ์ด๋ฃจ์ด์ง๋ค. Character ch = 'X'; // Character ch = new Character('X'); : ์คํ ๋ฐ์ฑ char c = ch; // char c = ch.charValue(); : ์คํ ์ธ๋ฐ์ฑ

- ๋ฐ์ฑ์ ๊ธฐ๋ณธํ์ ์ ๊ฐ์ฒด๋ก ๋ง๋ค์ด ์ฌ๋ฌ๊ฐ์ง ์ถ๊ฐ๊ธฐ๋ฅ์ ๋ฃ์ ์ ์์ง๋ง, ๊ทธ๋งํผ ๊ธฐ๋ณธํ์ ๋ง ์ฌ์ฉํ์ ๋ ๋ณด๋ค ๋น์ฉ์ด ์ถ๊ฐ์ ์ผ๋ก ๋ฐ์ํจ์ ์ ์ํด์ผ ํ๋ค.
- Stream์์๋ ๋ง์ฐฌ๊ธฐ์ง๋ก Stream<Integer>์ ๊ฐ์ด ๊ธฐ๋ณธํ์ ๋ฐ์ฑํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ ์ฑ๋ฅ์ ํฌ๊ฒ ์ ํ์ํค๊ธฐ์ ๊ธฐ๋ณธํ ํนํ ์คํธ๋ฆผ (IntStream, LongStream, DoubleStream ๋ฑ)์ ๋ฐ๋ก ์ ๊ณตํด์ค๋ค.
- IntStream๊ณผ ๊ฐ์ ๊ธฐ๋ณธํ ์คํธ๋ฆผ์ .boxed() ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ฑํ ์ ์๋ค.
// ๊ธฐ๋ณธํ(Primitive Type) ์คํธ๋ฆผ์ ๋ฐ์ฑํ๋ ๋ฐฉ๋ฒ. Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
- ๋ฐ๋๋ก Stream์ ๊ธฐ๋ณธํ ์คํธ๋ฆผ์ผ๋ก ๋ฐ๊พธ๊ณ ์ ํ๋ค๋ฉด .mapToInt() .mapToLong() ๊ฐ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
// .mapToType() ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ๊ธฐ๋ณธํ ์คํธ๋ฆผ์ผ๋ก ๋ณํ ๊ฐ๋ฅํ๋ค. // ์ด ๋ฉ์๋๋ ํ์
์ ๋ณ๊ฒฝํ๋ ๊ฒ ์ด์ธ์๋ .map()๊ณผ ๋์ผํ ๊ธฐ๋ฅ์ ํ๋ค. Stream.of(1.0, 2.0, 3.0) // Stream<Double> ์์ฑ .mapToInt(Double::intValue) // IntStream์ผ๋ก ๋ณํ // .mapToObj() ๋ฅผ ์ด์ฉํ๋ฉด .boxed()์ฒ๋ผ IntStream -> Stream<Intger> ๋ก ๋ณํ๋ ๊ฐ๋ฅํ๋ค. IntStream.range(1, 4).mapToObj(i -> "a" + i)
# 1. ์คํธ๋ฆผ ์์ฑํ๊ธฐ
# ๋ฐฐ์ด (Array) Stream
๋ฐฐ์ด์ ๋ด๋ถ์ stream ๋ฉ์๋๊ฐ ์๋ค. ๋ณดํต Arrays ์์ ์๋ 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 Stream
์ปฌ๋ ์ ์ ๊ฒฝ์ฐ ๋ด๋ถ์ Stream ๋ฉ์๋๊ฐ ์กด์ฌํ๋ฏ๋ก, ์์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
List<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream(); Stream<String> parallelStream = list.parallelStream(); // ๋ณ๋ ฌ ์ฒ๋ฆฌ ์คํธ๋ฆผ
# Empty Stream (null ๊ฐ ๋์ฒด)
๋น ์คํธ๋ฆผ์ด ํ์ํ ๊ฒฝ์ฐ Stream.empty()๋ฅผ ์ด์ฉํ์ฌ ๋น ์คํธ๋ฆผ์ ์์ฑํ ์๋ ์๋ค. ์ด๋ null ๊ฐ ์ฌ์ฉ์ ๋์ ํด์ค๋ค.
// ์ปฌ๋ ์
์ ๊ฐ์ด ๋น์๋ค๊ณ ์คํธ๋ฆผ ์ฐ์ฐ์ ๊ฐ์ null๋ก ์ฃผ๋ ๊ฑด ์ค๋ฅ๋ฅผ ๋ฐ์ ์ํฌ ์ ์๋ค. list.isEmpty() ? Stream.empty() : list.stream();
# ๊ธฐ๋ณธ ํ์ ํ Stream
๊ธฐ๋ณธํ์ ์ ์ฐ์๋ ๊ฐ๋ค์ Stream์ผ๋ก ๋ง๋ค ๋, ๊ฐ๊ฐ์ Stream ๊ฐ์ฒด์์์๋ range๋ฅผ ์ด์ฉํด์ ๋ง๋ค ์ ์๋ค.
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4] LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5] // Randomํ ์๊ฐ ํ์ํ๋ค๋ฉด DoubleStream doubles = new Random().doubles(3); // ๋์ 3๊ฐ ์์ฑ
๋ฌผ๋ก ํน์ ๊ฐ์ด ํ์ํ๋ค๋ฉด Stream.of() ๋ฅผ ์ด์ฉํด์ ๋ง๋๋ ๋ฐฉ๋ฒ๋ ์๋ค.
public static void main(String args[]) { Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5); // 1,2,3,4,5 ์์ฑ List<Integer> numbers = stream.map(x -> x + 1) .filter(x -> x % 2 == 0) .collect(Collectors.toList()); numbers.stream().forEach(System.out::println); // 2, 4, 6 }
# ๋ฌธ์ Stream
๋ณดํต์ String ์์ฒด๋ฅผ ์คํธ๋ฆผ์ ๋ฃ์ด ์ฌ์ฉํ์ง๋ง, ๋ฌธ์์ด ์์ฒด๋ฅผ ๋ฌธ์(char)๋ก ๋๋์ด ์คํธ๋ฆผ์ ๋ง๋ค๊ณ ์ถ๋ค๋ฉด str.chars()๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค.
// Char ๊ฐ์ IntStream์ ๋ฃ๊ฒ๋๋ฉด ํด๋น ๋ฌธ์์ ์ฝ๋ํ๋ก ์ ํ๋๋ค. IntStream charsStream = "Stream".chars(); // [83, 116, 114, 101, 97, 109] // ๋ณดํต์ ๊ตณ์ด Char๋ก ๋ณํํ์ง์๊ณ , String ๊ฐ ์์ฒด๋ฅผ ์ด์ฉํฉ๋๋ค. Stream<String> stringStream = Pattern.compile("- ").splitAsStream("Eric- Elena- Java"); // ["Eric", "Elena", "Java"]
# ์คํธ๋ฆผ ์ฐ๊ฒฐํ๊ธฐ (Stream.concat)
์ฌ๋ฌ๊ฐ๋ก ๋๋์ด์ ธ์๋ ์คํธ๋ฆผ์ 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]
# Stream.builder (์คํธ๋ฆผ์ ์ง์ ๊ฐ ์ถ๊ฐ)
๋ค๋ฅธ ๊ฐ์ ์ฐ๊ฒฐํ๊ฑฐ๋ ๋ณํํ์ง์๊ณ ์ง์ ํด๋น Stream์ ๊ฐ์ ์ถ๊ฐํ๊ณ ์ถ๋ค๋ฉด Stream.builder()๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. ํด๋น ์คํธ๋ฆผ์์ builder() ๋ฉ์๋๋ฅผ ํธ์ถํ ํ, ์ํ๋ ์์ ์ ํ๊ณ ๋ง์ง๋ง์ .build() ํด์ฃผ๋ฉด ์๋ฃ๋๋ค.
Stream<String> builderStream = Stream.<String>builder() .add("Eric").add("Elena").add("Java") .build(); // [Eric, Elena, Java]
# Stream.generate (ํจ์๋ก ์คํธ๋ฆผ ๊ฐ ์์ฑ)
Stream.generate(~)๋ฅผ ์ด์ฉํ๋ฉด ํจ์๋ฅผ ์ด์ฉํด ๊ฐ์ ์์ฑํด ๋ผ ์ ์๋ค. ๋ฌผ๋ก ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๊ฐ์๊ฐ ๋ฌดํ์ธ(infinite) ์คํธ๋ฆผ์ด ๋ง๋ค์ด์ง๊ธฐ์ .limit(n)๋ .skip(n)์ ์ด์ฉํด ๊ฐ์๋ฅผ ์ ํํ์ฌ ์คํธ๋ฆผ์ ์์ฑํ ์ ์๋ค.
- .limit(n) ์ฒ์๋ถํฐ n๊ฐ ๊น์ง์ ๊ฐ๋ง ์ฌ์ฉํ๋ค.
- .skip(n) ๊ฐ์ ์์์๋ถํฐ n๊ฐ ๋ฐ์ด๋๊ณ ๋๋จธ์ง ๊ฐ์ ์ฌ์ฉํ๋ค.
// java.util.function์ ์๋ Supplier๋ฅผ ์ด์ฉํด๋ ๋๋ค. ๋ฉ์๋ ์ํ๋ ๊ทธ๋ฌํจ. public static<T> Stream<T> generate(Supplier<T> s) { ... } Stream<String> generatedStream = Stream.generate(() -> "A").limit(4); // ["A", "A", "A", "A"]
public static void main(String args[]) { Supplier<String> supplyHi = () -> "Hi"; Stream<String> generatedStream = Stream.generate(supplyHi).limit(4); String str = generatedStream.collect(Collectors.joining(" ")); System.out.println(str); // Hi Hi Hi Hi }
# Stream.iterate()
generate์ ๋น์ทํ๋ฐ, ๋ค๋ฅธ ์ ์ ์ด๊ธฐ๊ฐ์ ์ฃผ๊ณ ๋ฉ์๋๋ฅผ ์ดํฐ๋ ์ดํฐ์ฒ๋ผ ์ฌ์ฉํ๋ค.
Stream<Integer> iteratedStream = Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]
# ํ์ผ Stream
์ ์ฌ์ฉํ์ง๋ ์์ง๋ง, ํ์ผ ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด ์คํธ๋ฆผ์ผ๋ก ์ฌ์ฉ ํ ์ ์๋ค.
// ํ์ผ์ String ์คํธ๋ฆผ์ผ๋ก ๋ณ๊ฒฝ Stream<String> lineStream = Files.lines(Paths.get("file.txt"), Charset.forName("UTF-8"));
# ๋ณ๋ ฌ ์คํธ๋ฆผ
Stream ๋์ parallelStream์ ์ด์ฉํ์ฌ ๊ฐ๋จํ๊ฒ ๋ณ๋ ฌ ์คํธ๋ฆผ์ ๋ง๋ค ์ ์๋ค. parallelStream์ ์ฐ๋ ๋๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ด๋ถ์ ์ผ๋ก Fork/Join framework๋ฅผ ์ฌ์ฉํ๋ค.
// ๋ณ๋ ฌ ์คํธ๋ฆผ ์์ฑ Stream<Product> parallelStream = productList.parallelStream(); // ๋ณ๋ ฌ ์ฌ๋ถ ํ์ธ boolean isParallel = parallelStream.isParallel(); // ์์ public static void main(String[] args) { List<String> list = Arrays.asList("AAA","BBB","CCC","DDD","EEE"); //์์ฐจ์ฒ๋ฆฌ Stream<String> stream = list.stream(); stream.forEach(System.out::println); //๋ณ๋ ฌ์ฒ๋ฆฌ Stream<String> parallelStream = list.parallelStream(); parallelStream.forEach(System.out::println); }
# 2. ์คํธ๋ฆผ ๊ฐ๊ณตํ๊ธฐ (์ค๊ฐ์ฐ์ฐ)
# filter
ํ์ฌ ์์๋ฅผ ์กฐ๊ฑด์ ๋ฐ๋ผ ๊ฑธ๋ฌ๋ด๋ ์์ ์ ํด์ค๋ค.
Stream<T> filter(Predicate<? super T> predicate);
List<String> names = Arrays.asList("Eric", "Elena", "Java"); Stream<String> stream = names.stream() .filter(name -> name.contains("a")); // ์ด๋ฆ์ a๊ฐ ํฌํจ๋๋ ๋จ์ด๋ง ๋จ๊ฒ๋จ. ex) [Elena, Java]
# map
๊ฐ๊ฐ์ ์์๋ฅผ input์ผ๋ก ๋ฐ์์ ์๋ก์ด ๊ฐ์ผ๋ก ๋ณํํจ. ์ด๋ฌํ ์์ ์ Mapping์ด๋ผ๊ณ ๋ถ๋ฅด๊ธฐ๋ ํ๋ค.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
List<String> names = Arrays.asList("Eric", "Elena", "Java"); Stream<String> stream = names.stream() .map(String::toUpperCase); // [ERIC, ELENA, JAVA] // ์ด๋ฐ ์์ผ๋ก ๊ฐ์ฒด ์์ ํน์ ํ๋๋ฅผ ๊บผ๋ด์ฌ ์๋ ์์. Stream<Integer> stream = productList.stream() .map(Product::getAmount); // [23, 14, 13, 23, 13]
# flatMap
์ฝ๊ฒ ์ค๋ช ํ๋ฉด, ์์๊ฐ ์ค์ฒฉ๋์ด์์ผ๋ฉด ํ๋จ๊ณ ํ์ด์ฃผ์ด ์ฝ๊ฒ ์ฌ์ฉํ๊ฒ ๋ง๋ค์ด์ฃผ๋ map์ด๋ค.
๋ฐฐ์ด์ ์์๊ฐ ์๋ ๋ฐฐ์ด ์์ฒด๋ฅผ ์ ๋ ฅ๊ฐ์ผ๋ก ๋ฐ๋๊ฒฝ์ฐ [ [1,2,3], [4,5,6] ] ์ฒ๋ผ ์ค์ฒฉ๋๋ ์คํธ๋ฆผ์ด ๋ง๋ค์ด์ง๋ค.
์ด๋ฅผ ์คํธ๋ฆผ์ผ๋ก ์์ ํ๊ฒ ๋๋ฉด [1, 2, 3, 4, 5, 6]์ด ์๋๋ผ [1,2,3] [4,5,6] ์ผ๋ก ์คํธ๋ฆผ ์์๊ฐ ๊ฒฐ์ ๋์ด ์คํธ๋ฆผ ๋ฐํ๊ฐ์ด ์ค์ฒฉ๋์ด ๋ค์ ์์ ์ ํ๊ธฐ ๊น๋ค๋ก์์ง๋๋ฐ, flatMap์ ์ฌ์ฉํ๋ฉด ๋ฐํ๊ฐ์ด ์ค์ฒฉ๋์ด์์ผ๋ฉด ์ค์ฒฉ์ 1๋จ๊ณ ์ ๊ฑฐํด์ค๋ค.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Stream<String[]> strStream = Stream.of( new String[] {"a", "b", "c"}, new String[] {"d", "e", "f"} ); // ์์ ๊ฐ์ ๊ทธ๋ฅ map์ ์ฌ์ฉํ๋ฉด 2์ค Stream์ด ๋ฐํ๋จ // ์์๊ฐ ์ด 2๊ฐ์. ["a", "b", "c"], ["d", "e", "f"] // Arrays.stream( ["a", "b", "c"] )... Stream<Stream<String>> stream = strStream.map(Arrays::stream); // flatMap์ ์ฌ์ฉํ๋ฉด 1์ค Stream์ผ๋ก ์ฐจ์์ ๋ฎ์ถ ์ ์์ // ์์๊ฐ ์ค์ฒฉ๋์ด์๋ค๋ฉด ํ๋จ๊ณ ํ์ด์ค. ์์๊ฐ ์ด 6๊ฐ์ ["a","b","c","d","e","f"] // Arrays.stream( "a" )... Stream<String> stream = strStream.flatMap(Arrays::stream);
Map vs FlatMap ์์ธ์ค๋ช
Stream<String[]> ์ฒ๋ผ ์ ๋ ฅ๊ฐ ์์ฒด๊ฐ ๋ฆฌ์คํธ๋ก ์ฃผ์ด์ง ๊ฒฝ์ฐ
Map => ์์ 2๊ฐ [1,2], [1,1] => [ [1,2], [1,1] ] ๋ฐํ
FlatMap => ์์๊ฐ ์ค์ฒฉ๋์ด์์ผ๋ฉด ํ๋ฒ ์ ๊ฑฐ ํ ์ฌ์ฉ, ์์ 4๊ฐ [ [1, 2], [1, 1] ] => [1,2,1,1] ๋ฐํ
* ์ฐธ๊ณ ๋ก Stream<String[]>์ ํด์ฃผ์๋คํด์ ๋ฐฐ์ด์์ ์๋ ์์๊น์ง ์คํธ๋ฆผํ ๋ ๊ฒ์ ์๋๋ค. ์ฃผ์๊ฐ์ด ์คํธ๋ฆผํ ๋ ๊ฒ์ด๋ฏ๋ก ์์ ๋ค์ด์๋ ์์๋ค์ ์๋ณธ ๊ทธ๋๋ก์ด๊ธฐ์ ์์ ์๋ ๊ฐ๋ค์ Arrays.Stream์ผ๋ก ๋ณต์ฌํด์ฃผ์ด์ผํ๋ค.


์์
String[] arrayOfWords = {"Hello", "World"}; Stream<String> streamOfWords = Arrays.stream(arrayOfWords); streamOfWords.map(s->s.split("")) // ๋ฌธ์์ด์ Array๋ก ๋ฐ๊ฟ. .map(Arrays::stream) // ํ๋์ ๋ฐฐ์ด [H,e,l,l,o]์ Stream<String[]> ์ผ๋ก ๋ฐ๊ฟ. .distinct() // ์ค๋ณต์ ์ ๊ฑฐํ๊ณ ์ถ์์ผ๋ ๋ฐฐ์ด์ด ์ค์ฒฉ๋์ด ๋์ํ์ง ์๋ ์ฝ๋. .collect(Collectors.toList()); // [ ["H","e","l","l","o"], ["W","o","r","l","d"] ]

1. map(s->s.split)์ ์ํด "HELLO"๊ฐ [H,E,L,L,O]๋ก ๋ถ๋ฆฌ๋๋ค.
2. ํ์ฌ ์คํธ๋ฆผ์ ์ค์ฒฉ๋์ด์์ผ๋ฏ๋ก distinct()๋ ์๋ฌด๋ฐ ์ญํ ์ ํ์ง ์๋๋ค.
3. ์ฆ ๊ฒฐ๊ณผ๋ฌผ๋ ๊ทธ๋๋ก ์ค์ฒฉ๋ [ [H,E,L,L,O], [W,O,R,L,D] ] ๋ฐฐ์ด์ด ๋ฆฌํด๋๋ค.
์ด๋ฅผ FlatMap์ ์ด์ฉํด์ฃผ๋ฉด ์ฝ๊ฒ ํด๊ฒฐ ํ ์ ์๋ค.
String[] arrayOfWords = {"Hello", "World"}; Stream<String> streamOfWords = Arrays.stream(arrayOfWords); streamOfWords.map(s->s.split("")) // ๋ฌธ์์ด์ Array๋ก ๋ฐ๊ฟ .flatMap(Arrays::stream) //๊ฐ๊ฐ์ ์์๊ฐ ์ค์ฒฉ์ด ์ ๊ฑฐ๋๊ณ , Stream<String>์ด ์ ์ฉ๋จ .distinct() // ์ค๋ณต์ด ์ ๊ฑฐ๋จ .collect(Collectors.toList()); // ["H","e","l","o","W","r","d"]

1. map(s->s.split(""))์ ๊ฒฐ๊ณผ๋ก ๋์จ ์ด์ค ๋ฐฐ์ด [ [H,E,L,L,O] [W,O,R,L,D] ]๋ฅผ FlatMap์ ๋๊ธด๋ค.
2. FlatMap์ [H,E,L,L,O] , [W,O,R,L,D]๋ก ๋ฐฐ์ด 2๊ฐ๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ๋๋ค. FlatMap์ ์์ ๋ง๋ค ์ค์ฒฉ์ ์ ๊ฑฐํ์ฌ H,E,L,L,O,W,O,R,L,D๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ๊ณ ์ด๋ฅผ Arrays.stream(~)์ ์ด์ฉํ์ฌ ๊ฐ๊ฐ์ ๋จ์ด๋ฅผ Stream<String>์ผ๋ก ๋ณํํ๋ค.
๋น์ฐํ๊ฑฐ์ง๋ง, flatMap๋ map๊ณผ ๋์ผํ๊ฒ ์์ฉ๊ฐ๋ฅํ๋ค.
// students ๋ Student ๊ฐ์ฒด๊ฐ ๋ค์ด๊ฐ์๋ list์ด๋ค. students.stream() .flatMapToInt(student -> // ํ์๊ฐ์ฒด๋ฅผ [๊ตญ์ด์ ์,์์ด์ ์,์ํ์ ์] ๋ก ๋ง๋ ๋ค. IntStream.of(student.getKor(), student.getEng(), student.getMath())) // ๊ทธ๋ฅ map์ ์ฌ์ฉํ๋ฉด students stream์ ํ ์์์ "[๊ตญ์ด,์์ด,์ํ]" ์ด๋ ๊ฒ ์ ์ฅ๋๋ค. // flatMap์ ์ด์ฉํ์ฌ "๊ตญ์ด,์์ด,์ํ" ์ผ๋ก ์ค์ฒฉ๋์ด์๋ ๋ฐฐ์ด์ ํ์ด์ฃผ์ด์ผ average()๊ฐ ๊ฐ๋ฅํ๋ค. .average().ifPresent(avg -> System.out.println(Math.round(avg * 10)/10.0)); // ๊ฐ ํ์์ ํ๊ท ์ ์.
# sorted
๊ฐ์ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ค. ํ์ํ๋ค๋ฉด sorted(~)์ ๋น๊ต ํจ์(Comparator)๋ฅผ ์ค ์ํ๋ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌํ ์ ์๋ค.
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
// ์ค๋ฆ์ฐจ์ ์ ๋ ฌ intList.stream().sorted() // ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ intList.stream().sorted(Comparator.reverseOrder()) // ํด๋น ๊ฐ์ฒด๊ฐ Comparable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ง ์๋๋ค๋ฉด ์ด๋ ๊ฒ ๋ด๋ฆผ์ฐจ์ ๊ตฌํ๊ฐ๋ฅ. strList.stream().sorted(Comparator.comparing(String::length).reversed()) // ๋ฉ์๋ ๋ ํผ๋ฐ์ค ์ฌ์ฉ (Comparable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์๋ ๊ฐ์ฒด๋ง ๊ฐ๋ฅ) intList.stream().sorted(Integer::compareTo) // ๋๋ค ์ฌ์ฉ (Comparable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์๋ ๊ฐ์ฒด๋ง ๊ฐ๋ฅ) intList.stream().sorted((a, b) -> a.compareTo(b)) // ์ต๋ช
ํด๋์ค๋ก Comparator ์ง์ ๊ตฌํ // compareTo()๋ intํ์ ๋ฐํํ๋ค. a ๋ณด๋ค (ํฌ๋ค 1), (๊ฐ๋ค 0), (์๋ค -1) intList.stream().sorted(new Comparator<Integer>() { @Override public int compare(Integer a, Integer b) { return a.compareTo(b); } })
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]
# ์ดํฐ๋ ์ดํฐ (peek)
map์ ์คํธ๋ฆผ ์์๋ฅผ ์ง์ ์์ ํ๋ค. ๋ง์ฝ ์์ ์ด ํ์์๊ณ ์ฝ๊ธฐ๋ง ํ๋ค๋ฉด peek์ ์ฌ์ฉํ๋ฉด ๋๋ค.
peek๋ Stream์ ๋ฐํํ๋๋ฐ, ๋ง์ฝ ์ถ๋ ฅํ ๊ฐ์ด ์์ด์ ๋๋ด๊ณ ์ถ๋ค๋ฉด forEach (map๊ณผ ๋์ผ) ์ฌ์ฉํ๋ฉด ๋๋ค.
Stream<T> peek(Consumer<? super T> action);
int sum = IntStream.of(1, 3, 5, 7, 9) .peek(System.out::println) // ์คํธ๋ฆผ ์์๋ค์ ์์ ํ ์ ์๋ค. ์ฝ๊ธฐ๋ง ํ๋ค. .sum();
# Optional API (null ๊ฐ ์ฒ๋ฆฌ, boxing)
2021.07.12 - [Backend/Java] - #12. Optional API (*Null๊ฐ ์ฒ๋ฆฌ)


#3. ์คํธ๋ฆผ ๊ฒฐ๊ณผ ๋ง๋ค๊ธฐ (์ต์ข ์ฐ์ฐ)
# ๋จ์ผ ๊ฐ (Calculating)
์คํธ๋ฆผ์ ์์๋ค์ ํ๋์ ๊ฐ์ผ๋ก ๋ง๋ ๋ค. .count() .sum() .min() .max() ๋ฑ ๊ธฐ๋ณธํ ํ์ ์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ ๋ค.
long count = IntStream.of(1, 3, 5, 7, 9).count(); long sum = LongStream.of(1, 3, 5, 7, 9).sum();
count๋ sum์ ์์๊ฐ ์์ผ๋ฉด 0์ ๋ฐํํ๋ฉด ๋์ง๋ง, min, max, average์ฒ๋ผ ๊ฒฐ๊ณผ๊ฐ ์๋ ๊ฒฝ์ฐ Null๊ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด Optional ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค.
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max(); DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5) .average() .ifPresent(System.out::println);
# Reduction (reduce)
์ง์ ์ฐ์ฐ์ ๊ตฌํํ๊ณ ์ถ๋ค๋ฉด, reduce๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
.reduce(์ด๊น๊ฐ, ํจ์, Combiner)์ด๋ค. ์ฐธ๊ณ ๋ก combiner๋ ๋ณ๋ ฌ ์คํธ๋ฆผ์ ํ๋๋ก ํฉ์น๋ reduce ์ฐ์ฐ์ด๋ค.
// ์ธ์ 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);
// ์ธ์๊ฐ ํ๋์ธ ๊ฒฝ์ฐ (ํจ์) OptionalInt reduced = IntStream.range(1, 4) // [1, 2, 3] .reduce((a, b) -> Integer.sum(a, b)); // ๋๋ค์์ผ๋ก ํํ // ์ธ์๊ฐ 2๊ฐ์ธ ๊ฒฝ์ฐ (์ด๊น๊ฐ, ํจ์) int reducedTwoParams = IntStream.range(1, 4) // [1, 2, 3] .reduce(10, Integer::sum); // ๋ฉ์๋ ๋ ํผ๋ฐ์ค๋ก ํํ // ์ธ์๊ฐ 3๊ฐ์ธ ๊ฒฝ์ฐ (์ด๊น๊ฐ, ํจ์, Combiner) Integer reducedParallel = Arrays.asList(1, 2, 3) .parallelStream() // ๋จ์ผ Stream์ ๊ฒฝ์ฐ Combiner๋ ์ฌ์ฉ๋์ง ์๋๋ค. .reduce(10, Integer::sum, (result1, result2) -> { // Combiner. ๊ฐ๊ฐ์ ์ค๋ ๋ ๊ฒฐ๊ณผ๋ฅผ ํฉ์น๋ reducer System.out.println("combiner was called"); return result1 + result2; });
// ๋ง์ง๋ง ๋ณ๋ ฌ ์คํธ๋ฆผ ์คํ๊ฒฐ๊ณผ. ์ค๋ ๋๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ์์์๋ถํฐ ์์ฐจ์ ์ผ๋ก ์คํ๋๋ค. combiner was called // result1 + result2 combiner was called // [์์์ ๊ณ์ฐํ result] + result2 36
# Collect (์ปฌ๋ ์ ๋ฐํ)
์ํ๋๋ฐ๋ก ๊ฐ๊ณต์ด ๋๋ Stream์ ํ๋์ ๊ฐ์ด ์๋๋ผ Collection์ผ๋ก ๋ฐ๊พธ์ด์ฃผ๋ ์ต์ข ์ฐ์ฐ(Terminal) ๋ฉ์๋์ด๋ค. ํด๋น ๋ฉ์๋๋ Collectors ํด๋์ค์ ๋ชจ์ฌ์๋ค.
Stream.of(1, 3, 3, 5, 5) .filter(i -> i > 2) .map(i -> i * 2) .map(i -> "#" + i) .collect(joining("-", "<=", "=>")) // "<=#6 - #6 - #10 - #10=>"
Collectors.joining(๊ตฌ๋ถ์, prefix, postfix) : stream์ ์์๋ค์ ํ๋์ String์ผ๋ก concat ํ๋ค.
Collectors.toList() : stream์ ์์๋ค์ List์ ๋ด์์ return ํ๋ค
Collectors.toSet() : stream์ ์์๋ค์ set์ ๋ด์์ return ํ๋ค. ( * distinct() ๋ผ๋ ์ค๊ฐ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ๋ฉด, toList๋ฅผ ํตํด์๋ ์์๊ฐ ๋ณด์ฅ๋ set์ ๊ตฌํ ํ ์ ๋ ์๋ค.)
# ๊ธฐ๋ณธ ์๋ฃํ ์ปฌ๋ ์ Collectors.method()
.sum(), .average() ์ ๊ฐ์ ๋ฐํํ๋ ๋ฐ๋ฉด, collect(Collectors.๋ฉ์๋)๋ ์ปฌ๋ ์ ์ ๋ฐํ๋ฐ์ ์ ์๋ค.
// ์คํธ๋ฆผ์ ํตํด int ์์๋ค์ ๋ฐ์ ์ ์ฒด ํ๊ท ์ Double ์ปฌ๋ ์
์ผ๋ก ๋ฐํํจ. Double averageAmount = productList.stream() .collect(Collectors.averagingInt(Product::getAmount)); // ์คํธ๋ฆผ์ ํตํด int ์์๋ค์ ๋ฐ์ ์ ์ฒด ํฉ์ Integer ์ปฌ๋ ์
์ผ๋ก ๋ฐํํจ. Integer summingAmount = productList.stream() .collect(Collectors.summingInt(Product::getAmount)); // SummaryStatistics ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ์ฌ๋ฌ ํต๊ณ ๊ฐ์ ํ๋ฒ์ ์ป์ ์ ์๋ค. // IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23} IntSummaryStatistics statistics = productList.stream() .collect(Collectors.summarizingInt(Product::getAmount)); statistics.getCount() statistics.getSum() statistics.getAverage() statistics.getMin() statistics.getMax()
// ๋ฌผ๋ก ๊ธฐ๋ณธํ ์คํธ๋ฆผ์ผ๋ก ์ ํํด์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์๋ค. Integer summingAmount = productList.stream() .mapToInt(Product::getAmount) // ๊ธฐ๋ณธํ int ์คํธ๋ฆผ์ผ๋ก ์ ํ .sum(); // sum ์ฌ์ฉ๊ฐ๋ฅ
# Collectors.groupingBy()
ํน์ ์กฐ๊ฑด์ผ๋ก ์์๋ค์ ๊ทธ๋ฃนํ ํ๋ ๋ฐฉ๋ฒ๋ ์๋ค. ๊ฒฐ๊ณผ๋ Map<Integer, List<>> ์ ๊ฐ์ ํํ์ ์ปฌ๋ ์ ์ผ๋ก ๋ฐํ๋๋ค.
Map<Integer, List<Product>> collectorMapOfLists = productList.stream() .collect(Collectors.groupingBy(Product::getAmount)); // Product๋ก ์ด๋ฃจ์ด์ง ๋ฆฌ์คํธ๋ฅผ amount(int)๋ก groupingBy ํ ๊ฒฐ๊ณผ // Map์ key๋ intํ, value๋ ๋ฆฌ์คํธ๋ก ๋ฐํ๋๋ค. ๋ฆฌ์คํธ ์์๋ Product ๊ฐ์ฒด๊ฐ ์๋ค. 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'}] }
# Colletors.partitioningBy()
grounpingBy์ ๊ฑฐ์ ๋น์ทํ๋ฐ, ํจ์ ๊ฐ์ด ์๋ ์กฐ๊ฑด (Predicate)์ key๊ฐ์ผ๋ก ์ด์ฉํ์ฌ ๋ฆฌ์คํธ๋ฅผ 2๊ฐ๋ก ๋๋๋ค.
Map<Boolean, List<Product>> mapPartitioned = productList.stream() .collect(Collectors.partitioningBy(el -> el.getAmount() > 15)); { // ๊ฒฐ๊ณผ๋ฌผ false=[ Product{amount=14, name='orange'}, Product{amount=13, name='lemon'}, Product{amount=13, name='sugar'} ], true=[Product{amount=23, name='potato'}, Product{amount=23, name='bread'} ] }
# Collector.of()
์ํ๋ Collector๊ฐ ์๋ค๋ฉด .of()๋ก ๋ง๋ค๊ณ Collector ๊ฐ์ฒด์ ๋ด์ ์ฌ์ฉ ํ ์ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ .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<Product, ?, LinkedList<Product>> toLinkedList = Collector.of(LinkedList::new, //์ด๊น๊ฐ Collector LinkedList::add, //ํจ์ (first, second) -> { // Combiner, ์ฌ๊ธฐ์์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋๋๋ฐ ์ฌ์ฉํ๋ค. first.addAll(second); return first; }); // ์ด๋ ๊ฒ ๋ง๋ค์ด์ง Collector ํจ์๋ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค. LinkedList<Product> linkedListOfPersons = productList.stream() .collect(toLinkedList); // ๋ด๊ฐ ๋ง๋ Collector ํจ์, toLinkedList
# Match (์กฐ๊ฑด ํ์ธ, boolean ๋ฐํ)
Predicate ๋ฅผ ์ด์ฉํ์ฌ ํด๋น ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์์๊ฐ ์๋์ง ํ์ธํ ํ boolean ๊ฐ์ ๋ฆฌํดํ๋ค.
๋ง์ฝ ์กฐ๊ฑด์ ๋ฐ๋ผ ๊ฐ์ ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด ์ค๊ฐ์ฐ์ฐ์ธ filter๋ฅผ ์ด์ฉํ์.
- ํ๋๋ผ๋ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์์๊ฐ ์๋์ง(.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")); // true boolean allMatch = names.stream() .allMatch(name -> name.length() > 3); // true boolean noneMatch = names.stream() .noneMatch(name -> name.endsWith("s")); // true
# forEach
์๋ฐ์ for-Each ๋ฌธ์ด๋ map()๊ณผ ๋น์ทํ๋ค. ๋ค๋ง Stream์ ๋ฐํํ์ง์๊ณ ์ฐ์ฐ์ ์ข ๋ฃํ๋ค.
names.stream().forEach(System.out::println);
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev