1장 자바의 주요 변화
- Java1.0 : 스레드, 락, 메모리 모델 지원
- Java5 : 스레드 풀, 병렬 실행 컬렉션(concurrent collection) 등의 도구 도입
- Java7 : 병렬 실행에 도움을 주는 포크/조인 프레임워크 제공
- Java 8 : 스트림 API, 람다, 메서드 참조, 인터페이스의 디폴트 메서드
- Java9 : 리액티브 프로그래밍(병렬 실행 기법, RxJava)
Java 8의 변화
- 스트림 API
- 내부 반복으로 데이터를 처리한다.
- 기존에는 한 번에 한 항목을 처리했지만 자바 8에서는 작업을(데이터베이스 질의처럼) 고수준으로 추상화하여 일련의 스트림으로 만들어 처리할 수 있다.
- 입력 부분을 여러 CPU 코어에 쉽게 할당할 수 있다.
- 스레드를 사용하지 않고 공짜로 병렬성을 얻을 수 있다.
- 멀티쓰레드에서 리스트를 필터링할 때, 포킹 단계(forking step)으로 처리할 수 있다. 포킹 단계란 리스트의 앞부분은 한 CPU가, 리스트의 뒷 부분을 다른 CPU가 처리한 다음, 하나의 CPU가 두 결과를 합치는 방식이다.
- 하지만 지금은 새로운 스트림 API도 컬렉션 API랑 동일하게 순차적으로 데이터 접근한다.
- 메서드에 코드를 전달하는 기법(메서드 참조, 람다)
- 메서드의 파라미터에 메서드를 전달할 수 있다.
- 메서드 참조 : 메서드가 이급값이 아닌 일급값. 더 간결한 코드 구현 가능
- 인터페이스의 디폴트 메서드
- 이미 정의된 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드 지원.
- 기존 인터페이스를 변경하면 구현 클래스도 변경해야 한다. 구현 클래스에서 변경하지 않아도 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드가 고안되었다.
- but 다이아몬드 상속 문제가 발생한다. 인터페이스의 다중 상속으로 인해 디폴트 메서드 충돌했을 때, 구현 클래스에서 오버라이딩을 해야 한다.
- Optional<T> : null 회피하는 기법
2장 동작 파라미터화 코드 전달하기
2.1 변화하는 요구사항에 대응하기
첫 번째 : 녹색 사과 필터링하기
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory) {
if(GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
두 번째 : 색을 파라미터화
빨간 사과도 필터하고 싶다면 어떻게 해야할까? 파라미터로 Color를 받아 처리할 수 있다.
public static List<Apple> filterApples(List<Apple> inventory, Color color) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory) {
if(apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
세 번째 : 모든 속성으로 필터링
색깔말고 무게로도 필터링하고 싶다면 파라미터에 무게도 추가할 수 있다.
public static List<Apple> filterGreenApples(List<Apple> inventory, Color color,int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory) {
if((apple.getColor().equals(color) && flag) || (apple.getWeight() > weight && !flag)) {
result.add(apple);
}
}
return result;
}
2.2 동작 파라미터
위의 코드에서 다양한 속성으로 필터링하기 위해선 계속 파라미터에 추가해야 한다는 문제가 발생한다. 이는 동작 파라미터를 사용해서 간단하게 작성할 수 있다.
우선 Predicate 인터페이스를 구현한다. Predicate 는 boolean을 반환하는 함수를 가지고 있다.
public interface ApplePredicate {
boolean test(Apple apple);
}
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
public class AppleRedAndHeavyPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return RED.equals(apple.getColor() && apple.getWeight() > 150);
}
}
이렇게 동작 파라미터를 사용하면 다양한 동작(전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
네 번째: 추상적 조건으로 필터링
위에서 작성한 Predicate를 사용하여 필터 메소드를 수정해보자.
public static List<Apple> filterGreenApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
한 개의 파라미터(ApplePredicate)에 다양한 동작을 전달할 수 있다. 전달한 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정된다. 이는 filterApples 메서드의 동작을 파라미터화한 것이다.
2.3 복잡한 과정 간소화
다음 코드는 동작 파라미터화를 적용시키고 실행하는 코드이다. filterApples 메서드를 호출할 때 ApplePredicate 구현 클래스를 생성한다.
// 무거운 사과 선택
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight > 150;
}
}
//녹색 사과 선택
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
public class FilteringApples {
public static void main(String... args) {
List<Apple> inventory = Arrays.asList(
new Apple(80, Color.GREEN),
new Apple(155, Color.GREEN),
new Apple(120, Color.RED)
);
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> greenApples = filterApples(inventory, new AppleGreenPredicate());
}
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(p.test(apple)) result.add(apple);
}
return result;
}
}
다섯 번째 : 익명 클래스 사용하기
filterApples 메서드를 호출할 때, 익명 클래스를 사용하면 더 간단하게 작성할 수 있다.
public class FilteringApples {
public static void main(String... args) {
List<Apple> inventory = Arrays.asList(
new Apple(80, Color.GREEN),
new Apple(155, Color.GREEN),
new Apple(120, Color.RED)
);
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate() {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
});
List<Apple> greenApples = filterApples(inventory, new AppleGreenPredicate() {
@Override
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
});
}
}
여섯 번째 : 람다 표현식 사용
익명 클래스는 람다를 사용하면 더 간단히 작성할 수 있다.
public class FilteringApples {
public static void main(String... args) {
List<Apple> inventory = Arrays.asList(
new Apple(80, Color.GREEN),
new Apple(155, Color.GREEN),
new Apple(120, Color.RED)
);
List<Apple> heavyApples = filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);
List<Apple> greenApples = filterApples(inventory, (Apple apple) -> GREEN.equals(apple.getColor()));
}
}
일곱 번째 : 리스트 형식으로 추상화
작성해온 filterApples 메서드가 사과 말고도 바나나, 오렌지, 정수, 문자열 등의 다양한 데이터를 다룰 수 있도록 추상화하자.
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for(T e : list) {
if(p.test(e)) {
result.add(e);
}
}
return result;
}
이제 다양한 타입을 가진 리스트에 filter 메서드를 사용할 수 있다.
List<Apple> redApples = filter(inventory, (Apple a) -> RED.equals(a.getColor()));
List<Integer> smallNumbers = filter(inventory, (Integer n) -> n < 10);
실제 자바 API에서 동작 파라미터화 사용
실제 자바 API에서 동작 파라미터화를 사용하는 경우를 살펴보자.
Comparator로 정렬하기
- java.util.Comparator 인터페이스를 이용해서 sort의 동작을 파라미터화하기
public interface Comparator<T> {
int compare(T o1, T o2);
}
inventory.sort( new Comparator<Apple>() {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
);
//람다로 작성
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
Runnable로 코드 블록 실행하기
Thread tread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}
);
//람다로 작성
Thread tread = new Thread(() -> System.out.println("hello"));
GUI 이벤트 처리하기
- ExecutorService 인터페이스 : 태스크 제출과 실행 과정의 연관성을 끊어준다.
- 태스크를 스레트 풀로 보내고 결과를 Future로 저장한다.
- Callable 인터페이스를 이용해 결과를 반환하는 태스크를 만든다.
//java.util.concurrent.Callable
public interface Callble<V> {
V call();
}
ExecutorService es = ExecutorService.newCachedThreadPool();
Future<String> threadName = es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
);
//람다로 작성
Future<String> threadName = es.submit(() -> Thread.currentThread().getName(););
'스터디' 카테고리의 다른 글
[모던 자바 인 액션] 3장. 람다, 함수형 인터페이스, 메서드 참조 (0) | 2023.06.28 |
---|---|
[객체지향의 사실과 오해] 4. 역할, 책임, 협력 (0) | 2023.06.24 |
[객체지향의 사실과 오해] 3. 타입과 추상화 (0) | 2023.06.16 |
[ 객체지향의 사실과 오해 ] 2. 객체의 구성 (상태, 행동, 식별자) (0) | 2023.06.09 |
[ 객체지향의 사실과 오해 ] 1. 객체란 무엇인가 (0) | 2023.06.02 |
댓글