본문 바로가기

Dev.BackEnd/JAVA

[JAVA8] 1. 동작 파라미터화 전달하기

[JAVA 8] 1. 동작을 파라미터로 전달하기
메소드 내부에서 실행되는 동작이 유동적인 경우가 있다. 이러한 경우 오버로딩을 사용하여 해결할 수 있지만, 전달되는 파라미터가 동일한 경우 그럴 수 없다. 때문에 메소드 내부에서 if 문을 통해 해당하는 동작에 따라 실행되도록 해야 한다. 이것에는 실제 수행되어야 하는 비즈니스 로직과 상관없는 코드들을 생성하고, 실행해야 한다는 문제점이 있다. 더욱이 가독성은 지극히 떨어지며 그 메소드는 더이상 한 가지 일만 수행하지 않게 된다.
코드를 살펴보자.
private List<Apple> filter(List<Apple> list) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if (apple.getColor().equalsIgnoreCase("red")) {
result.add(apple);
}
}
return result;
}
List<Apple> redAppleList = filter(apples);
현재 `Apple` 이라는 객체가 담긴 리스트를 filtering하여 `Apple`의 color가 red인 `Apple`의 리스트를 반환하고 있다. 그러나 color가 red인 것이 아닌 green인 리스트를 반환해야 한다면? 이와 동일한 메소드를 하나 더 만들고 이름을 `filterGreenApple`로 해서 filtering을 진행해야 한다. 기존의 filtering 메소드와 중복되는 코드가 많아진다. 그렇기 때문에 우리는 파라미터로 특정 동작을 전달할 것이다.


우선 인터페이스를 설계하자.
다형성을 이용하여 이 인터페이스를 구현하고 있는 여러 동작들을 받을 수 있다.
public interface ApplePredicate {
boolean test(Apple apple);
}
여기서는 Apple의 속성을 판단하는 메소드인 test로 인터페이스를 설계하였다.

private List<Apple> filter(List<Apple> list, ApplePredicate predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
인자로 ApplePredicate를 넘겨 test에 해당하는 Apple들의 list를 return하도록 메소드를 만들었다.

이제 이 메소드를 사용하여 result를 받아보자.
List<Apple> result = filter(apples, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return apple.getColor().equalsIgnoreCase("red");
}
});
익명 클래스를 통해 동작을 전달하였다. ApplePredicate 인터페이스를 구현하는 클래스를 만들 수도 있지만, 한 번 사용하고 사용하지 않을 클래스를 구현할 필요없다.

만든 메소드들을 제네릭을 사용하여 보다 유연한 메소드들로 변경해보자.
public interface Predicate<T> {
boolean test(T type);
}
private <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> filteringResult = new ArrayList<>();
for (T type : list) {
if (predicate.test(type)) {
filteringResult.add(type);
}
}
return filteringResult;
}
List<Apple> result = filter(apples, new Predicate() {
@Override
public boolean test(Apple apple) {
return apple.getColor().equalsIgnoreCase("red");
}
});
이제 Apple 뿐만 아니라 다른 List에도 적용할 수 있는 interface와 이를 사용한 filter가 만들어졌다.

그러나 익명클래스 구조가 너무 지저분하고 가독성을 떨어뜨린다.
그래서 나온 것이 바로 람다(Lamda)이다.

람다(Lamda)
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.

파라미터 리스트를 넘겨서 람다의 바디를 반환하게 되며 이 둘은 화살표에 의해 구분된다. 파라미터 리스트로는 모든 것이 올 수 있고 void 값이 전달될 수도 있다. void의 경우에는 ( ) 만 작성해주면 된다.
(parameters) -> expression
( ) -> expression

람다의 바디로는 한 줄이 올 수도 있고 { }를 사용하여 블록이 올 수도 있다.
{ } 블록을 사용하면 return value가 존재할 때 return keyword로 명시해줘야 한다.
(parameters) -> expression
or
(parameters) -> { statements; }


함수형 인터페이스
하나의 추상 메서드를 지정하는 인터페이스다. 디폴트 메서드를 많이 포함해도 추상 메서드가 하나 뿐이라면 그 인터페이스는 함수형 인터페이스다. 람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스 취급할 수 있다.

앞서 설계한 메소드들을 함수형 인터페이스와 람다를 사용해 리팩토링해보자.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T type);
}
private <T> List<T> filterAppleList(List<T> list, Predicate<T> predicate) {
return list.stream().filter(t -> predicate.test(t)).collect(toList());
}
List<Apple> result = filterAppleList(apples, (Apple apple) -> apple.getColor().equalsIgnoreCase("red"));
코드가 훨씬 간단해진 것을 확인할 수 있다. 

사실 뒤에 Stream API 부분에서 다루겠지만 Java 8에서 기본적으로 제공하는 filter 메소드를 통해서 쉽게 필터링을 할 수 있다.
List<Apple> greenApples = apples.stream()
.filter(apple -> apple.getColor().equalsIgnoreCase("red"))
.collect(toList());



1. end