람다식

17644 단어 JavaJava

람다식(Lamda expression)

람다식의 도입으로 인해, 자바는 객체지향언어인 동시에 함수형 언어가 되었다.

람다식이 무엇인지 알아보도록 하자.

람다식이란?

람다식(Lamda expression)은 간단히 말해서 메서드를 하나의 '식(expression)'으로 표현한 것이다.

  • 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 익명함수라고도 한다.

람다식 작성하기

람다식은 '익명함수'답게 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 '->'을 추가한다.

    int max(int a, int b){
        return a > b ? a : b;
    }

위의 코드를 람다식으로 변경하면 아래와 같이 나타낼 수 있다.

    (int a, int b) -> {return a > b ? a : b;}
  • 반환값이 있는 메서드의 경우, return문 대신에 '식'으로 대신할 수 있다.
    (int a, int b) -> a > b ? a : b
    //이 때 문장이 아닌 '식'이므로 끝에 ';'을 붙이지 않는다.
  • 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있으며, 대부분의 경우에 생략 가능하다.
  • 람다식에 반환타입이 없는 이유도 항상 추론이 가능하기 때문이다.
    (a, b) -> a > b ? a : b

함수형 인터페이스

하나의 메서드가 선언된 인터페이스를 정의해서 람다식을 다루는 것은 기존의 자바 규칙들을 어기지 않으면서도 자연스럽다.

  • 그렇기 때문에, 인터페이스를 통해 람다식을 다루기로 결정하였다.
  • 람다식을 다루기 위한 인터페이스를 함수형 인터페이스(functional interface)라고 부르기로 했다.
    @FunctionalInterface
    interface MyFunction{ //함수형 인터페이스 MyFunction을 정의
        public abstract int max(int a, int b)
    }
  • 단, 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다.
  • 그래야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 때문이다.
  • 반면에 static 메서드와 default메서드 개수에는 제약이 없다.

📌 @FunctionalInterface을 붙이면, 컴파일러가 함수형 인터페이스를 올바르게 정의하였는지 확인해주므로, 꼭 붙이도록 하자.

함수형 인터페이스의 타입의 매개변수와 반환타입

함수형 인터페이스가 아래와 같이 정의되어 있을 때

    @FunctionalInterface
    interface MyFunction{ //함수형 인터페이스 MyFunction을 정의
        void myMethod(); //추상 메서드
    }
  • 메서드의 매개변수가 MyFunction타입이라면, 아래와 같이 이 메서드를 호출 할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야한다.
    void eMethod(MyFunction f){ //매개변수의 타입이 함수형 인터페이스인 임의의 메서드 작성
        f.myMethod(); //MyFunction에 정의된 메서드를 호출
    }

    .....

    MyFunction f = () - > System.out.println("람다식 작성");
    aMethod(f); //람다식을 참조하는 참조변수를 매개변수로

    //또는 참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.
     aMethod(() - > System.out.println("람다식 작성"));

이렇게 람다식을 참조변수로 다룰 수 있다는 것은 메서드를 통해 람다식을 주고받을 수 있다는 것을 의미한다.

람다식의 타입과 형변환

함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다.

  • 람다식은 익명객체이며, 익명객체는 타입이 없다.(컴파일러가 임의로 이름을 정하기 때문에 알 수 없다.)
  • 그래서 대입 연산자의 양변 타입을 일치시키기 위해서 아래와 같이 형변환이 필요하다.
    MyFunction f = (MyFunction) (() -> {}); //양변의 타입이 다르기 때문에 형변환이 필요하다.
  • 이러한 형변환은 생략이 가능하며, 람다식은 함수형 인터페이스로만 형변환이 가능하다.
  • 굳이 Object타입으로 형변환을 하고 싶다면 위와 같이 함수형 인터페이스로 형변환한 후 한번 더 Object 타입으로 형변환 해주어야한다.

외부 변수를 참조하는 람다식

람다식 내에서 참조하는 지역변수는 final이 붙지 않았어도 상수로 간주된다.

  • 람다식 내에서 지역변수를 참조하고 있다면 람다식 내에서나 다른 곳 어디에서도 이 변수들의 값을 변경하는 일은 허용되지 않는다.

java.util.function패키지

대부분의 메서드는 타입이 비슷하다. 매개변수가 없거나 한 개 또는 두 개, 반환 값은 없거나 한개이며 제네릭 메서드로 정의하면 매개변수나 반환 타입이 달라도 문제가 되지 않는다.

  • java.util.function패키지에 일반적으로 자주쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의 해놓았다.
  • 매번 새로운 함수형 인터페이스를 정의하지 말고, 가능하면 이 패키지의 인터페이스를 활용하는 것이 좋다.

매개변수와 반환값의 유무에 따라 4개의 함수형 인터페이스가 정의되어 있고, Function의 변형으로 Predicate가 있는데, 반환값이 boolean이라는 것만 제외하면 Function과 동일하다.

  • Predicate는 조건식을 함수로 표현하는데 사용된다.
함수형 인터페이스메서드설명
java.lang.Runnablevoid run()매개변수도 없고, 반환값도 없다.
Supplier<T>T get()매개변수는 없고, 반환값만 있다.
Consumer<T>void accept(T t)Supplier와 반대로 매개변수만 있고, 반환값이 없다.
Function<T,R>R apply(T t)일반적인 함수, 하나의 매개변수를 받아서 결과를 반환한다.
Predicate<T>boolean test(T t)조건식을 표현하는데 사용되며, 매개변수는 하나. 반환타입은 boolean이다.
  • 매개변수가 두 개인 함수형 인터페이스는 이름 앞에 'Bi'가 붙는다.

참고로 컬렉션 프레임워크의 인터페이스에도 다수의 디폴트 메서드가 추가되 었는데, 그 중의 일부는 함수형 인터페이스를 사용한다.

메서드 참조

람다식이 하나의 메서드만 호출하는 경우에는 '메서드 참조(method reference)'라는 방법으로 람다식을 간략히 할 수 있다.

    Function<String, Integer> f = (String s) -> Integer.parseInt(s);

위와 같이 작성된 람다식을 메서드 참조로 나타내면?

    Function<String, Integer> f = Integer::parseInt;
  • 위 메서드 참조에서 람다식의 일부가 생략된 모습을 볼 수 있다.
  • 컴파일러는 생략된 부분을 우변의 parseInt메서드의 선언부로부터, 또는 좌변의 Function인터페이스에 지정된 지네릭스 타입으로부터 쉽게 알아낼 수 있다.

람다식을 메서드 참조로 바꾼 예시

    BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2);

    //메서드 참조
    BinFunction<String, String, Boolean> f = String::equals;

이미 생성된 객체의 메서드를 람다식에서 사용하는 경우에는 클래스 이름 대신 그 객체의 참조변수를 적어줘야한다.

    MyClass obj = new MyClass();
    Function<String, Boolean> f = (x) -> obj.equals(); //람다식
    Function<String, Boolean> f = obj::equals; //메서드 참조

하나의 메서드만 호출하는 람다식은 '클래스이름::메서드이름' 또는 '참조변수::메서드이름'으로 바꿀 수 있다.

생성자의 메서드 참조

생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.

    Supplier<MyClass> s = () -> new MyClass(); //람다식
    Supplier<MyClass> s = MyClass::new; //메서드 참조

그리고 배열을 생성할 때는 아래와 같이 하면 된다.

    Function<Integer, int[]> f = x -> new int[x]; //람다식
    Function<Integer, int[]> f= int[]::new; //메서드 참조

📌 메서드 참조는 람다식을 마치 static변수처럼 다룰 수 있게 해주며, 코드를 간략히 하는데 유용하므로 많이 사용된다.

Reference

자바의 정석 3rd Edition

좋은 웹페이지 즐겨찾기