람다

람다 정의

  • 자바 8부터 도입된 람다는 자바에서 함수형 프로그래밍을 지원하기 위한 핵심 기능이다.

  • 람다는 익명 함수이다. 따라서 이름 없이 함수를 표현한다.

보통 메서드나 함수는 다음과 같이 표현한다.

반환타입 메서드명(매개변수) {
    본문
}

람다는 다음과 같이 간결하게 표현한다.

(매개변수) -> {본문}
//이름이 없는 함수

자바는 독립적인 함수를 지원하지 않으며, 메서드는 반드시 클래스나 인터페이스에 속한다.

👆 용어 - 람다와 람다식

  • 람다 (Lambda) : 익명 함수를 지칭하는 일반적인 용어 (개념)

  • 람다식 (Lambda Expression) : (매개변수) -> {본문} 형태로 람다를 구현하는 구체적인 문법 표현을 지칭한다.

람다는 개념을 의미하고(넓은 의미), 람다식은 자바에서 그 개념을 구현하는 구체적인 문법을 의미한다.

람다는 변수처럼 다룰 수 있다.

Procedure procedure = () -> { // 람다를 변수에 담음
    System.out.println("hello! lambda");
};

procedure.run(); // 변수를 통해 람다를 실행

람다도 익명 클래스처럼 클래스가 만들어지고, 인스턴스가 생성된다.

public class InstanceMain {
    public static void main(String[] args) {

        //익명 클래스
        Procedure procedure1 = new Procedure() {
            @Override
            public void run() {
                System.out.println("hello, lambda!");
            }
        };
        System.out.println("class.class = " + procedure1.getClass());
        System.out.println("class.instance = " + procedure1);

        //람다
        Procedure procedure2 = () -> {
            System.out.println("hello, lambda!");
        };
        System.out.println("lambda.class = " + procedure2.getClass());
        System.out.println("lambda.instance = " + procedure2);
    }
}
===========================실행 결과===========================
class.class = class lambda.lambda1.InstanceMain$1
class.instance = lambda.lambda1.InstanceMain$1@1d81eb93
lambda.class = class lambda.lambda1.InstanceMain$$Lambda/0x0000019e63003c28
lambda.instance = lambda.lambda1.InstanceMain$$Lambda/0x0000019e63003c28@34a245ab

익명 클래스의 경우 $로 구분하며 뒤에 숫자가 붙고, 람다의 경우 $$로 구분하며 뒤에 복잡한 문자가 붙는다.


함수형 인터페이스

  • 함수형 인터페이스는 정확히 하나의 추상 메서드를 가지는 인터페이스를 말한다.

  • 람다는 클래스, 추상 클래스에는 할당할 수 없다. 오직 단일 추상 메서드를 가지는 함수형 인터페이스에만 할당할 수 있다.

/**
 * SAM : 단일 추상 메서드, Single Abstract Method
 */
public class SamMain {
    public static void main(String[] args) {

        SamInterface samInterface = () -> {
            System.out.println("Hello World!");
        };
        samInterface.run();

        /*컴파일 오류*/
/*
        NotSamInterface notSamInterface = () -> {
            System.out.println("Hello World!");
        };
*/
    }

    public interface NotSamInterface {
        void run();
        void go();
    }

    @FunctionalInterface
    public interface SamInterface {
        void run();
    }
}
  • 람다는 하나의 함수이다. 따라서 람다를 인터페이스에 담으려면 하나의 메서드(함수) 선언만 존재해야 한다.

  • 인터페이스는 여러 메서드(함수)를 선언할 수 있는데, 이 중 하나에 할당해야 하는 문제가 발생한다.

  • 자바는 이러한 문제를 해결하기 위해 단 하나의 추상 메서드만을 포함하는 함수형 인터페이스에만 람다를 할당할 수 있도록 제한했다.

👆 @FunctionalInterface

  • 이 애노테이션을 통해 함수형 인터페이스임을 선언해두면 나중에 실수로 추상 메서드를 추가할 때 컴파일 오류가 발생한다.

  • 따라서 함수형 인터페이스임을 보장할 수 있다.

  • 람다를 사용할 함수형 인터페이스라면 @FunctionalInterface 애노테이션을 필수로 추가하는 것을 권장한다.


람다와 시그니처

람다를 함수형 인터페이스에 할당할 때는 메서드의 형태를 정의하는 요소인 메서드 시그니처가 일치해야 한다.

@FunctionalInterface
public interface MyFunction {
    int apply(int a, int b);
}

메서드의 시그니처

  • 이름 : apply

  • 매개변수 : int, int

  • 반환 타입 : int

람다는 익명 함수이므로 시그니처에서 이름은 제외한다. 그 외 매개변수, 반환 타입이 함수형 인터페이스에 선언한 메서드와 맞아야 한다. (매개변수 이름은 상관없다. 타입과 순서만 맞으면 된다.)

MyFunction function = (int a, int b) -> {
    return a + b;
};

또 다른 예시

@FunctionalInterface
public interface Procedure {
    void run();
}
  • 이름 : run

  • 매개변수 : 없음

  • 반환 타입 : 없음

Procedure procedure = () -> {
    System.out.println("Hello, World!");
}

람다 생략

람다는 간결하게 코드를 작성할 수 있는 다양한 문법 생략을 지원한다.

@FunctionalInterface
public interface MyFunction {
    int apply(int a, int b);
}
public class LambdaSimpleV1 {
    public static void main(String[] args) {
        //기본
        MyFunction function1 = (int a, int b) -> {
            return a + b;
        };
        System.out.println(function1.apply(1, 2));

        //단일 표현식의 경우 중괄호와 리턴 생략 가능
        MyFunction function2 = (int a, int b) -> a + b;
        System.out.println(function2.apply(1, 2));

        //단일 표현식이 아닐 경우 중괄호와 리턴 모두 필수
        MyFunction function3 = (int a, int b) -> {
            System.out.println("람다 실행");
            return a + b;
        };
        System.out.println(function3.apply(1, 2));
    }
}

매개변수와 반환 값이 없는 경우

@FunctionalInterface
public interface Procedure {
    void run();
}
public class LambdaSimpleV2 {
    public static void main(String[] args) {
        Procedure procedure1 = () -> {
            System.out.println("Hello, Lambda!");
        };
        procedure1.run();

        //단일 표현식은 중괄호 생략 가능
        Procedure procedure2 = () -> System.out.println("Hello, Lambda!");
    }
}

타입 추론

@FunctionalInterface
public interface MyFunction {
    int apply(int a, int b);
}
  • 이 함수형 인터페이스를 보면 이미 (int a, int b)로 매개변수의 타입이 정의되어 있다.

  • 이 정보를 사용하면 람다에서 타입 정보를 생략할 수 있다.

public class LambdaSimpleV3 {
    public static void main(String[] args) {
        //타입 생략 전 (타입 직접 입력)
        MyFunction function1 = (int a, int b) -> a + b;

        //타입 생략 후 (타입 추론)
        MyFunction function2 = (a, b) -> a + b;

        int result = function2.apply(1, 2);
    }
}
  • 자바 컴파일러는 람다가 사용되는 함수형 인터페이스의 메서드 타입을 기반으로 람다의 매개변수와 반환값의 타입을 추론한다. 따라서 람다는 타입을 생략할 수 있다.

  • 반환 타입은 문법적으로 명시할 수 없다. 대신에 컴파일러가 자동으로 추론한다.

매개변수 괄호 생략

public class LambdaSimpleV4 {
    public static void main(String[] args) {
        MyCall call1 = (int value) -> value * 2;  //기본
        MyCall call2 = (value) -> value * 2;      //타입 추론
        MyCall call3 = value -> value * 2;        //매개변수 1개, () 생략 가능
    }

    interface MyCall {
        int call(int value);
    }
}
  • 매개변수가 정확히 하나이면서 타입을 생략하고 이름만 있는 경우 소괄호(())를 생략할 수 있다.

  • 매개변수가 없는 경우 또는 매개변수가 둘 이상이면 ()가 필수이다.


람다의 전달

람다는 함수형 인터페이스를 통해 변수에 대입하거나 메서드에 전달하거나 반환할 수 있다.

public class LambdaPassMain1 {
    public static void main(String[] args) {
        MyFunction add = (a, b) -> a + b;
        MyFunction sub = (a, b) -> a - b;

        System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
        System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));

        MyFunction cal = add;
        System.out.println("cal.apply(1, 2) = " + cal.apply(1, 2));

        cal = sub;
        System.out.println("cal.apply(1, 2) = " + cal.apply(1, 2));
    }
}
  • 기본형이나 참조형이 변수에 값을 대입할 수 있는 것처럼 함수형 인터페이스로 선언한 변수에 람다 인스턴스의 참조값을 대입할 수 있다.

  • 람다도 인터페이스(함수형 인터페이스)를 사용하므로 람다 인스턴스의 참조값을 변수에 전달할 수 있다.

  • 변수에 참조값을 전달할 수 있으므로 다음과 같은 사용이 가능하다.

    • 매개변수를 통해 메서드(함수)에 람다를 전달할 수 있다. (정확히는 람다 인스턴스의 참조값을 전달)

    • 메서드가 람다를 반환할 수 있다. (정확히는 람다 인스턴스의 참조값을 반환)

람다를 메서드에 전달

public class LambdaPassMain2 {
    public static void main(String[] args) {
        MyFunction add = (a, b) -> a + b;
        MyFunction sub = (a, b) -> a - b;

        //변수를 통해 전달
        calculate(add);
        calculate(sub);

        //람다를 직접 전달
        calculate((a, b) -> a + b);
        calculate((a, b) -> a - b);
    }

    private static void calculate(MyFunction function) {
        int a = 1, b = 2;
        int result = function.apply(a, b);
        System.out.println("result = " + result);
    }
}

메서드가 람다를 반환

public class LambdaPassMain3 {
    public static void main(String[] args) {
        MyFunction add = getOperation("add");
        MyFunction sub = getOperation("sub");
        MyFunction xxx = getOperation("xxx");

        System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
        System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));
        System.out.println("xxx.apply(1, 2) = " + xxx.apply(1, 2));
    }

    private static MyFunction getOperation(String operator) {
        return switch (operator) {
            case "add" -> (a, b) -> a + b;
            case "sub" -> (a, b) -> a - b;
            default -> (a, b) -> 0;
        };
    }
}
  • 람다는 함수형 인터페이스를 구현한 익명 클래스 인스턴스와 같은 개념이다.

  • 람다를 변수에 대입한다는 것은 람다 인스턴스의 참조값을 대입하는 것이고, 람다를 메서드(함수)의 매개변수나 반환값으로 넘긴다는 것 역시 람다 인스턴스의 참조값을 전달, 반환하는 것이다.

  • 람다를 자유롭게 전달하거나 반환할 수 있기 때문에 코드의 간결성과 유연성이 높아진다. 만약 익명 클래스를 작성했다면 매우 번잡했을 것이다.

👆 고차 함수 (Higher-Order Function)

고차 함수는 함수를 값처럼 다루는 함수를 의미하며, 일반적으로 다음 두 가지 중 하나를 만족하면 고차 함수라 한다.

  1. 함수를 인자로 받는 함수(메서드)

  2. 함수를 반환하는 함수(메서드)

자바에서 람다(익명 함수)는 함수형 인터페이스를 통해서만 전달할 수 있다. 즉 자바에서 함수를 주고받는다는 것은 함수형 인터페이스를 구현한 어떤 객체(람다 or 익명 클래스)를 주고받는 것과 같다.

고차 함수함수를 다루는 추상화 수준이 더 높다는 데에서 유래했다. 보통 일반적인 함수는 데이터(값)을 다루는데, 이것을 넘어 함수라는 개념 자체를 값처럼 다룬다는 점에서 추상화의 수준이 한 단계 높아진다고 해서 고차 함수라고 부른다.

Last updated