Lambda Expression & Functional Interface & Method Reference

May 17, 2020

람다식이란?

자바8에서부터 함수형 프로그래밍에서 사용하는 람다의 개념을 도입했다. 익명 함수의 작성으로 좀 더 간결한 표현이 가능하게 되었다.

예제

first와 second 두 개의 멤버를 갖는 class Pair를 정의하고 이를 first를 비교하여 오름차순 정렬, first 값이 같다면 second를 비교하여 오름차순으로 정렬하는 프로그램을 작성한다고 가정하자.

기존의 자바에서는 다음과 같이 익명 클래스 작성으로 구현할 수 있다.

public class Test {
    public static void main(String[] args) {
        List<Pair> pairList = new ArrayList<>();

        pairList.add(new Pair(3, 1));
        pairList.add(new Pair(1, 1));
        pairList.add(new Pair(1, 2));

        mySort(pairList);

        for(Pair cur : pairList){
            System.out.println(cur.first + " " + cur.second);
        }
    }

    public static void mySort(List<Pair> pairList){
        Collections.sort(pairList, new Comparator<Pair>(){
            @Override
            public int compare(Pair a, Pair b) {
                if(a.first > b.first) return 1;
                else if(a.first < b.first) return -1;
                else if(b.second > b.second) return 1;
                else if(b.second < b.second) return -1;
                else return 0;
            }
        });
    }

}

class Pair{
    public int first;
    public int second;

    public Pair(int first, int second){
        this.first = first;
        this.second = second;
    }
}

인터페이스를 통째로 구현하던 것을 람다식을 사용하면 생략하고 구현 가능하며 다음과 같이 작성할 수 있다.

public static void mySort(List<Pair> pairList){
        Collections.sort(pairList, (Pair a, Pair b) -> {
            if(a.first > b.first) return 1;
            else if(a.first < b.first) return -1;
            else if(b.second > b.second) return 1;
            else if(b.second < b.second) return -1;
            else return 0;
        });
    }

람다식 표현법

간결한 표현을 위해 람다식은 여러 요소를 생략할 수 있다.

// 큰 값 구하기
// 기본적인 람다식 
(int a, int b) -> {return a > b ? a : b;}

// 큰 값 구하기
// 매개변수의 타입을 생략 할 수 있다. 
(a, b) -> {return a > b ? a : b;}

// 큰 값 구하기
// body가 return 하나로 구성되는 경우 이를 생략할 수 있다.
(a, b) -> (a > b ? a : b)

// 제곱 구하기
// 매개변수가 하나일 경우 소괄호를 생략할 수 있다.
a -> a * a

함수형 인터페이스

람다식은 작성하여 변수에 저장할 수 있다. 자바는 함수형 프로그램이 아니기 때문에 이는 인터페이스와 어노테이션을 통해 구현된다. 그리고 람다식에서 함수명이 생략되는 특성을 생각해보면, 함수형 인터페이스는 오직 하나의 추상 메소드만을 가져야한다.

@FunctionalInterface
interface LambdaInterface{
    public int max(int a, int b);
}

public class Test {
    public static void main(String[] args) {
        LambdaInterface lambdaInterface = (a, b) -> (a > b ? a : b);
        System.out.println(lambdaInterface.max(5, 10));
    }
}

람다를 지원하면서 모범적인 API를 작성하는 방법에도 변화가 생겼다. 상속하는 클래스의 메서드를 재정의하여 사용하는 템플릿 메서드 패턴을 사용하는 것 대신에 함수형 인터페이스를 사용, 함수 객체를 받아서 적용하는 팩토리나 생성자를 구현하는 것으로 말이다.

API 구성 시 함수형 인터페이스를 직접 구현해도 무방하나 표준 라이브러리에서 많은 기능을 제공하고 있다. java.util.function 패키지에서는 다양한 인터페이스를 제공하고 있으며, 여기서 제공하고 있는 인터페이스라면 이 표준 함수형 인테페이스를 사용하는 것이 좋다.

Interface Function Signature Example
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T,R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println

메소드 참조

메소드 참조는 지정한 메소드의 정보(파라미터, 반환 타입)를 파악하여, 람다식에서 불필요한 매개 변수를 제거하는 역할을 한다.

@FunctionalInterface
interface LambdaInterface{
    public void print(int a);
}

public class Test {
    public static void main(String[] args) {
        // 일반 람다식
        LambdaInterface lambdaInterface = (a) -> System.out.println(a);
        lambdaInterface.print(1);

        // 메소드 참조 형식
        LambdaInterface lambdaInterface2 = System.out::println;
        lambdaInterface.print(1);
    }
}

메소드 참조보다 오히려 람다로 작성하는게 더 깔끔할 때가 있는데, 그러면 그냥 람다로 작성하는게 맞다.

type example labmda expression
static Integer::parseInt str -> Integer.parseInt(str)
bound(instance) Instant.now()::isAfter Instant then = Instant.now();
t -> then.isAfter(t)
unbound(instance) String::toLowerCase str -> str.toLowerCase()
Class Constructor TreeMap<K,V>::new () -> new TreeMap<K,V>
Array Constructor int[]::new len -> new int[len]

참고

  • Joshua Bloch, Effective Java, 프로그래밍인사이트

songmk 🙁