본문 바로가기
공부/JAVA

[모던 자바 인 액션][Java] 3.람다 표현식

by 미네밍 2022. 4. 3.

많은 책과 글들을 봤지만, 사실 람다라는 개념이 조금 어려웠다.

내가 이해할 수 있는 언어로 쓰여진 글들이 없어서, 모던자바 인 액션을 공부하며 내 나름대로의 기록을 해본다.

보통 공부하는 내용들은 깃헙에 올리기는 한다. 딱 일목요연하게 정리만 된 글도 좋지만, 내가 이해한 것들을 의식의 흐름대로 정리해보기 위해서 이 글은 따로 티스토리에 박제한다.

Java8 람다

람다를 한 단어로 말하자면, "익명함수"이다.

익명클래스나 익명함수를 쓰는 이유가 뭘까? 많은 블로그 글에서도 접할 수 있듯, "한번만 사용되고 버려지는", "재사용되지 않는" 코드를 사용할 때 쓰일 수 있다.

Java8 이전의 버전에서는 보통 이렇게 한번 쓰고 버려지는 코드의 경우, 익명 클래스라는 방법을 통해 해결하였다. 

익명 클래스의 경우는, 클래스의 선언과 객체화가 한번에 이루어져 즉석으로 필요한 구현을 만들 수 있다는 장점이 있다.

....
Runnable r = new Runnable() {
    public void run(){
    	int value = 10;
        System.out.println(this.value);
        }
    }
};
r.run();
...

 

익명클래스의 예시로 자주 볼 수 있는 Runnable. 위와같은 코드는 사실 간결하진 않다.

Java8에서부터 함수형 프로그래밍을 도입하며 익명함수인 '람다'를 지원하기 시작했다.

Runnable r = () -> {
    	int value = 10;
        System.out.println(this.value);
        };
r.run();

람다식의 간단한 표현은 위와 같다. 조금 더 간결해졌다.

 

람다식 문법

// (parameters) -> expression
(String a) -> System.out.println(a);

// (parameters) -> { statements; }
(String a) -> { System.out.println(a) };

람다식은 위처럼 표현가능하다. 화살표를 기점으로 왼쪽과 오른쪽이 나뉘어지는데,

왼쪽은 필요한 파라미터를 선언해주면 되고, 화살표 오른쪽 항은 함수의 바디로, 람다의 반환값을 구현해주면 된다.

람다의 문법은 특별히 어려운 것이 없다.

람다를 알기위해서는 먼저 함수형 인터페이스에 대한 이해가 필요하다.

 

함수형 인터페이스

함수형 인터페이스란, 단 하나의 추상메서드만 있는 인터페이스를 의미한다.

위에서 예시로 든 Runnable은 대표적인 함수형 인터페이스의 예로 볼 수 있다.

실제 Runnable 인터페이스에는 위와 같이 run이라는 추상메서드 단 1개만 존재한다.

람다식은 아무상황에서나 쓸 수 있는 것은 아니다.

우리가 Runnable 인터페이스를 람다로 표현할 수 있었던 것 또한 Runnable 인터페이스가 함수형 인터페이스이기 때문이었다.

즉, 람다는 "함수형 인터페이스" 를 파라미터로 받는 메서드에 인자로 넘기거나, 함수형 인터페이스 변수에만 활용할 수 있다.

 

함수형 인터페이스의 종류

  • Supplier<T>
    • 인자를 받지 않고, T타입의 값을 제공하는 함수형 인터페이스
    • () -> T
  • Consumer<T>
    • T타입을 받아 아무런 값도 반환하지 않는 함수형 인터페이스
    • (T) -> void
  • Function<T,R>
    • T타입을 받아 R타입을 반환하는 함수형 인터페이스
    • (T) -> R
  • Predicate<T>
    • T타입을 받아 불리언을 반환하는 함수형 인터페이스
    • (T) -> boolean
  • Runnable
    • 아무런값을 받지 않고 아무런 값을 반환하지 않는 인터페이스

Runnable 인터페이스의 경우, 아무런 인자를 받지 않고, 아무런 값을 반환하지도 않는 함수형 인터페이스이다.

함수형 인터페이스마다 위처럼 정해진 규칙(?) 이 있는데, 이것을 바로 함수의 시그니처, 함수 디스크립터 라고 표현하는 것으로 보인다.

모던 자바인 액션 책을 읽으면서 처음에 조금 이해가 어려웠었다. 계속 읽어보니 다음의 의미로 해석된다.

 

Runnable이라는 함수형 인터페이스의 시그니처는 "인수가 없으며 void 를 반환하는" 것이다.

Runnable 객체나, Runnable을 인수로 받는 메서드에 우리가 람다식을 넘기고 싶을 때, 해당 람다식이 Runnable의 시그니처인 "인수가 없으며, void를 반환하는" 형태여야만 한다는 의미이다.

 

나중에 더 자세하게 공부하게 되겠지만, Stream API가 제공하는 많은 메서드들에 함수형 인터페이스를 인자로 받는 것들이 많다. 이 부분에 람다식을 사용할 때 해당 인자들이 어떤 시그니처를 가지고 있는지를 보고, 람다식도 그에 맞춰 작성해주어야 한다는 것으로 이해했다.

@FunctionalInterface // (3)
interface SumInterface{   // (1)
	public int sum(int a, int b);
}

SumInterface si = (int a, int b)-> a + b;   // (2)
System.out.println(si.sum(3, 2));

위의 코드에서

(1) SumInterface 라는 함수형 인터페이스를 만들었고, 이 인터페이스의 시그니처는 2개의 int 인자를 받아 int 형의 값을 반환하는 형태이다.

(2) SumInterface라는 함수형 인터페이스 객체에 람다식을 선언해주었는데, 2개의 int변수를 받아 a+b 라는 int 값을 반환해주는 형식의 람다식을 구현해주었다.

(3) 어노테이션은 사실 그 자체로는 주석과 같은 의미이기 때문에, 사실 함수형인터페이스에 @FuntionalInterface 라는 어노테이션을 생략하더라도 기능상 오류가 나지는 않는다. 하지만, 해당 어노테이션을 붙이면, 컴파일 시 해당 인터페이스에 추상메서드가 1개인지를 체크해주게 된다. 따라서, 추후 누군가 함수형 인터페이스인지 모르고 추상메서드를 추가해버리는 일을 미연에 방지해줄 수 있을 것이다.

 

위에서 예시로 만든 함수형 인터페이스의 경우는 int 형 인자를 받는 것으로 한정했지만, 실제로 우리가 사용하는 많은 함수형 인터페이스들은 대다수 제네릭 형식으로 되어있을 것이기 때문에 다양한 람다식을 구현할 수 있을 것이다.

람다에서 더 확장하여 책에는 메서드 참조와 관련한 내용도 있는데, 람다에 대해 더 익숙해지면 따로 정리해보려고 한다.

 

참고

모던 자바 인 액션 책

댓글