티스토리 뷰

Java

[Java] 람다식(Lambda Expression)

Jaehee Jeon 2022. 9. 14. 17:18
반응형

선수지식: [Java] - 함수형 인터페이스

 


람다식(Lambda Expression)이란?

 

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

 

 위와 같이 새로 정의해서 사용해야 하는데, 이러한 함수의 사용 빈도까지 적으며 이런 행위를 반복해야 하는 것은 매우 불편한 일이다. 자바를 비롯한 각종 프로그래밍 언어에서는 이러한 불필요한 코드를 줄일 수 있도록 메서드의 이름 없이 함수를 간단히 표현하는 '람다식' 이라는 표현 방식이 사용 가능하다.

 자바에서는 Java8부터 이를 지원했으며, 함수형 언어적 특성을 갖게 되었다. 또한 람다식은 익명 객체로, 하나의 변수처럼 취급이 가능해 매개 변수로 Stream연산에 사용된다.

 

 

람다식 작성법

 

 기존의 메서드에서 아래의 규칙을 통해 람다식으로 변형할 수 있다.

  1. 메서드의 이름과 반환형을 제거, 화살표(->)로 매개변수와 함수 몸체를 잇는다.
  2. 반환값에 대해 예약어 "return" 과 세미콜론(";")을 생략 가능하다.
  3. 매개변수의 타입이 추론 가능(목표하는 타입의 하위 타입으로 컴파일러가 알아서 예측 가능)하면 생략 가능하다. 대부분 생략한다.
  4. 매개변수가 하나이며 타입이 생략되었다면, 매개변수를 감싸는 괄호("()")도 생략 가능하다.
  5. 함수 몸체가 한 문장이라면, 몸체를 감싸는 대괄호("{}")도 생략 가능하며, 세미콜론(";")도 함께 생략한다.

위의 규칙을 통해 앞에서 언급한 "max" 메서드를 람다식으로 변형해보자

 

// 0. 기존의 메서드
int max(int a, int b){
    return (a > b) ? a : b;
}

// 1. '규칙 1' 적용
(int a, int b) -> {return (a > b) ? a : b;}

// 2. '규칙 2' 적용
(int a, int b) -> {(a > b) ? a : b;}

// 3. '규칙 3' 적용
(a, b) -> {(a > b) ? a : b;}

// 4. '규칙 5' 적용
(a, b) -> (a > b) ? a : b

 

다른 예시로 제곱을 위한 "square" 메서드는 아래와 같이 람다식으로 표현 가능하다.

 

// 기존 메서드
int square(int x) {
	return x * x;
}

// 람다식
x -> x * x

 

 

람다식을 어떻게 사용하는가?

 

 자바에서는 함수만 따로 존재할 수 없다. 모든 함수는 어떠한 클래스의 하위에 속해야 한다. 맨 앞에서 람다식은 익명 객체라고 하였는데, 실제로 람다식을 선언하면 아래와 같이 익명 클래스의 객체가 된다.

 

// 이 람다 식이 의미하는 바는 아래와 같다.
(a, b) -> (a > b) ? a : b

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

 

 생성한 객체를 사용하려면, 그 객체의 주소를 아는 참조변수가 필요하며, 위의 경우 참조변수의 타입은 Object 대신 'max'라는 메서드를  가지는 인터페이스 또는 클래스로 지정해야한다. 

 

(max메서드를 가지는 클래스/인터페이스) obj = (a,b) -> (a > b) ? a : b;

// 람다식은 메서드의 이름은 없으나 위에서 생성한 구현 객체가 'max'메서드를 호출 가능하게 한다.
System.out.println(obj.max(3,5)); // output: 5

 

 따라서 아래와 같이 람다식이 구현하는 함수의 원형을 포함하는 함수형 인터페이스가 먼저 존재하고, 그것의 구현 객체를 람다식으로 만들어서 사용한다. (함수형 인터페이스에 대해서는 '함수형 인터페이스' 참고)

 

@FunctionalInterface
interface ExampleInterface() {
    // 인터페이스의 메서드들은 기본적으로 public abstract이므로 생략해도 컴파일러가 알아서 붙임
    // public abstract int max(int a, int b);
    int max(int a, int b);
}

class Main {
    public static void main(String[] args) {
        // 함수형 인터페이스의 익명 객체를 구현
        // 람다식을 쓰지 않은 경우
        ExampleInterface x = new ExampleInterface(){
        	public int max(int a, int b){
            	return (a > b) ? a : b;
            }
        };
        
        // 람다식을 쓰는 경우
        ExampleInterface y = (a, b) -> (a > b) ? a : b;
    }
}

 

 즉, 간단히 정리하면

  1. 람다식을 통해 기존에 존재하는 함수형 인터페이스를 간편하게 오버라이딩 하고,
  2. 인터페이스의 이름으로 참조변수의 타입을 선언, 인터페이스의 함수를 사용한다. (람다식은 이름이 없어 사용이 그냥은 불가하므로)

 

아니 결국 함수형 인터페이스까지 별도로 작성해야 하면 함수를 오버라이딩하기는 쉬워도
그냥 max함수 하나를 맨 처음처럼 만들어 쓰는 것보다 불편한 것 아니냐?!

 라고 생각할 수 있으나, 보통 람다식을 사용하는 곳에서는 이미 목적에 맞는 함수형 인터페이스가 내장되어 있으며, 그것을 일시적으로 재정의해서 사용하기만 하면 된다.

 

 람다식의 사용처는 매우 다양한데 대표적으로

함수형 인터페이스를 재정의한 Comparator를 입력받는 Collections의 sort 메서드(Comparator를 사용한 객체의 정렬),

스트림

등이 있다.

반응형