์๋ฐ 8์์ ๋ฑ์ฅํ Stream์ Functional Programming ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์ปฌ๋ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
์คํธ๋ฆผ์ '๋ฐ์ดํฐ์ ํ๋ฆ'์ ์ถ์ํํ ๊ฒ์ด๋ค. ์ปฌ๋ ์ (Collection)์ ์์๋ฅผ ํ๋์ฉ ์ฐธ์กฐํด ํจ์๋ฅผ ์ ์ฉํ๊ฑฐ๋, ์ํ๋ ์กฐ๊ฑด์ ๋ง๋ ์์๋ฅผ ์ ํํ๊ฑฐ๋, ์์๋ฅผ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ณํํ๊ฑฐ๋, ์์๋ฅผ ๋ชจ๋ ํฉ์น๋ ๋ฑ์ ์ฐ์ฐ์ ์ํํ ์ ์๋ค.
์๋ฅผ ๋ค์ด words.stream()์ **words**๋ผ๋ ์ปฌ๋ ์
(Collection)์ ์คํธ๋ฆผ์ผ๋ก ๋ณํํ๋ค. ๊ทธ๋์ ์ด ์คํธ๋ฆผ์์ ๋ค์ํ ์ฐ์ฐ์ ์ํํ ์ ์๊ฒ ๋๋ค.
์ด ์ฐ์ฐ๋ค์ ์คํธ๋ฆผ์ ๋ค๋ฅธ ์คํธ๋ฆผ์ผ๋ก ๋ณํํ๋ค. ์ด ์ฐ์ฐ๋ค์ 'lazy'ํ๊ฒ ๋์ํ๋๋ฐ, ์ด๋ ์ค์ ๋ก ์ต์ข
์ฐ์ฐ์ด ํธ์ถ๋๊ธฐ ์ ๊น์ง๋ ์คํ๋์ง ์๋๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค. ๋ํ์ ์ธ ์ค๊ฐ ์ฐ์ฐ์๋ filter(), map(), flatMap(), sorted() ๋ฑ์ด ์๋ค.
filter : ์ฃผ์ด์ง ์กฐ๊ฑด์ ์ถฉ์กฑํ๋ ์์๋ง ์ ํํ๋ ๋ฐ ์ฌ์ฉ
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// ๊ฒฐ๊ณผ: [2, 4]
// filter ๋ฉ์๋๋ ์ง์๋ง ์ ํํ๋ค.
map : ๊ฐ ์์๋ฅผ ๋ค๋ฅธ ์์๋ก ๋ณํํ๋ ๋ฐ ์ฌ์ฉ
List<String> words = Arrays.asList("Java", "Stream", "Example");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
// ๊ฒฐ๊ณผ: [4, 6, 7]
// map ๋ฉ์๋๋ ๊ฐ ๋จ์ด์ ๊ธธ์ด๋ฅผ ๊ณ์ฐํ๋ค.
์คํธ๋ฆผ ํ์ดํ๋ผ์ธ์ ๋ซ๋ ์ญํ ์ ํ๋ค. ์ด ์ฐ์ฐ์ด ํธ์ถ๋๋ฉด ์ค์ ๋ก ๋ฐ์ดํฐ๊ฐ ์ฒ๋ฆฌ๋๋ฉฐ, ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋๊ฑฐ๋, ์ธ๋ถ ๊ฐ์ฒด๊ฐ ์ฐ์ฐ ๊ฒฐ๊ณผ๋ฅผ ์๋นํ๊ฒ ๋๋ค. ์ต์ข
์ฐ์ฐ์ ๊ฒฐ๊ณผ๋ ์ปฌ๋ ์
, ๊ฐ์ฒด, ์ซ์, void ๋ฑ ์ฌ๋ฌ ๊ฐ์ง ํํ๊ฐ ๋ ์ ์๋ค. ๋ํ์ ์ธ ์ต์ข
์ฐ์ฐ์๋ collect(), forEach(), reduce(), sum(), count(), anyMatch(), noneMatch(), findFirst(), findAny() ๋ฑ์ด ์๋ค.
collect : ์คํธ๋ฆผ์ ์์๋ฅผ ๋ค๋ฅธ ํํ๋ก ๋ณํํ๊ณ ๋ชจ์ผ๋ ์ญํ ์ ํ๋ค. ๋ณดํต **Collectors**์ ํฉํ ๋ฆฌ ๋ฉ์๋์ ํจ๊ป ์ฌ์ฉ๋๋ค.
List<String> words = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");
List<String> longWords = words.stream()
.filter(word -> word.length() > 5)
.collect(Collectors.toList());
// ['Banana', 'Cherry', 'Elderberry']
// collect๋ ๊ธธ์ด๊ฐ 5 ์ด์์ธ ๋จ์ด๋ค์ ์๋ก์ด ๋ฆฌ์คํธ์ธ longWords๋ก ๋ชจ์๋ค.
forEach: ์คํธ๋ฆผ์ ๊ฐ ์์์ ์ฃผ์ด์ง ๋์์ ์ํํ๋ค. ๋ณดํต ์ฌ์ด๋ ์ดํํธ(side effect)๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค (์: ์ถ๋ ฅ).
List<String> words = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");
words.stream()
.forEach(System.out::println);
// words์ ๊ฐ ๋จ์ด๋ฅผ ์ฝ์์ ์ถ๋ ฅํ๋ค.
์ ์ฝ๋์์๋ ๋ฉ์๋ ์ฐธ์กฐ๊ฐ ์ฌ์ฉ๋์๋ค. ํน์ ๋ฉ์๋๋ฅผ ์ง์ ์ฐธ์กฐํ์ฌ ํจ์ํ ์ธํฐํ์ด์ค์ ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด๋ค. ๋ฉ์๋ ์ฐธ์กฐ๋ :: ๊ธฐํธ๋ฅผ ์ฌ์ฉํ๋ค.
๋ฐ๋ผ์ words.stream().forEach(System.out::println); ์ฝ๋๋ words ์ปฌ๋ ์
์ ๊ฐ ์์์ ๋ํด System.out.println ๋ฉ์๋๋ฅผ ์ ์ฉํ์ฌ ์ฝ์์ ์ถ๋ ฅํ๋ค๋ ์๋ฏธ์ด๋ค.
reduce: ์คํธ๋ฆผ์ ์์๋ฅผ ์กฐํฉํ์ฌ ๋จ์ผ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ๋ค. **reduce**๋ ์ดํญ ์ฐ์ฐ์(binary operator)๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// 0์ ์ด๊ธฐ๊ฐ์ด๋ค. ์คํธ๋ฆผ์ด ๋น์ด์๋ค๋ฉด ์ด ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋๋ค.
// (a, b) -> a + b๋ ์ดํญ ์ฐ์ฐ์๋ก, ์คํธ๋ฆผ์ ํ์ฌ ์์(b)์ ์ด์ ๊น์ง์
// ์ฐ์ฐ ๊ฒฐ๊ณผ(a)๋ฅผ ์ด์ฉํ์ฌ ์๋ก์ด ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ๋ค.
// reduce๋ ๋ชจ๋ ์ซ์๋ฅผ ๋ํ์ฌ ํฉ๊ณ๋ฅผ ๊ณ์ฐํ๋ค.
<aside> ๐ก ์์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ณผ์ ์ ๊ฑฐ์น๊ฒ ๋๋ค.
์ด๋ฐ ์์ผ๋ก reduce ๋ฉ์๋๋ ์คํธ๋ฆผ์ ๋ชจ๋ ์์๋ฅผ ํ๋์ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก '์ถ์(reduce)'ํ๋ ์ญํ ์ ํ๋ค.
</aside>
sum, count, min, max, average ๋ฑ: ์ด๋ค์ ํน์ํ ํํ์ reduce ์ฐ์ฐ์ผ๋ก, ์คํธ๋ฆผ์ ์์๋ฅผ ์ซ์๋ก ์ฒ๋ฆฌํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream()
.count();
// 5 ์คํธ๋ฆผ์ ๋ฐ์ดํฐ ๊ฐ์๋ฅผ ์ธ์ด ๋ฐํํ๋ค.
OptionalInt min = numbers.stream()
.mapToInt(Integer::intValue)
.min();
// OptionalInt[1]
์์ ์ฝ๋์์ min() ๋ฉ์๋๋ ์คํธ๋ฆผ์ ์์ ์ค ๊ฐ์ฅ ์์ ๊ฐ์ ์ฐพ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ง์ฝ ์คํธ๋ฆผ์ด ๋น์ด์๋ค๋ฉด '๊ฐ์ฅ ์์ ๊ฐ'์ด๋ผ๋ ๊ฐ๋
์ด ์กด์ฌํ์ง ์๊ฒ ๋๋ ๋ฌธ์ ๊ฐ ์๊ธด๋ค. ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด์ min() ๋ฉ์๋๋ **OptionalInt**๋ฅผ ๋ฐํํ๊ฒ ๋๋ค. **OptionalInt**๋ ๊ฐ์ด ์์ ์๋ ์๊ณ , ์์ ์๋ ์์์ ๋ํ๋ธ๋ค.
**OptionalInt**์๋ ๊ฐ์ด ์กด์ฌํ๋์ง ํ์ธํ๋ isPresent(), ๊ฐ์ด ์กด์ฌํ ๊ฒฝ์ฐ ๊ทธ ๊ฐ์ ๊ฐ์ ธ์ค๋ getAsInt(), ๊ฐ์ด ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ์ ๋ฐํํ๋ orElse() ๋ฑ์ ๋ฉ์๋๊ฐ ์๋ค.
OptionalInt min = numbers.stream()
.mapToInt(Integer::intValue)
.min();
if (min.isPresent()) {
System.out.println("Min: " + min.getAsInt());
} else {
System.out.println("No min value");
}
match : ์คํธ๋ฆผ์ ๋ชจ๋ ์์๊ฐ ์ฃผ์ด์ง ์กฐ๊ฑด์ ๋ง์กฑํ๋์ง ๋๋ ์ด๋ค ์์๊ฐ ์กฐ๊ฑด์ ๋ง์กฑํ๋์ง ๊ฒ์ฌํ๋ค. anyMatch, allMatch, noneMatch ๋ฑ์ด ์๋ค.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0);
// allEven ๊ฒฐ๊ณผ: false
// anyEven ๊ฒฐ๊ณผ: true
// allMatch๋ ๋ชจ๋ ์์๊ฐ ์ง์์ธ์ง ๊ฒ์ฌํ๊ณ , anyMatch๋ ์ด๋ค ์์๋ผ๋ ์ง์์ธ์ง ๊ฒ์ฌํ๋ค.
List<String> words = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");
boolean hasShortWord = words.stream()
.anyMatch(word -> word.length() <= 4);
// true
find : ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ฒซ ๋ฒ์งธ ์์๋ฅผ ์ฐพ๋๋ค.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
// ๊ฒฐ๊ณผ: Optional[2]
// findFirst ๋ฉ์๋๋ ์ฒซ ๋ฒ์งธ ์ง์๋ฅผ ์ฐพ๋๋ค.
sorted : ์คํธ๋ฆผ์ ์์๋ฅผ ์ ๋ ฌํ๋ค. findFirst, findAny ๋ฑ์ด ์๋ค.
List<Integer> numbers = Arrays.asList(5, 3, 2, 1, 4);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
// ๊ฒฐ๊ณผ: [1, 2, 3, 4, 5]
List<String> words = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");
Optional<String> firstWord = words.stream()
.filter(word -> word.startsWith("B"))
.findFirst();
// ๊ฒฐ๊ณผ: Optional['Banana']