Modern Java in Action #3

Taeyong.Hwang·2022년 3월 15일
1

Modern Java in Action

목록 보기
3/6
post-thumbnail

람다 표현식

람다 표현식이란, 메서드로 전달할 수 있는 익명 함수를 단순화한 것이다.
이를 통해, 기존 문법에 대한 syntactic sugar를 제공한다.

함수형 인터페이스

람다 표현식은 함수형 인터페이스로 선언된 자리에만 사용할 수 있습니다. 함수형 인터페이스란, 추상 메서드를 하나만 갖는 인터페이스를 의미합니다. 람다 표현식을 함수형 인터페이스 자리에 사용할 수 있는 이유는, 람다식을 함수형 인터페이스를 구현한 클래스의 인스턴스로 취급하기 때문입니다.

Runnable r1 = () -> Sysmtem.out.println("Hello World 1"); // 아래와 동일한 코드. 결국 람다 표현식은 우리에게 syntatic sugar를 제공하는 것
 
Runnable r2 = new Runnable() {   //보면 Runnable 인터페이스로부터 인스턴스를 생성함을 확인할 수 있는데, '익명 클래스'는 인터페이스로부터도 인스턴스를 생성할 수 있게 해준다고 한다.
    public void run() {
        System.out.println("Hello World 2");
    }
}

@FunctionalInterface 애노테이션

해당 애노테이션을 인터페이스에 붙여주면, 대상 인터페이스가 실제로 함수형 인터페이스가 아닌 경우에 컴파일러에서 에러를 발생시킨다고 한다.

@FunctionalInterface
public interface Function<T, R> {
 ...
}

활용: 실행 어라운드 패턴

실제 작업에 대한 코드를 설정, 정리 코드가 둘러싸고 있는 형태의 패턴을 실행 어라운드 패턴이라고 한다. 해당 패턴의 경우 설정, 정리가 반복되기 때문에 작업에 대한 코드를 파라미터화해서 람다식을 통해 전달해주는 방법을 사용할 수 있다.

// 함수형 인터페이스, 람다식을 통한 구현
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}
 
public String processFile(BufferedReaderProcessor p) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { // 해당 라인을 이해하기 위해서 Try With Resources (JAVA7 spec)를 찾아보는 것이 좋다. 
        return p.process(br);
    }
}

// 람다식을 processFile 메서드의 매개변수로 넘겨줌으로써 반복되는 코드에 대해 재사용성을 늘려줄 수 있다.
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());

메서드 참조

기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있게 해준다고 한다. 실제로 메서드를 호출하는 것은 아니고 람다 표현식에 대한 syntactic sugar를 제공해준다고 생각하면 될 듯하다.

메서드 참조는 그때의 컨텍스트와 일치해야 한다. (자세히 알기 위해서는 '자바 람다의 형식 검사, 추론' 등을 검색해보자)

e.g. Apple::getWeight === (Apple a) -> a.getWeight()

메서드 참조를 만드는 방법에는 3가지가 존재한다.
1. 정적메서드 참조 - ClassNeme::staticMethod
2. 인스턴스 메서드 참조 - ClassName::instanceMethod
3. 기존 객체의 인스턴스 메서드 참조 - expr::instanceMethod

생성자에 대한 참조를 만들 수도 있다. - ClassName:new

// 아래 두개는 같다.
Supplier<Apple> c1 = Apple:: new;    //Apple() constructor 참조
Apple a1 = c1.get();
 
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
 
// 아래 두개도 같다
Function<Integer, Apple> c2 = Apple::new;  // Apple(int weight) constructor 참조  -> 같은 시그니처를 갖는 함수형 인터페이스에 맵핑된다.
Apple a2 = c2.apply(110);
 
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);

람다, 메서드 참조 활용하기

아래의 코드는 배운 내용을 바탕으로 고도화 시키는 작업을 보여준다.

//1단계 - 코드 전달
public class AppleComparator implements Comparator<Apple> {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
}
inventory.sort(new AppleComparator());
 
//2단계 - 익명 클래스 사용
inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
});
 
//3단계 - 람다 표현식 사용
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
 
//3단계 - 람다 표현식 + 타입 추론 활용
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
 
//3단계 - 람다 표현식 + 타입 추론 활용 + comparing 메서드 활용
//comparing 정적 메서드는 Comparable 키를 추출해서 Comparator 객체로 만드는 Funtion 함수를 인수로 받는 메서드라고 한다..
 
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight()));
 
//4단계 - 메서드 참조 사용
inventory.sort(comparing(Apple::getWeight));

람다 표현식을 조합할 수 있는 유용한 메서드

기본적으로 제공하는 함수형 인터페이스는 간단한 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있는 유틸리티 메서드(디폴트 메서드)를 제공한다.

디폴트 메서드는 추상 메서드가 아니기 때문에, 함수형 인터페이스의 정의를 벗어나지 않는다고 한다.

간단히 아래의 예제들을 살펴보자.

Comparator 조합

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
 
// 역정렬
inventory.sort(comparing(Apple::getWeight).reversed()); 
                                                        
 
// 여러 개의 Comparator 사용 (메서드 체이닝 활용)
// 비슷한 예시 : controller에 대한 mock test code (찾아서 보여주자.)
inventory.sort(comparing(Apple::getWeight)
    .reversed()
    .thenComparing(Apple::getCountry));    //두 사과의 무게가 같으면 국가별로 정렬

Predicate 조합

Predicate<Apple> notRedApple = redApple.negate(); //결과 반전
 
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150); //빨간색 + 무게 150 초과
 
Predicate<Apple> redAndHeavyAppleOrGreen =
    redApple.and(apple -> apple.getWeight() > 150)
        .or(apple -> GREEN.equals(a.getColor()));   //(빨간색 and 무게 150초과) or 녹색사과 (우선순위 왼 -> 오른)
profile
나를 위해 정리하는 개발 블로그

1개의 댓글

comment-user-thumbnail
2022년 3월 21일

잘 보고 있습니다. 다음 장도 기대되네요!

답글 달기