✏️[모던 자바 인 액션, 전문가를 위한 자바 8, 9, 10 기법 가이드] 스터디 관련 책 내용을 정리한 글입니다.
📌이 장의 내용
- 변화하는 요구사항에 대응
- 동작 파라미터화
- 익명 클래스
- 람다 표현식 미리 보기
- 실전 예제 : Comparator, Runnable, GUI
동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있습니다. 동작 파라미터화란, 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미합니다. 이 코드 블록은 나중에 프로그램에서 호출됩니다. 즉, 코드 블록의 실행을 나중으로 미룹니다.
동작 파라미터화로 자주 변화하는 요구사항에 유연하게 대응할 수 있습니다.
2.1 변화하는 요구사항에 대응하기
2.1.1 첫 번째 시도 : 녹색 사과 필터링
초기 요구사항은 녹색 사과만을 얻을 수 있는 필터링이 필요했습니다.
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple a : inventory) {
if (a.getColor() == Color.GREEN) {
result.add(a);
}
}
return result;
}
2.2.2 두 번째 시도 : 색을 파라미터화
계속해서 변화하는 요구사항에 유연하게 대응할 수 있게 메서드에 파라미터를 추가할 수 있습니다..
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
List<Apple> result = new ArrayList<>();
for (Apple a : inventory) {
if (a.getColor() == color) {
result.add(a);
}
}
return result;
}
2.1.3 세 번째 시도 : 가능한 모든 속성을 필터링
그 외에도 더 다양한 요소로 필터링한 결과값이 필요한 기능이 추가되어야 할 수 있습니다.
모든 속성을 추가하고 어떤 것을 기준으로 필터링할지 가리키는 플래그를 추가할 수 있습니다.
public static List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple a : inventory) {
if ((flag && a.getColor().equals(color)) || (!flag && a.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
앞으로 요구사항이 바뀌었을 때 유연하게 대응할 수 없는 코드입니다.
예를 들어, 사과의 크기, 모양, 무게, 원산지 등 여러 가지 요소들로 동시에 사과를 필터링하려면 여러 중복된 필터링 메서드를 만들거나 모든 것을 처리하는 하나의 필터링 메서드를 구현해야 합니다.
2.2 동작 파라미터화
요구사항에 유연하게 대응하기 위해 선택 조건에 따라 불리언 값을 반환하는 방식인 프레디케이트가 있습니다.
다음은 선택 조건을 결정하는 인터페이스를 정의하는 코드입니다.
// 참 또는 거짓을 반환하는 함수를 프레디케이트라고 합니다.
public interface ApplePredicate {
boolean test (Apple a);
}
// 다양한 선택 조건을 대표하는 여러 버전의 ApplePredicate를 정의할 수 있습니다.
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple a) {
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple a) {
return apple.getColor() == Color.GREEN;
}
}
위 조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상할 수 있습니다. 이를 전략 디자인 패턴이라고 합니다.
전략 디자인 패턴은 각 알고리즘은 캡슐화하는 알고리즘 패밀리를 정의해 둔 다음 런타임에 알고리즘을 선택하는 기법입니다.
2.2.1 네 번째 시도 : 추상적 조건으로 필터링
다음 코드는 ApplePredicate를 이용해서 전에 구현했던 코드에 비해 더 유연하며 가독성도 좋아지고 사용하기 쉬운 필터링 메서드입니다
public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple a : inventory) {
if (p.test(a)) { // 프레디케이트 객체로 사과 검사 조건을 캡슐화
result.add(a);
}
}
return result;
}
컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점입니다. 한 메서드가 다른 동작을 수행하도록 재활용할 수 있습니다. 따라서 유연한 API를 만들 때 동작 파라미터화가 중요한 역할을 할 수 있습니다.
2.3 복잡한 과정 간소화
filterApples 메서드로 새로운 동작을 전달하려면 번거롭고 시간 낭비인 작업이지만 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음 인스턴스화해야 합니다. 그래서 자바는 클래스 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공합니다. 익명 클래스를 이용하면 코드의 양을 줄일 수 있지만, 모든 것을 해결하는 것은 아닙니다.
2.3.1 익명 클래스
익명 클래스는 말 그대로 이름이 없는 클래스입니다. 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있습니다. 즉, 즉석에서 필요한 구현을 만들어서 사용할 수 있습니다.
2.3.2 다섯 번째 시도 : 익명 클래스 사용
다음은 익명 클래스를 이용해서 ApplePredicate를 구현하는 객체를 만드는 방법으로 필터링 예제를 다시 구현했다.
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple a) {
return RED.equals(a.getColor());
}
});
하지만 익명 클래스는 많은 공간을 차지하고 많은 프로그래머들이 익명 클래스의 사용에 익숙하지 않습니다.
여전히 객체를 만들고 명시적으로 새로운 동작을 정의하는 메서드를 구현해야 한다는 점은 변하지 않습니다.
2.3.3 여섯 번째 시도 : 람다 표현식 사용
자바 8의 람다 표현식을 이용해서 다음처럼 간단하게 재구현할 수 있습니다.
List<Apple> result = filterApples(inventory, (Apple a) -> RED.equals(a.getColor));
2.3.4 일곱 번째 시도 : 리스트 형식으로 추상화
filterApples는 Apple과 관련된 동작만 수행했습니다. 하지만 Apple 이외의 다양한 대상이 필터링 메서드가 동작하도록 리스트 형식으로 추상화할 수 있습니다.
public interface predicate<T> {
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
List<Apple> redApples = filter(inventory, (Apple a) -> RED.equals(a.getColor());
List<Apple> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0;);
2.4 실전 예제
지금까지 동작 파라미터화가 변화하는 요구사항에 쉽게 적응하는 유용한 패턴을 확인했습니다. 동작 파라미터화 패턴은 동작을 캡슐화한 다음에 메서드로 전달해서 메서드의 동작을 파라미터 화했습니다.
2.4.1 Comparator로 정렬하기
자바 8의 java.util.Comparator 객체를 이용해서 List에 있는 sort 메서드의 동작을 파라미터 화할 수 있습니다.
// java.util.Comparator
public iterface Comparator<T> {
int compare(T o1, T o2);
}
comparator를 구현해서 sort 메서드의 동작을 다양화할 수 있습니다.
예를 들어, 익명 클래스를 이용해서 무게가 적은 순서로 사과를 정렬할 수 있습니다.
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
// 람다 표현식을 이용하면 다음처럼 구현할 수 있습니다.
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.4.2 Runnable로 코드 블록 실행하기
자바 8까지는 Thread 생성자에 객체만을 전달할 수 있었으므로 보통 결과를 반환하지 않은 void run 메서드를 포함하는 익명 클래스가 Runnable 인터페이스를 구현하도록 하는 것이 일반적인 방법입니다. 자바에서는 Runnable 인터페이스를 이용해서 실행할 코드 블록을 지정할 수 있습니다.
// java.lang.Runnable
public interface Runnable {
void run();
}
// Runnable을 이용해서 다양한 동작을 스레드로 실행할 수 있습니다.
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("Hello World");
}
});
// 자바 8부터 지원하는 람다 표현식을 이용하면 다음처럼 스레드 코드를 구현할 수 있습니다.
Thread t = new Thread(() -> System.out.println("Hello World"));
2.4.3 Callable을 결과로 반환하기
ExecutorService를 이용하면 태스크를 스레드 풀로 보내고 결과를 Future로 저장할 수 있습니다.
Callable 인터페이스를 이용해 결과를 반환하는 태스크를 만듭니다. -> Runnable의 업그레이드 버전
// java.util.concurrent.Callable
public interface Callable<V> {
V call();
}
아래 코드와 같이 실행 서비스에 태스크를 제출해서 위 코드를 활용할 수 있습니다.
태스크를 실행하는 스레드의 이름을 반환합니다.
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> threadName = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
});
// 람다를 이용해서 다음처럼 코드를 간결화할 수 있습니다.
Future<String> threadName = executorService.submit(() -> Thread.currentThread().getName());
'📚 서적 및 학습자료 > 모던 자바 인 액션[2회독]' 카테고리의 다른 글
[모던 자바 인 액션] 3장 동작 람다 표현식(2) (1) | 2023.12.04 |
---|---|
[모던 자바 인 액션] 3장 동작 람다 표현식(1) (1) | 2023.12.03 |
[모던 자바 인 액션] 1장 Java 8, 9, 10, 11 : 무슨 일이 일어나고 있는가? (0) | 2023.11.24 |
[모던 자바 인 액션] 20장 OOP와 FP의 조화 : 자바와 스칼라 비교 (0) | 2023.04.02 |
[모던 자바 인 액션] 19장 함수형 프로그래밍 기법 (0) | 2023.04.02 |