자바 인 액션 실전 요약

Ch 3. 람다 표현식

 

주니어인 내가 당장 알아야하는 챕터만으로 정리했다.

병렬 처리와 프로그래밍의 역사적 흐름과 같은 내용은 제외했다.

최대한 책의 내용을 그대로 요약하려 노력했고 내 의견은 기울여서 표현했다.

예제 코드는 내 프로젝트에서 따오거나 직접 작성한 코드들이다.

 

Chapter 3

람다 표현식

 

람다란?

메서드로 전달할 수 있는 익명 함수를 단순화한 것

참고로 람다라는 용어는 다음과 같다고 한다.

"람다 표현식"에서의 "람다"는 람다 대수(lambda calculus)에서 유래한 용어입니다. 람다 대수는 함수 정의와 함수 응용을 기술하는 형식적인 언어로, 수학적인 논리와 계산 이론에서 사용됩니다. 이 용어는 프로그래밍 언어에서도 채택되었고, 특히 함수형 프로그래밍 언어에서 람다 표현식(lambda expression)이라는 형태로 나타납니다. - chat GPT

 

람다의 구성

(Apple a1, Apple a2) -> a1.get무게().compareTo(a2.get무게());

파라미터 리스트: (Appple a1, Apple a2)

화살표: ->

람다 바디: a1.get무게().compareTo(a2.get무게())

 

아래 코드는 사과 인스턴스 2개를 받고 무게를 비교하여 첫 번째 사과가 무겁다면 true를, 아니면 false를 반환하고 있다.

Apple a1과 Apple a2를 받고, compareTo의 반환값이 람다의 반환값이 되는 것이다.

반면에 익명 클래스를 이용한다면?

new Comparator<Apple>() {
	public int compare(Apple a1, Apple a2) {
          return a1.get무게().compareTo(a2.get무게());
    }
};

우리의 관심사인 "어떻게 동작하냐?" 외에 무의미한 코드들이 있어 람다 표현식 대비 간결하지 못하다.

 

람다의 두 표현식

expression style: (파라미터 리스트) -> 표현식

block style: (파라미터 리스트) -> { statements; }

 

예를 들면,

() -> "Raoul" 은 expression style로 "Raoul"을 반환한다.

() -> {return "Raoul";} 은 block style로 똑같이 "Raoul"을 반환한다.

반면에, () -> {"Raoul";} 은 잘못된 표현식으로 올바른 구문(statement)가 아니다.

 

여기서 드는 의문.

객체 지향 언어인 자바는 primitive type과 reference type으로만 전달하거나 받을 수 있다.

람다 표현식은 어떤 type일까? 어떻게 받을 수 있을까?

 

람다는 함수형 인터페이스로 받을 수 있다.

함수형 인터페이스란,

인터페이스에 @FunctionalInterface가 추가되어 있거나

단 하나의 추상 메서드를 갖는 인터페이스를 의미한다.

즉, 인터페이스에 반환형과 파라미터에 정의된 그 하나의 메서드로

람다 표현식이 적용되는 것이다. 예제를 통해 확인하자.

 

아래 예제는 간단하게 두 수를 더하는 로직을 구현하였으며,

람다가 어떻게 동작하는 지만 살펴보자.

 

main

public class Prac {

    public static void main(String[] args) {
        int firstNumber = 5;
        int secondNumber = 6;

        System.out.println(Calculator.add(firstNumber, secondNumber, (int a, int b) -> a+b));
    }
}

 

Calculator class

public class Calculator {

    public static int add(int firstNumber, int secondNumber, Adder adder) {
        return adder.add(firstNumber, secondNumber);
    }
}

 

Adder Interface

@FunctionalInterface
public interface Adder {
    int add(int a, int b);
}

 

Adder 인터페이스에 int로 반환하고 두 개의 int를 받는 add라는 추상 메서드와

(int a, int b) -> a+b)가 전달되어 동작하는 모습이다.

 

비교를 위해 익명 클래스로 작성한다면,

public class Prac {

    public static void main(String[] args) {
        int firstNumber = 5;
        int secondNumber = 6;

        System.out.println(Calculator.add(firstNumber, secondNumber, new Adder() {
                public int add(int a, int b) {
                    return a+b;
                }
            })
        );
    }
}

작성을 했다면, 친절하게 IDE에서 람다로 고칠 수 있다고 알려준다. 

 

람다로 표현하면 나중에 다시 봐도, 다른 개발자가 봐도 의미가 분명하게 전달되는 것이 느껴진다.

 

추가로, 함수 디스크립터(function descriptor)라고 해서 람다 표현식의 시그니처가 있다.

함수형 인터페이스, 그러니깐 하나의 추상 메서드를 갖는 인터페이스로 정의된 파라미터가

람다 표현식으로 받을 수 있기 위해서는 같은 파리미터 리스트를 받고 같은 다형성을 가져야 한다.

이 때, 람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라고 부른다.

예를 들면, 위 Adder.add와 람다 표현식은 (int, int) -> int 함수 디스크립터를 가진다.

컴파일러는 함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖는 것으로 람다 표현식의 유효성을 확인한다.

 

자바에서 java.util.function 패키지로 제공하는 함수형 인터페이스를 확인해보자.

 

Predicate

(T) -> boolean

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

 

Consumer

(T) -> void

@FunctinoalInterface
public interface Consumer<T> {
	void accept(T t);
}

 

Function

(T) -> R

@FunctinoalInterface
public interface Function<T, R> {
	R apply(T t);
}

 

Supplier

() -> T

@FunctinoalInterface
public interface Supplier<T> {
	T get();
}