19. 람다 표현식(Lambda Expression)
1. 키워드
- 람다 표현식(Lambda Expression)과 익명 클래스(Anonymous Class)
- 함수형 인터페이스(Functional Interface)
- 메서드 참조(Method Reference)와 생성자 참조(Constructor Reference)
2. 람다 표현식(Lambda Expression)
- 람다 표현식이란 간단히 말해 메서드를 하나의 식으로 표현한 것이다.
- 위의 예제처럼 메서드를 람다 표현식으로 표현하면, 클래스를 작성하고 객체를 생성하지 않아도 메서드를 사용할 수 있다.
- 그런데 자바에서는 클래스의 선언과 동시에 객체를 생성하므로, 단 하나의 객체만을 생성할 수 있는 클래스를 익명 클래스(Anonymous Class)라고 한다.
- 따라서 자바에서 람다 표현식은 익명 클래스와 같다고 할 수 있다.
- 이러한 람다 표현식은 메서드의 매개변수로 전달될 수도 있으며, 메서드의 결괏값으로 반환될 수도 있다.
- 따라서 람다 표현식을 사용하면, 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높여준다.
- Java SE 8부터는 이러한 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍을 할 수 있게 되었다.
1) 람다 표현식 작성
- 자바에서는
->
(화살표) 기호를 사용하여 람다 표현식을 작성할 수 있다.
- 자바에서 람다 표현식을 작성할 때 유의해야 할 사항은 다음과 같다.
1] 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있다.
2] 매개변수가 하나인 경우에는 ()
(괄호)를 생략할 수 있다.
3] 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 {}
(중괄호)를 생략할 수 있고, 이때 ;
(세미콜론)은 붙이지 않는다.
4] 함수의 몸체가 하나의 return
문 만으로 이루어진 경우에는 {}
(중괄호)를 생략할 수 없다.
5] return
문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 되고, 이때 ;
(세미콜론)은 붙이지 않는다.
- 다음 예제는 전통적인 방식의 스레드 생성과 람다 표현식을 사용한 스레드 생성을 비교하는 예제이다.
new Thread(
new Runnable() {
public void run() {
System.out.println("전통적인 방식의 일회용 스레드 생성");
}
}
)
.start();
new Thread(
() -> {
System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
}
)
.start();
// 전통적인 방식의 일회용 스레드 생성
// 람다 표현식을 사용한 일회용 스레드 생성
- 위의 예제에서 볼 수 있듯이 람다 표현식을 사용하면 불필요한 코드를 줄일 수 있으며, 코드의 가독성이 훨씬 좋아진다.
2) 함수형 인터페이스(Functional Interface)
- 람다 표현식을 사용할 때는 람다 표현식을 저장하기 위한 참조 변수의 타입을 결정해야만 한다.
- 위의 문법처럼 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입을 함수형 인터페이스라고 부른다.
- 함수형 인터페이스는 추상 클래스와는 달리 단 하나의 추상 메서드만을 가져야 한다.
- 또한, 다음과 같은 어노테이션(Annotation)을 사용하여 함수형 인터페이스임을 명시할 수 있다.
- 위와 같은 어노테이션을 인터페이스의 선언 앞에 붙이면, 컴파일러는 해당 인터페이스를 함수형 인터페이스로 인식한다.
- 자바 컴파일러는 이렇게 명시된 함수형 인터페이스에 두 개 이상의 메서드가 선언되면 오류를 발생시킨다.
- 다음 예제는 함수형 인터페이스를 선언하고 사용하는 예제이다.
@FunctionalInterface
interface Calc { // 함수형 인터페이스의 선언
public int min(int x, int y);
}
class Test {
public static void main(String[] args) {
Calc minNum = (x, y) -> x < y ? x : y; // 추상 메서드 구현
System.out.println(minNum.min(3, 4)); // 함수형 인터페이스 사용
}
}
// 3
- 자바는
java.util.function
패키지를 통해 여러 상황에서 사용할 수 있는 다양한 함수형 인터페이스를 미리 정의하여 제공한다.
3. 메서드 참조(Method Reference)
- 메서드 참조는 람다 표현식이 단 하나의 메서드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.
- 메서드 참조를 사용하면 불필요한 매개변수를 제거하고 다음과 같이
::
기호를 사용하여 표현할 수 있다.
- 다음 예제는 두 개의 값을 전달받아 제곱 연산을 수행하는
Math
클래스의 클래스 메서드인pow()
메서드를 호출하는 람다 표현식이다.
- 위의 예제는 단순히
Math
클래스의pow()
메서드로 인수를 전달하는 역할만 하므로, 메서드 참조를 사용하여 다음과 같이 간단히 표현할 수 있다.
- 또한, 특정 인스턴스의 메서드를 참조할 때에도 참조 변수의 이름을 통해 메서드 참조를 사용할 수 있다.
MyClass obj = new MyClass;
Function<String, Boolean> func = a -> obj.equals(a); // 람다 표현식
Function<String, Boolean> func = obj::equals(a); // 메서드 참조
- 다음 예제는 람다 표현식과 메서드 참조를 비교하는 예제이다.
DoubleUnaryOperator oper;
oper = n -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));
oper = Math::abs; // 메서드 참조
System.out.println(oper.applyAsDouble(-5));
// 5.0
// 5.0
Note
DoubleUnaryOperator
인터페이스는 한 개의double
형 매개변수를 전달받아 한 개의double
형 값을 반환하는java.util.function
패키지에서 제공하는 함수형 인터페이스이다.
1) 생성자 참조(Constructor Reference)
- 생성자를 호출하는 람다 표현식도 앞서 살펴본 메서드 참조를 이용할 수 있다.
- 즉, 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변환할 수 있다.
- 다음 예제는 단순히 객체를 생성하고 반환하는 람다 표현식이다.
- 위의 예제는 단순히
Object
클래스의 인스턴스를 생성하고 반환하기만 하므로, 생성자 참조를 사용하여 다음과 같이 간단히 표현할 수 있다.
- 이때 해당 생성자가 존재하지 않으면 컴파일 시 오류가 발생한다.
- 또한, 배열을 생성할 때에도 다음과 같이 생성자 참조를 사용할 수 있다.
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new; // 생성자 참조