도서 - 자바 8 인 액션 1
by choising
자바 8 in action
Chapter 1
1.1.2 스트림 처리
- 스트림
- 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임
- Java 스트림API
- 조립 라인 처럼 어떤 항목을 연속적으로 제공하는 어떤 기능
1.1.3 동작 파라미터화로 메서드에 코드 전달하기
- 동작 파라미터화
- 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록
- 메서드를 다른 메서드의 인수로 넘겨주는 기능
1.1.4 병렬성과 공유 가변데이터
- pure 함수, side-effect-free 함수, stateless 함수
- 다른 코드와 동시에 실행하더라도 안전하게 실행할 수 있는 코드
- 공유된 가변 데이터에 접근 X
1.2.1 메서드와 람다를 일급 시민으로
- 일급 시민
- 프로그램을 실행하는 동안 전달할 수 있는 값
- 람다
- 익명 함수
1.2.2 코드 넘겨주기
- Predicate(프레디케이트)
- 인수로 값을 받아 true나 false로 반환하는 함수
1.2.3 메서드 전달에서 람다로
- 한 번만 사용할 메서드는 따로 정의를 구현할 필요가 없다
- 하지만 람다가 몇 줄 이상 길어진다면(복잡해 진다면) 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 레퍼런스를 활용하는 것이 바람직하다
- 코드의 명확성이 우선
1.3 스트림
- External iteration(외부 반복)
- for-each 루프(for, while)를 이용해서 각 요소를 반복하며 작업 수행
- Internal iteration(내부 반복)
- 라이브러리 내부에서 모든 데이터가 처리
1.3.1 멀티스레딩은 어렵다
- 자바 8은 스트림API로 아래 두 문제를 모두 해결했다
- 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제
- 멀티코어 활용 어려움
- 라이브러리가 데이터 처리의 반복되는 패턴을 제공한다면 좋을 것
- 이를 테면 데이터를 필터링 (ex. 무게에 따른 사과 선택)
- 데이터를 추출 (ex. 각 사과의 무게를 추출)
- 데티러를 그룹화 (ex. 리스트의 숫자를 홀수와 짝수로 그룹화)
- 구글 검색 같은 방식
- Forking step(포킹 단계)
- ex. 두 CPU를 가진 환경에서 리스트를 필터링할 때
- 한 CPU는 앞부분을 처리하고 다른 CPU는 뒷부분을 처리하도록 요청
- 그리고 각각 CPU는 자신이 맡은 절반의 리스트를 처리
- 마지막으로 하나의 CPU가 두 결과를 정리
- Forking step(포킹 단계)
- 컬렉션은 어떻게 데이터를 저장하고 접근할지에 포커스
- 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 포커스
1.4 디폴트 메서드
- 디폴트 메서드란
- 인터페이스가 포함하는 메서드 이나 구현 클래스에서 구현하지 않아도 되는 메서드
- 메서드 바디가 인터페이스에 포함
- 문제
- 하나의 클래스에서 여러 인터페이스를 구현할 수 있는데
- 그럼 여러 인터페이스에 다중 디폴트가 메서드가 존재할 수 있다
- 이는 어느정도 다중 상속이다
- 다중 상속에 따르는 문제가 발생할 수 있음
Chapter 2
동작 파라미터화
- 리스트의 모든 요소에
어떤 동작
을 수행할 수 있음 - 리스트 관련 작업을 끝낸 다음에
어떤 다른 동작
을 수행할 수 있음 - 에러가 발생하면
정해진 어떤 다른 동작
을 수행할 수 있음
2.1.2
- 소포트웨어 공학의 DRY
- Don’t repeat yourself
- 같은 것을 반복하지 말 것
2.2
- 전략 디자인 패턴
- 각 알고리즘(전략)을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법
Chapter 3 (~ 92p)
- 람다 표현식
- 익명클래스와 비슷한 것
- 메서드로 전달할 수 있는 익명 함수를 단순화 한 것
3.1 람다란 무엇인가?
- 람다 특징
- 익명
- 함수
- 메서드처럼 특정 클래스에 종속되지 않는다
- 하지만 메서드처럼 파라미터 리스트, body, return, 가능한 예외리스트를 포함한다
- 전달
- argunmet로 전달하거나 변수로 저장할 수 있다
- 간결성
Comparator<Apple> byWeight = new Comparator<Apple>() {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
};
// use lambda
Comparator<Apple> byWeightUseLambda = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// use intellij
Comparator<Apple> byWeightUseIntellij = Comparator.comparing(Apple::getWeight);
- 람다 구성요소
- 파라미터 리스트
- 메서드의 파라미터(ex. Comparator의 compare 메서드의 a1, a2)
- 화살표
- 화살표(->)는 람다의 파라미터 리스트와 바디를 구분
- 람다의 바디
- return value
- 파라미터 리스트
- 예제들
- F3이나 F4는 functional interface 이고 compile error를 피하기 위해 사용한 것 뿐
- 이 예제에서는 equal(“=”) 이후 뒷부분만 신경쓰면 됨
@Test
public void lambdaExpressions() {
Function<String, Integer> f1 = (String s) -> s.length();
Function<Apple, Boolean> f2 = (Apple a) -> a.getWeight() > 150;
F3 f3 = (x, y) -> {
System.out.println("Result:");
System.out.println(x+y);
};
F4 f4 = () -> 42;
Comparator<Apple> byWeightUseLambda = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
}
3.2 어디에, 어떻게 람다를 사용할까?
List<Apple> greenApples = Apple.filter(inventory, (Apple a) -> "green".equals(a.getColor()));
3.2.1 함수형 인터페이스
- 오직 하나의 추상 메서드만 지정하는 인터페이스
- 그 하나의 메서드는 파라미터화가 가능
- ex. Comparator, Runnable 등
@Test
public void runnableTest() {
// use Lambda
Runnable r1 = () -> System.out.println("Hello World 1");
// use Anonymous Class
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World 2");
}
};
process(r1);
process(r2);
process(()-> System.out.println("Hello World 3"));
}
private static void process(Runnable r) {
r.run();
}
3.2.2 함수 디스크립터
- Signatrue(시그너처)
public double getMyFundsFromBank(String bankName)
- 요런 부분이 메소드 시그너처.
-
이름과 return, argument등 메소드만의 정체성.
- Function descriptor(함수 디스크립터)
람다 표현식의 시그너처
를 서술하는 메서드- 따라서
추상 메서드의 시그너처
와Function descriptor
가 같다면, 함수형 인터페이스를 활용할 수 있겠죠
@FunctionalInterface는 무엇인가
- 앞서 말한 함수형 인터페이스가 아닐 경우 컴파일 에러를 발생시킬 뿐
- ex. 추상메서드가 1개 이상이면 에러발생
- 위 어노테이션은 별게 아니었다
3.3 람다 활용 : 실행 어라운드 패턴
- Execute around pattern (실행 어라운드 패턴)
- 실제 자원을 처리하는 코드를
설정(set up)
과정리(clean up)
두 과정이 둘러싸는 형태 - 예를 들어 자원처리에 사용되는 패턴은
- 자원을 열고
- 처리한 다음에
- 자원을 닫는 순서로 이루어짐
- 실제 자원을 처리하는 코드를
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
public class ExecuteAroundPattern {
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
return p.process(br);
}
}
}
위와 같이 구현하면, 람다를 이용해서 다양한 동작을 processFile에 메서드로 전달할 수 있다
@Test
public void processFileTestSpec() throws IOException {
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((br) -> br.readLine() + br.readLine());
System.out.println(oneLine);
System.out.println(twoLines);
}
3.4 함수형 인터페이스 사용
- 함수형 인터페이스의 추상 메서드는 람다 표현식의
시그너처
를 묘사한다
3.4.1 Predicate
- test라는 추상 메서드
- T -> boolean
3.4.2 Consumer
- accept라는 추상 메서드
- T -> void
@Test
public void functionalInterfaces() {
Predicate<String> nonEmptyStringPredicate = (s) -> !s.isEmpty();
predicateExample(Arrays.asList("a", "b", "c", "", "e"), nonEmptyStringPredicate);
consumerExample(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println("hello" + i));
}
private static void predicateExample(List<String> ss, Predicate<String> p) {
for (String s : ss) {
if(p.test(s)) System.out.println(s);
}
}
private static void consumerExample(List<Integer> ii, Consumer<Integer> c) {
for (Integer i : ii) {
c.accept(i);
}
}
실행결과
a
b
c
e
hello1
hello2
hello3
hello4
hello5
3.4.3 Function
- 제너릭 형식 T를 인수로 받아, 제너릭 형식 R 객체를 반환하는 apply 추상메서드
- T -> R
@Test
public void functionTest() {
System.out.println(map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length()));
}
private static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(f.apply(t));
}
return result;
}
실행결과
[6, 2, 6]
- 제너릭은 참조형만 사용할 수 있다
- 프리미티브 타입(기본형)은 사용 불가
- 오토박싱이 이루어질 때에도 비용이 소모됨
- 래퍼로 감싸며 힙에 저장됨
- 박싱한 값은 기본형을 가져올 때에도 메모리를 탐색하는 과정이 필요
- 오토박싱을 피하면 좋겠지?
- 그래서 함수형 인터페이스에 기본형 특화 인터페이스들도 존재
- ex. Predicate
- IntPredicate, LongPredicate, DoublePredicate
- 대표적으로 위 세개 외에
- Supplier
- () -> T
- 객체 생성 시
- BiFunction<T, U, R>
- (T, U) -> R
- 두 객체 비교 시
- Supplier
3.5 형식 검사, 형식 추론, 제약
- Target type (대상 형식)
- 어떤 콘텍스트에서 기대되는 람다 표현식의 형식
3.5.4 지역 변수 사용
- Capturing lambda(람다 캡쳐링)
- 람다 표현식에서는 자유변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다
- Free variable
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
- 캡쳐링 할 수 있는 자유 변수에는 제약이 있다
- final로 선언되어 있거나
- 실질적으로 final 처럼 사용되어야됨
- 즉 변수가 아니라 상수를 캡쳐링 할 수 있는 느낌
3.6 메서드 레퍼런스
- 메서드 레퍼런스를 만드는 방법
- 정적 메서드 레퍼런스
- Integer.parseInt() ->
Integer::parseInt
- Integer.parseInt() ->
- 다양한 형식의 인스턴스 메서드 레퍼런스
- String s, s.length() ->
String::length
- String s, s.length() ->
- 기존 객체의 인스턴스 메서드 레퍼런스
- T a = new T(), T라는 객체에는 getValue() 메서드가 있다면
a::getValue
가능
- 정적 메서드 레퍼런스
Quiz 3-6 메서드 레퍼런스
- 람다 표현식과 일치하는 메서드 레퍼런스는?
- Function<String, Integer> StringToInteger = (String s) -> Integer.parseInt(s);
Integer::parseInt
- BiPredicate<List
, String> contains = (list, element) -> list.contains(element); List::contains
3.6.2 생성자 레퍼런스
@Test
public void apple() {
Double d = 300.0;
Apple a1 = new Apple(d);
Function<Double, Apple> c2 = Apple::new;
Function<Double, Apple> c3 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110.0);
Apple a3 = c3.apply(110.0);
}
@Test
public void apple2() {
BiFunction<String, Double, Apple> c4 = Apple::new;
Apple a4 = c4.apply("green", 10.0);
BiFunction<String, Double, Apple> c5 = (s, d) -> new Apple(s, d);
Apple a5 = c5.apply("red", 20.0);
}
3.7 람다, 메서드 레퍼런스 활용하기!
- Apple을 무게에 따라 정렬하는 메소드를 만들어보쟈.
- 최종목표는 아래와 같다
inventory.sort(comparing(Apple::getWeigth));
3.7.1 코드 전달
public class AppleComparator implements Comparator<Apple> {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
요롷게 하면
inventory.sort(new AppleComparator());
까지 가능
3.7.2 익명 클래스 사용
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
3.7.3 람다 표현식 사용
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing(a -> a.getWeight()));
- 기본
- 형식 추론 사용
- 정적 메서드 Comparator.comparing 사용
3.7.4 메서드 레퍼런스 사용
import static java.util.Comparator.*;
...
inventory.sort(comparing(Apple::getWeight));
- Apple을 weight별로 비교해서 inventory를 sort하라
3.8 람다 표현식을 조합할 수 있는 유용한 메서드
- 역정렬
inventory.sort(comparing(Apple::getWeight).reversed());
- Comparator 연결
inventory.sort(comparing(Apple::getWeight)
.reversed());
.thenComparing(Apple::getCountry);
thenComparing(java.util.Comparator)은 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달한다.
3.8.2 Predicate 조합
- negate()
- 특정 프레디케이트를 반전
- and() , or()
Predicate<Apple> redAndHeavyAppleOrGreen =
redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));
3.8.3 Function 조합
- andThen()
- 함수 f 먼저 실행한 이후에 함수 g를 실행하게끔 가능
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // (1+1) * 2 = 4
- compose()
- f.compose(g)
- 주어진 함수 g를 실행한 다음에 그 결과를 함수 f의 인수로 제공
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // 1 + (1*2) = 3
Chapter 4 스트림 소개
4.1 스트림이란 무엇인가?
- 스트림을 이용하면
- 선언형(데이터를 처리하는 임시 구현 코드 대신 질의로 표현가능)으로 컬렉션 데이터를 처리할 수 있다
- 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다
- 아래는 자바 7 코드
public void findLowCalories() {
List<Dish> menu =
new ArrayList<>(Arrays.asList(
new Dish("a", 500),
new Dish("b", 300),
new Dish("c", 700),
new Dish("d", 100)));
List<Dish> lowCaloricDishes = new ArrayList<>(); // garbage variable
for (Dish d : menu) {
if(d.getCalorie() < 400) {
lowCaloricDishes.add(d);
}
}
// sortWithAnonymousClass
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
@Override
public int compare(Dish d1, Dish d2) {
return Integer.compare(d1.getCalorie(), d2.getCalorie());
}
});
// getNameFromList
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish d : lowCaloricDishes) {
lowCaloricDishesName.add(d.getName());
}
System.out.println(lowCaloricDishesName);
}
-
주석으로도 명시해 놓았지만
List<Dish> lowCaloricDishes = new ArrayList<>();
부분이 컨테이너 역할만 하는 중간 변수 즉 ‘가비지 변수’ 이다 -
자바 8 스트림을 사용한 코드
public void useStream() {
List<Dish> menu =
new ArrayList<>(Arrays.asList(
new Dish("a", 500),
new Dish("b", 300),
new Dish("c", 700),
new Dish("d", 100)));
List<String> lowCaloricDishesName = menu.parallelStream() // menu.stream()
.filter(d -> d.getCalorie() < 400)
.sorted(Comparator.comparing(Dish::getCalorie))
.map(Dish::getName)
.collect(Collectors.toList());
System.out.println(lowCaloricDishesName);
}
-
stream() 대신 parallelStream() 을 사용하면 이 코드가 멀티코어 아키텍처에서 병렬로 실행가능하다
- 선언형
- 루프와 if 조건문 등의 제어를 사용하여
어떻게 동작을 하라
가 아니라 저칼로리의 요리만 선택하라
같은 동작수행을 지정할 수 있다
- 루프와 if 조건문 등의 제어를 사용하여
- filter -> sorted -> map -> collect
- 고수준 빌딩 블록
-
데이터 처리과정을 병렬화 하면서 스레드와 락을 걱정할 필요가 없다
- 특징 요약
- 선언형 : 간결, 가독성 향상
- 조립가능 : 유연성이 향상
- 병렬화 : 성능 향상
4.2 스트림 시작하기
- 스트림이란
- 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
- 연속된 요소
- 컬렉션과 마찬가지로 연속된 값 집합의 인터페이스
- 컬렉션은 자료구조 이므로 시간과 공간의 복잡성, 관련된 요소 저장 및 접근 연산이 주
- 스트림은 filter, sorted, map처럼 표현 계산식이 주
- 즉, 컬렉션의 주제는
데이터
고 스트림의 주제는계산
이다
- 소스
- 스트림은 컬렉션, 배열, I/O자원 등의 데이터 제공 소스로부터 데이터를 소비한다
-
데이터 처리 연산
-
파이프라이닝
- 내부 반복
List<String> threeHighCaloricDishNames =
menu.stream()
.filter(d -> d.getCalorie() > 300)
.map(Dish::getName)
.limit(3)
.collect(Collectors.toList());
System.out.println(threeHighCaloricDishNames);
- menu.stream() 메소드를 호출하여 menu로부터 스트림을 얻음
데이터 소스
는 menu- 데이터 소스는
연속된 요소
를 스트림에 제공
- filter, map, limit, collect
데이터 처리 연산
-
filter, map, limit은 서로
파이프라인
을 형성할 수 있도록 스트림을 반환한다 - filter
- 람다를 인수로 받아 스트림에서 특정 요소를 제외시킨다
- map
- 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다
- limit
- 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림 크기를 축소한다
- collect
- 스트림을 다른 형식으로 변환한다
4.3 스트림과 컬렉션
- 데이터를
언제
계산하느냐가 가장 큰 차이- 컬렉션
- 컬렉션에 추가되기
전에
계산되어야 한다 - ex) dvd에 저장된 영화
- 컬렉션에 추가되기
- 스트림
- 이론적으로
요청할 때만 요소를 계산
한다 - 스트림에 요소를 추가하거나 요소를 제거할 수 없다
- ex) 인터넷 스트리밍 영화
- 이론적으로
- 컬렉션
4.3.1 딱 한번만 탐색할 수 있다
- 탐색된 스트림의 요소는 소비된다
- 한번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 한다
4.3.2 외부 반복과 내부 반복
- 외부반복
List<String> names = new ArrayList<>();
for (Dish d : menu) {
names.add(d.getName());
}
- 내부반복
List<String> names =
menu.stream()
.map(Dish::getName)
.collect(Collectors.toList());
4.4 스트림 연산
- 스트림의 연산은 크게 두 가지로 분류 가능하다
4.4.1 중간연산
-
연결할 수 있는 스트림 연산
-
단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다
- lazy
List<String> names =
menu.stream()
.filter(d -> {
System.out.println("filtering" + d.getName());
return d.getCalorie() > 300;
})
.map(d -> {
System.out.println("mapping" + d.getName());
return d.getName();
})
.limit(3)
.collect(Collectors.toList());
System.out.println(names);
실행결과
filteringpork
mappingpork
filteringbeef
mappingbeef
filteringchicken
mappingchicken
[pork, beef, chicken]
- filter와 map이 서로 다른 연산이지만 한 과정으로 병합되어 섞여 나오는 것을 확인할 수 있다
- 이걸 loop fusion(루프 퓨전) 이라고 한다고.. ㅎㅎ
4.4.2 최종 연산
-
스트림을 닫는 연산
-
forEach
- 각 소스에 람다를 적용한 다음 void를 반환
4.4.3 스트림 이용하기
-
스트림 이용과정은 다음 세 가지로 요약 가능하다
- 질의를 수행할
데이터 소스
- 스트림 파이프라인을 구성할
중간 연산
연결 -
스트림 파이프라인을 실행하고 결과를 만들
최종 연산
- 스트림 파이프라인의 개념은 builder pattern과 유사
Chapter 5 스트림 활용
5.1 필터링과 슬라이싱
5.1.1 프레디케이트로 필터링
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(Collectors.toList());
5.1.2 고유 요소 필터링
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
- distinct를 안쓰고
.collect(Collectors.toSet()
) 도 가능하다
5.1.3 스트림 축소
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalorie() > 300)
.limit(3)
.collect(Collectors.toList());
- predicate와 일치하는
처음 세 요소
5.1.4 요소 건너뛰기
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalorie() > 300)
.skip(2)
.collect(Collectors.toList());
- predicate와 처음 일치하는 두 요소를 제외
- skip과 limit은 상호보완
5.2 매핑
- 함수를 argument로
- argument로 제공된 함수는 각 요소에 적용, 적용된 결과가 새로운 요소에
매핑
- 기존의 값을 고친다 라는 개념보다는 새로운 버전을 만든다
5.2.1 스트림의 각 요소에 함수 적용하기
- 새로운 요리 이름 리스트 생성
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(Collectors.toList());
- 단어의 글자 수로 된 새로운 리스트 생성
List<String> words = Arrays.asList("Java8", "Lambda", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
- 요리 이름의 글자 수로 된 새로운 리스트 생성
- chaining
List<Integer> dishNames = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(Collectors.toList());
5.2.2 스트림 평면화
- [“Hello”, “World”] 리스트로
- 만들기
List<String> words = Arrays.asList("Hello", "World");
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(Collectors.toList());
- 요론식이 가능하다 근데 map이 반환하는건 Stream<String[]> 이다
- Stream
을 만들고 싶다
- Stream
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
- 이걸 위에 적용하면
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(Collectors.toList());
- 이거인데 이건 Stream<Stream
>이 되어버렸다 - 해결법
List<String> words = Arrays.asList("Hello", "World");
List<String> list = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
- flatMap을 사용하면 된다
- flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다
- 다시말해 flatMap은 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다
5.3 검색과 매칭
5.3.1 프레디케이트가 적어도 한 요소와 일치하는지 확인
if(menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("any");
}
- anyMatch는 boolean을 반환하는 최종연산이다
5.3.2 프레디케이트가 모든 요소와 일치하는지 검사
if(menu.stream().allMatch(Dish::isVegetarian)) {
System.out.println("all");
}
- noneMatch는 allMatch와 정 반대이다
- 주어진 프레디케이트와 일치하는 요소가 없어야 한다
- anyMatch, allMatch, noneMatch는 쇼트서킷 기법을 활용
-
&&, 같은 연산 - and 하다가 하나만 false 여도 결과를 알 수 있듯!
-
5.3.3 요소 검색
- findFirst, findAny는 둘 다 요소를 찾아주는 애
- 병렬 실행에서는 첫번째 요소를 찾기가 어려워 findAny를 사용한다
5.4 리듀싱
- 모든 스트림 요소를 처리해서 값으로 도출하는 과정
- 종이를 작은 조각이 될 때 까지 접는 것과 비슷하다 해서 fold라고도 부른다
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
- 위와 같이 스트림의 모든 요소를 더할 수 있다
- arg1 : 초기값 0
- arg2 : 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator
- 메서드 레퍼런스 사용
int sum2 = numbers.stream().reduce(0, Integer::sum);
- arg1의 초깃값을 없이도 사용이 가능하나
- 그럴 경우 아무 요소도 없는 상황에서 null이기에
- return 이 Optional이다
Optional<Integer> max = numbers.stream().reduce(Integer::max);
생각해야할 문제
내부 연산을 갖는 연산, stateful operation- reduce 같은 연산은 내부 상태를 갖는다
- 같은 연산 결과를 내부에 누적해야 한다
- sorted나 distinct 연산도 마찬가지로 과거 이력을 알고 있어야 한다
- 어떤 걸 정렬하려면 모든 요소가 버퍼에 추가되어야 한다
- 필요한 저장소 크기는 정해져 있지 않다
- 그래서 스트림의 크기가 엄청 크거나 무한이라면 문제가 생길 수 있다
- reduce 같은 연산은 내부 상태를 갖는다
5.6 숫자형 스트림
- reduce로 int 합을 구할 경우 boxing 비용이 소모된다
- 효율적으로 처리할 수 있도록 기본형 특화 스트림을 제공한다
5.6.1 기본형 특화 스트림
int calories = menu.stream()
.mapToInt(Dish::getCalorie)
.sum();
- 기본형 특화 스트림 -> 객체 스트림으로 복원하기
IntStream intStream = menu.stream().mapToInt(Dish::getCalorie);
Stream<Integer> stream = intStream.boxed();
- Optional도 기본현 특화 스트림 버전을 제공한다
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalorie)
.max();
int max = maxCalories.orElse(1);
5.6.2 숫자 범위
- IntStream과 LongStream에서는
range
와rangeClosed
라는 static method를 제공한다- range는 arg1
초과
arg2미만
- rangeClosed는 arg1
이상
arg2이하
- range는 arg1
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);
5.7 스트림 만들기
- 크기가 정해지지 않은 스트림을 무한 스트림이라 한다
- Stream.iterate와 Stream.generate를 제공한다
Subscribe via RSS