보통 중복된 코드는 메서드로 빼서 관리한다. 가독성과 재사용성에 좋다. 그렇다면 아래의 경우라면 어떨까?
private Cars createCars() {
try {
List<String> carNames = inputView.readCarNames();
return new Cars(carNames);
} catch (IllegalArgumentException e) {
outputView.printErrorMsg(e.getMessage());
return createCars();
}
}
private int readTryCount() {
try {
return inputView.readTryCount();
} catch (IllegalArgumentException e) {
outputView.printErrorMsg(e.getMessage());
return readTryCount();
}
}
try-cath문, 예외를 처리하는 로직이 중복된다. 해결 방법을 생각해보면
2번이 더 간단하고 1번의 경우, 여러 클래스로 책임을 분리하는게 애매하고 오버엔지니어링 같다는 생각을 했다. 2번으로 구현하며 함수형 인터페이스를 공부해보았다.
Object 객체로 형변환 할 수 없다. 람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.Comparator가 있다. (Comparator는 Java8 이후부터 함수형 인터페이스로 변경되었다.)@FunctionalInterface
public interface Comparator<T> {
public int compare(T obj1, T obj2);
}
Comparator로 람다식을 다루는 예시를 들어보겠다.Comparator<Integer> c = (a, b) -> b.compareTo(a);
java.util.function 패키지에 자주 쓰이는 형식의 메서드가 함수형 인터페이스로 정의되어 있다.
| 함수형 인터페이스 | 메서드 | 설명 |
|---|---|---|
Java.lang.Runnable | void run() | 매개변수 X, 반환값 X |
Supplier<T> | T get() | 매개변수 X, 반환값 1개 |
Consumer<T> | void accept(T t) | 매개변수 1개, 반환값 X |
Function<T, R> | R apply(T t) | 매개변수 1개, 반환값 1개 |
Predicate<T> | boolean test(T t) | 매개변수 1개, 반환값 1개(boolean) |
BiConsumer<T, U> | void accept(T t, U u) | 매개변수 2개, 반환값 X |
BiPredicate<T, U> | boolean test(T t, U u) | 매개변수 2개, 반환값 1개(boolean) |
BiFunction<T, U, R> | R apply(T t, U u) | 매개변수 2개, 반환값 1개 |
UnaryOperator<T> | T apply(T t) | Function의 변형 |
BinaryOperator<T> | T apply(T t, T t) | Function의 변형 |
위 예시에서 각 메서드의 개별 로직은
// 1)
private Cars createCars() {
List<String> carNames = inputView.readCarNames();
return new Cars(carNames);
}
// 2)
private int readTryCount() {
return inputView.readTryCount();
}
이다. 공통 로직을 담은 메서드에 이 부분을 매개변수로 넘기자. 두 메서드 모두 매개변수는 없고 반환값은 1개이므로 Supplier<T>를 매개변수로 사용하면 될 것이다.
private <T> T retryOnException(final Supplier<T> retryOperation) {
try {
return retryOperation.get();
} catch (IllegalArgumentException e) {
outputView.printException(e);
return retryOnException(retryOperation);
}
}
retryOnException는 아래와 같이 사용한다.
retryOnException(this::createCars);
retryOnException(this::readTryCount);
우아한테크코스 Level1을 진행하며 페어가 제안한 '함수형 인터페이스'로 메서드의 중복된 로직을 분리하였다. 제네릭 메서드와 함수형 인터페이스의 기초를 공부하면서 헷갈렸던 개념을 바로 잡고, 프로그램의 비즈니스 코드에 직접 적용하면서 재미를 느꼈다. 알아도 막상 적용할 때 생각나지 않을 때도 있는데, 페어 프로그래밍을 하면서 새로운 접근 방식으로 문제를 해결할 수 있었다.
Java의 정석