포킹
자바 8에서 추가된 스트림 api에서 데이터를 필터링, 추출, 그룹화 등의 기능을 진행할 수 있다. 이러한 동작들을 병렬화 할 수 있어 여러 cpu에서 작업을 분산해서 처리할 수 있다. 이런 작업을 포킹 단계라고 한다.
함수형 인터페이스
- 하나의 추상메서드를 가지고 있는 함수형 인터페이스지만 상속을 받은 인터페이스는 추상메서드를 하나만 가지고 있다고 하여도 함수형 인터페이스가 아니다.
- 디폴트 메소드가 아무리 많아도 추상 메소드가 하나이면 함수형 인터페이스이다.
- @FunctionalInterfeace 애노테이션을 붙이면 함수형 인터페이스가 아닌 경우 컴파일 에러를 발생 시킬 수 있다.
람다에서 지역변수를 final로 제약하는 이유
람다에서 지역변수가 final로 사용되는지 궁금한데 이는 인터페이스 변수는 힙에 저장되고 지역변수는 스택에 저장되는 이유가 대표적이다. 람다에서 지역변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수가 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 그렇기 때문에 람다 내부에서 사용하는 값은 복사본으로써 그 값은 변경되면 안된다는 제약이 존재한다. (그래서 무조건 final로 제약하는 것)
predicate에는 여러 and, or, negete등이 사용이 가능하다.
and와 or로 predicate를 연결 할 수 있고 negete로 반대의 값을 가져올수도 있다.
List<String> dish = Arrays.asList("banana", "pizza", "chicken");
Predicate<String> isBanana = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("banana");
}
};
Predicate<String> isChicken = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("chicken");
}
};
Predicate<String> isNotPizza = new Predicate<String>() {
@Override
public boolean test(String s) {
return !s.equals("pizza");
}
};
// predicate or
System.out.println(dish.stream().filter(isBanana.or(isChicken)).collect(Collectors.joining(", ")));
// predicate and
System.out.println(dish.stream().filter(isBanana.and(isChicken)).collect(Collectors.joining(", ")));
// predicate negate
System.out.println(dish.stream().filter(isBanana.negate()).collect(Collectors.joining(", ")));
Map의 문제 FlatMap으로 해결
- 문제상황
["hello", "world"] 두개의 단어에서 알파벳 중복된 걸 빼고 하나의 알파벳 배열로 합쳐 보자. 구하고자 하는 결과물은 다음과 같다. ["H", "e", "l", "o", "W", "r", "d"]
- Map으로 진행
List<String> str = Arrays.asList("hello", "world");
str.stream()
.map(data -> data.split(""))
.distinct()
.collect(Collectors.toList());
이렇게 하면 각 문자열에서 문자로 쪼개고 거기서 distinct작업을 한뒤 합쳐서 성공적으로 하나의 list안에 중복된 문자가 없을 것 같지만 실제로는 ["hello"], ["world"]가 노출 된다.
왜냐하면 실제로 동작하는은 다음과 같이 진행되기 때문이다.
hello → ["h","e","l","l","o"] (split하면 문자열 배열로 반환)
world → ["w", "o", "r", "l", "d"] (split하면 문자열 배열로 반환)
최종
→ distinct 대상이 string[] 결국 두개가 합쳐져 Stream<String[]>이 된다.
그럼 이를 해결 하기 위해서 map에서 생성된 배열이 dinstinct로 넘어갈 때 각 값을 다른 스트림으로 만든 다음 모든 스트림을 하나의 스트림으로 연결해야 하는데 이를 flatMap으로 해결이 가능하다.
List<String> str = Arrays.asList("hello", "world");
System.out.println(str.stream()
.map(data -> data.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList()));
map으로 반환된 Stream<String[]>에서 각 String[]을 Stream으로 변형한 후 하나의 stream으로 합쳐서 distinct를 진행하면 정상적인 결과가 도출된다.
flatmap은 각 요소를 별도의 스트림으로 만든 후 다시 합쳐준다. 또한 flatMap은 스트림을 받아 다른 스트림으로 변경해주는 역할을 한다.
예를 들어 (1, 2, 3) 배열과 (4, 5) 배열이 있을 때 이 두개를 합쳐서 (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3,5) 이렇게 묶고 싶으면 다음과 같이 flatMap을 사용하여 두개를 합칠 수 있다.
List<Integer> data1 = Arrays.asList(1, 2, 3);
List<Integer> data2 = Arrays.asList(4, 5);
data1.stream()
.flatMap(
data -> data2.stream().map(j -> new int[] {data, j})
)
.collect(Collectors.toList());
만약 map(data → data.2stream().map(j → new int[] {data, })로 사용했으면 결국 최종적으로 Stream<Stream<int[]>>가 만들어지는 아쉬운 결과가 나올 수 있다.
findFirst와 findAny 차이
findFirst와 findAny는 같은 결과를 가져오지만 병렬 스트림에서는 findFirst로 첫 번째 요소를 가져오는게 어렵기 때문에 그런 경우에는 findAny를 사용한다.
parallel stream과 fork/join
parallel을 사용하여 fork/join 병렬을 진행 할 수 있다. 병렬 스트림은 기본적으로 ForkJoinPool을 사용하는데 이는 프로세스 수 즉 Runtime.getRuntime().availableProcessor()가 반환하는 값에 상응하는 스레드를 갖는다. 이 fork/join의 스레드 수는 조절이 가능하다.
Optional Serialize
Optional의 경우에 serialize를 구현하지 않았기 때문에 이는 직렬화 될 수 없다.
옳지 못한 상속 차단
코드 양이 많고 동작이 변경 되기를 원치 않는 클래스의 경우 final로 선언 또는 private 생성자를 지정한다. 이렇게 되어 있는 클래스의 기능을 이용하기 위해서는 델리게이션 즉 멤버 변수로 이용해서 클래스에서 필요한 메서드를 직접 호출하는 메서드를 작성하는 것이 좋다.
다중 상속 상황에서 동일한 이름의 메소드 우선순위
1. 클래스가 무조건 이긴다.
2. 서브 인터페이스가 이긴다. 예를 들어 B 클래스가 A 클래스를 상속받는다면 B 클래스가 무조건 A 클래스를 이긴다.
3. 이래도 불명확한 경우에는 여러 인터페이스를 상속받은 클래스에서 명시적으로 받고자 하는 곳을 지정할 수 있다.
CompletableFuture와 리액티브 프로그래밍 컨셉
값이 있을 때는 onNext, 도중에 에러가 발생했을 때는 onError, 값을 다 소진했거나 에러가 발생해서 더 이상 처리할 데이터가 없을 때 onComplete 등 각각의 콜백이 호출 된다. 이런 이벤트는 보통 순서의 개의치 않는다.
CompletableFuture 클래스의 join는 Future 인터페이스의 get 메소드와 동일한 의미로써 작업이 끝날 때 까지 기다린다. 다만 join은 어떠한 예외도 발생시키지 않는다는 차이점이 존재한다.
publisher는 subscriber가 자신을 구독하는 경우 최초로 onSubscribe를 호출하여 SubScription 객체를 전달한다. 이 Subscription 인터페이스는 publisher에게 이벤트 요청을 알리는 request 메소드가 있고 더 이상 받지 않겠다고 하는 cancel이 존대한다.
publisher는 반드시 Subscription의 request 메소드에 정의된 개수 이하의 요소만 Subscriber에게 전달 할 수 있다. 해당 요청을 받은 publisher는 subscriber에게 onNext를 통해 여러번에 통해 데이터를 전달 할 수 있고 전달된 값에 따라 onComplete, onError를 통해 publisher에게 데이터 전달 성공 유무를 전달 할 수 있다. 이 대화는 publisher가 subscriber에게 전달한 subscription을 통해서 이루어진다.
책 전체가 조금 재미없게 구성되어 있다. 그래도 정리할 수 있어 좋았다.
'JAVA > 고급 자바' 카테고리의 다른 글
Virtual thread pinning issue와 java 24에서 해소 방법 (1) | 2024.12.20 |
---|---|
문자열 연결 시 실행되는 내부 로직 (0) | 2022.10.31 |
Java 메모리 구조 및 GC 알고리즘 정리 (0) | 2019.09.23 |
[공유] 자바 유료화 관련 글 공유 (0) | 2018.10.04 |
Iterator 그리고 Iterable에 대해 정리 (0) | 2018.10.04 |