모던 자바 인 액션 내용 정리

JAVA/고급 자바|2020. 4. 12. 16:46

 

포킹 

자바 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을 통해서 이루어진다.

 

 

 

 

 

책 전체가 조금 재미없게 구성되어 있다. 그래도 정리할 수 있어 좋았다.

댓글()