728x90
반응형

본 글은 모던자바인액션 책을 참고하여 작성한 글입니다.

1. 람다 표현식

(Apple apple) -> RED.equals(apple.getColor())
👉🏻이전글에서 언급한 것처럼
람다표현식은 특정 동작을 파라미터로 전달할 때, 코드의 복잡성을 간소화하기 위해 Java8에서 도입된 기술입니다.

정의

람다 표현식은 메서드로 전달할 수 있는 익명함수를 단순화한 것이라고 볼 수 있습니다.

// 인터페이스 선언
public interface ApplePredicate {
    boolean test (Apple apple);
}

// 전달받을 메서드 선언
public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ( p.test(apple) ) {
            result.add(apple);
        }
    }
    return result;
}

// 람다 표현식을 파라미터로 전달
List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));



특징

  • 익명 : 보통의 메서드와는 달리 이름이 없어서 가독성에 관한 걱정거리를 줄일 수 있습니다.
  • 함수 : 특정 클래스에 종속되지 않는다는 점에서 함수라고 부를 수 있습니다. 하지만 파라미터 및 반환형식은 맞춰줘야 합니다.
  • 전달 : 메서드의 인수로 전달하거나 변수로 저장할 수 있습니다.
  • 간결성 : 자질구레한 코드를 구현할 필요가 없습니다. 가장 핵심이라고 할 수 있는 특징입니다.

람다 문법

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

  • (Apple a1, Apple a2) : 파라미터 리스트
  • -> : 화살표, 파라미터 리스트와 바디를 구분
  • a1.getWeight().compareTo(a2.getWeight()); : 람다 바디, 실제 구현 및 반환이 이루어지는 부분

스타일

  • 표현식 스타일 : (parameters) -> expression
  • 블록 스타일 : (parameters) -> { statements; }

활용

👉🏻이전글과 위 예시코드에서 언급한 것처럼
람다표현식을 함수형 인터페이스와 관련 메서드를 직접 선언한 후, 선언된 메서드에 전달하는 방식으로도 활용할 수 있지만,
Java8의 java.util.function패키지에서 선언된 함수형 인터페이스(ex. Comparator)를 사용할 때에도 활용될 수 있습니다.

그리고 해당 패키지의 함수형 인터페이스는 Java8의 다양한 라이브러리(Collection의 List)에서 사용되고 있는데,
먼저 함수형 인터페이스가 뭔지에 대해서 짚고 넘어가겠습니다.

2. 함수형 인터페이스



정의

함수형 인터페이스는 추상 메서드가 오직 하나인 인터페이스입니다.

public interface Predicate<T> {
    boolean test(T t);
}




함수형 인터페이스에는 아래와 같이 추상 메서드 이외에 디폴트 메서드가 선언될 수 있는데,
디폴트 메서드에 대해서는 (다음글)에서 짚고 넘어가겠습니다.

@FunctionalInterface
public interface Comparator<T> {

    // 추상 메서드 선언
    int compare(T o1, T o2);

    // 추상 메서드2 선언 (equals는 최상위 클래스인 Object의 메서드이므로 추상 메서드 Count에서 제외된다.)
    boolean equals(Object obj);

    // 디폴트 메서드 선언
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

}




함수형 인터페이스@FunctionalInterface 어노테이션을 태깅할 수 있는데,
해당 어노테이션이 태깅된 인터페이스가 컴파일 과정에서 함수형 인터페이스라고 판단되지 않는 경우,
에러를 반환합니다.

Why? 함수형 인터페이스

함수형 인터페이스는 동작 파라미터화 기능을 사용할 때 사용될 수 있습니다.

👉🏻이전글에서 언급했던 것처럼
특정 동작을 메서드의 파라미터로 전달할 때(동작 파라미터화 기능), 익명클래스를 전달하는 방식이 있고 람다표현식을 전달하는 방식이 있습니다.
그 중 람다표현식을 전달하는 방식은 사실 함수형 인터페이스를 활용한다고도 볼 수 있는데, 이에 대해서는 람다 표현식의 컴파일 과정을 보면 알 수 있습니다. 해당 부분은 (다음글)에서 짚고 넘어가겠습니다.

3. 람다의 활용



시그니처와 함수 디스크립터



메서드 시그니처란?

Java에서 메서드 시그니처는 메서드의 이름과 파라미터의 순서 및 타입, 갯수를 의미합니다.
아래 예시처럼 메서드 시그니처를 표현할 수 있습니다.

  • 메서드
public int Multiply(int a, int b) {
    return a * b;
}



  • 시그니처 : Multiply(int a, int b)

람다표현식 시그니처

람다표현식 또한 위와 같은 시그니처를 가질 수 있습니다. 람다표현식의 시그니처는 아래와 같이 표현됩니다.
(Apple, Apple) -> int

위의 시그니처는 두 개의 Apple을 인수로 받아 int를 반환하는 람다표현식을 가리키는 시그니처입니다.
이러한 시그니처 표현을 살펴보는 이유는 람다표현식과 함수형 인터페이스의 시그니처가 일치하는지 판단할 수 있어야하기 때문입니다.

함수 디스크립터란?

함수 디스크립터는 위에서 언급했던 함수형 인터페이스의 시그니처입니다.
앞으로 함수형 인터페이스의 시그니처는 함수 디스크립터로 표현할 것입니다.

예시

  • Ex1)
// 함수형 인터페이스 파라미터를 입력으로 받는 메서드 선언
public void execute(Runnable r) {
    r.run();
}

// 람다표현식 입력
execute(() -> {});




위 함수형 인터페이스 파라미터의 추상메서드 run의 시그니처, 함수 디스크립터는 () -> void이고,
입력한 람다표현식의 시그니처도 () -> void로 일치하므로 유효한 문법입니다.

  • Ex2)
// 함수형 인터페이스를 반환하는 메서드 선언
public Callable<String> fetch() {
    return () -> "Tricky example"; // 람다표현식 반환
}




위 메서드에서 반환되는 함수형 인터페이스 Callable의 추상메서드 call의 함수 디스크립터는 () -> String이고,
return 문에서 반환한 람다표현식의 시그니처도 () -> String으로 일치하므로 유효한 문법입니다.

실행 어라운드 패턴

람다표현식을 실질적으로 활용하는 패턴 중 대표적인 패턴으로 실행 어라운드 패턴이 있습니다.
실행 어라운드 패턴은 실제 처리되는 작업 이외에 해당 작업을 진행하기 위한 준비단계, 작업이 끝난 후의 마무리 단계를 공통 모듈로 사전 구현한 후에
실제 진행되는 작업만 파라미터로 입력받아 실행시키는 패턴입니다.

예를 들면 파일 처리 작업이 있는데, 해당 작업은 파일이라는 자원을 열고 처리한 후에 자원을 닫는 순서로 이루어집니다.
여기서 자원을 열고 닫는 과정은 공통적으로 반복되는 작업이고, 이 작업들로 실제 처리하는 작업을 둘러싸는 형태의 패턴을 구성할 수 있습니다.
그리고 실제 처리하는 작업은 동작 파라미터화 기능을 통해 패턴의 파라미터로 입력하여 처리할 수 있습니다.

  • 실행 어라운드 패턴 예제
// 함수형 인터페이스 선언
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"))) {
        return p.process(br);
    }
}

// 파일 처리 람다함수 입력
String oneLine = processFile((BufferedReader br) -> br.readLine()); // 한 줄 반환
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine()); // 두 줄 반환



이어서

다음 글에서 람다표현식이 함수형 인터페이스에 어떻게 활용되는지 좀 더 자세히 알아보고,
메서드/생성자 참조 기법과 디폴트 메서드에 대해 알아보겠습니다.

반응형

+ Recent posts