애플리케이션의 요구 조건 변경에 대응하는 절차를 살펴보면서
왜 함수형 프로그래밍이 필요한가?를 이해하자
인터페이스로 명세서와 실체 구현체를 분리하고 유연한 구조를 확보한 후, 람다 표현식을 이용해서 코드의 중복을 제거하는 방법에 대해서 알아본다.
함수를 구현한 코드를 전달하는 메서드 참조 기능에 대해서 알아본다.
인터페이스로 대응
public interface TravelInfoFilter {
public boolean isMacthed(TravelInfo travelInfo);
}
public List<TravelInfo> searchTravelInfo(TravelInfoFilter searchCondition){
List<TravelInfo> returnValue = new ArrayList<>();
for(TravelInfo travelInfo : travelInfoList){
// 인터페이스의 isMatched 메서드를 호출.
// 실제 구현에 대해서는 캡슐화되어 있음
if(searchCondition.isMacthed(travelInfo)){
returnValue.add(travelInfo);
}
}
return returnValue;
}
searchTravelInfo 메서드의 파라미터로 TravelInfoFilter를 전달
TravelInfoFilter 인터페이스의 isMatched 메서드를 호출하여 주어진
TravelInfo 데이터가 조회 조건에 맞는지 확인하고 맞으면 리턴 객체에 포함시키고 틀리면 계속 진행된다.
isMatched에 내부적으로 어떤 조건을 구현해 놓았는지 알지 못하지만 그 결괏값에 따라 true/false 값을 확인할 수 있으므로 외부에서 들어오는 다양한 조건에 대해 처리가 가능하도록 메서드가 개선되었다.
public static void main(String[] args) {
Travel travel = new Travel();
List<TravelInfo> searchList =
travel.searchTravelInfo(new TravelInfoFilter() {
@Override
public boolean isMacthed(TravelInfo travelInfo) {
if (travelInfo.getCountry().equals("vietnam")) {
return true;
} else {
return false;
}
}
});
람다 표현식으로 코드 함축
List<TravelInfo> searchList =
travel.searchTravelInfo(travelInfo -> {
if (travelInfo.getCountry().equals("vietnam")) {
return true;
} else {
return false;
}
});
List<TravelInfo> searchListByCountry =
travel.searchTravelInfo(TravelInfo travelInfo) -> travelInfo.getCountry().equals("vietnam"));
List<TravelInfo> searchListByCity =
travel.searchTravelInfo(TravelInfo travelInfo) -> travelInfo.getCity().equals("hanoi"));
List<TravelInfo> searchListByCountry =
travel.searchTravelInfo(travelInfo -> trvelInfo.getCountry().equals("vietnam"));
for(TravelInfo travelInfo: searchListByCountry){
System.out.println(travelInfo);
}
List<TravelInfo> searchListByCity =
travel.searchTravelInfo(travelInfo -> travelInfo.getCity().equals("hanoi"));
for(TravelInfo travelInfo: searchListByCity){
System.out.println(travelInfo);
}
자바 7까지 변수 혹은 메서드의 파라미터로 전달할 수 있는 것은 객체나 기본 데이터 뿐이었다. 자바에서만 사용할 수 있는 데이터 종류만 변수로 참조하거나 인수로 전달할 수 있고 그 외의 것은 참조할 수 없었다.
하지만 자바 8에서는 추가적으로 메서드 자체를 참조할 수 있게 되었으며 이를 특별히 메서드 참조(method reference)라 한다. 자바 8에서 추가된 메서드 참조를 이용하면 앞에서 실행한 람다 표현식을 훨씬 깔끔하게 활용할 수 있다.
람다 표현식을 사용하면 익명 클래스의 소스 코드 중복성은 해결할 수 있지만, 소스 코드의 재사용성이라는 측면에서는 활용도가 떨어진다. 이 경우 람다 표현식을 하나의 함수로 선언하고 이 함수를 다른 곳에서 활용하면 재사용성을 높일 수 있다.
메서드 참조 유형
정적 (둘은 같은 역할을 한다.)
Integer::parseInt; // 메서드 참조
str -> Integer.parseIng(str); // 람다
한정적 (인스턴스)
Instant.now()::isAfter;
Instant then = Instant.now();
t -> then.isAfter(t);
비한정적 (인스턴스)
String::toLowerCase;
str -> str.toLowerCase();
클래스 생성자
TreeMap<K,V>::new;
() -> new TreeMap<K,V>();
배열 생성자
int[]::new;
len -> new int[len];
어떤 상황에서는, 람다의 매개변수의 이름 자체가 프로그래머에게 좋은 가이드가 되기도 한다. 이런 람다는 길이는 더 길지만 메서드 참조보다 읽기 쉽고 유지보수도 쉬운코드가 될 수 있다.
따라서, 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용해야 한다.
public static boolean isTailand(TravelInfo travelInfo){
if(travelInfo.getCountry().equals("tailand")){
return true;
}
else{
return false;
}
}
List<TravelInfo> searchInfoTailand = searchInfoTailand.searchTravelInfo(Travel::isTailand);
for(TravelInfo travelInfo : searchInfoTailand){
System.out.println(travelInfo);
}
실제 구현해야할 부분만을 코드로 작성하고 이를 람다 표현식으로 선언할 지 아니면 메서드를 추가한 후 메서드 참조를 이용해서 정의한 기능을 전달할지 판단하면 된다.
함수의 메서드명은 동일할 필요 없이 개발자 필요에 따라 명명 규칙에 맞춰 선언하면 되지만 입력되는 인수와 리턴 타입은 인터페이스의 public 메서드와 동일해야 한다.(리턴타입: boolean, 인수 : TravelInfo travelInfo)