포스트

Java8 람다

람다 표현식


람다 표현식(lambda expression)이란?

  • 람다 표현식(lambda expression)이란 간단히 말해 메소드를 하나의 식으로 표현한 것이다.
1
2
3
4
// 메소드
int min(int x, int y) {
    return x < y ? x : y;
}
1
2
// 람다 표현식
(x, y) -> x < y ? x : y;
  • 위의 예제처럼 메소드를 람다 표현식으로 표현하면, 클래스를 작성하고 객체를 생성하지 않아도 메소드를 사용할 수 있다.
  • 그런데 자바에서는 클래스의 선언과 동시에 객체를 생성하므로, 단 하나의 객체만을 생성할 수 있는 클래스익명 클래스라고 한다. 따라서 자바에서 람다 표현식은 익명 클래스와 같다고 할 수 있다.
1
2
// 람다 표현식
(x, y) -> x < y ? x : y;
1
2
3
4
5
6
// 익명 클래스
new Object() {
    int min(int x, int y) {
        return x < y ? x : y;
    }
}
  • 이러한 람다 표현식은 메소드의 매개변수로 전달될 수도 있으며, 메소드의 결괏값으로 반환될 수도 있다.
  • 따라서 람다 표현식을 사용하면, 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높여준다. Java SE 8부터는 이러한 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍을 할 수 있게 되었다.

람다 표현식 작성

자바에서는 화살표(->) 기호를 사용하여 람다 표현식을 작성할 수 있다.

1
2
// 문법
(매개변수목록) -> { 함수몸체 }
  • 자바에서 람다 표현식을 작성할 때 유의해야 할 사항은 다음과 같다.
    1. 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있다.
    2. 매개변수가 하나인 경우에는 괄호(())를 생략할 수 있다.
    3. 함수의 몸체가 하나의 return 문으로만 이루어진 경우에는 중괄호({})를 생략할 수 없다.
    4. return 문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 된다.(이때 세미콜론(;)은 붙이지 않음)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 전통적인 방식의 스레드 생성과 람다 표현식을 사용한 스레드 생성을 비교하는 예제
public class prog {
	public static void main(String[] args){
		new Thread(new Runnable() {
			public void run() {
				System.out.println("전통적인 방식의 일회용 스레드 생성");
			}
		}).start();
		
		new Thread(()->{
			System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
		}).start();
	}
}

이미지

  • 위의 예제에서 볼 수 있듯이 람다 표현식을 사용하면 불필요한 코드를 줄일 수 있으며, 코드의 가독성이 훨씬 좋아진다.

함수형 인터페이스(functional interface)

  • 람다 표현식을 사용할 때는 람다 표현식을 저장하기 위한 참조 변수의 타입을 결정해야만 한다.
1
2
// 문법
참조변수의타입 참조변수의이름 = 람다 표현식
  • 위의 문법처럼 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입함수형 인터페이스라고 부른다.
  • 함수형 인터페이스는 추상 클래스와는 달리 단 하나의 추상 메소드만을 가져야 한다. 또한, 다음과 같은 어노테이션(annotation)을 사용하여 함수형 인터페이스임을 명시할 수 있다.
1
2
// 문법
@FunctionalInterface
  • 위와 같은 어노테이션을 인터페이스의 선언 앞에 붙이면, 컴파일러는 해당 인터페이스를 함수형 인터페이스로 인식한다. 자바 컴파일러는 이렇게 명시된 함수형 인터페이스에 두 개 이상의 메소드가 선언되면 오류를 발생시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
// 함수형 인터페이스를 선언하고 사용하는 예제
@FunctionalInterface
interface Calc { // 함수형 인터페이스의 선언
	public int min(int x, int y);
}

public class prog {
	public static void main(String[] args){
		Calc minNum = (x, y) ->  x < y ? x : y;	// 추상 메소드의 구현
		System.out.println(minNum.min(3, 4)); // 함수형 인터페이스의 사용 
	}
}

이미지

  • 자바는 java.util.function 패키지를 통해 여러 상황에서 사용할 수 있는 다양한 함수형 인터페이스를 미리 정의하여 제공한다. java.util.function 패키지에 대한 더 자세한 사항은 다음 페이지를 참고하면 된다.

    Package java.util.function


메소드 참조


메소드 참조(method reference)

  • 메소드 참조(method reference)는 람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.
  • 메소드 참조를 사용하면 불필요한 매개변수를 제거하고 다음과 같이 :: 기호를 사용하여 표현할 수 있다.
1
2
3
4
5
6
// 문법
클래스이름::메소드이름

또는

참조변수이름::메소드이름
  • 다음 예제는 두 개의 값을 전달받아 제곱 연산을 수행하는 Math 클래스의 클래스 메소드인 pow() 메소드를 호출하는 람다 표현식이다.
1
2
// 예제
(base, exponent) -> Math.pow(base, exponent);
  • 위의 예제는 단순히 Math 클래스의 pow() 메소드로 인수를 전달하는 역할만 하므로, 메소드 참조를 사용하여 다음과 같이 간단히 표현할 수 있다.
1
2
// 예제
Math::pow;
  • 또한, 특정 인스턴스의 메소드를 참조할 때에도 참조 변수의 이름을 통해 메소드 참조를 사용할 수 있다.
1
2
3
4
5
6
// 예제
MyClass obj = new MyClass;

Function<String, Boolean> func = (a) -> obj.equals(a); // 람다 표현식

Function<String, Boolean> func = obj::equals(a); // 메소드 참조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 람다 표현식과 메소드 참조를 비교하는 예제
import java.util.function.*;

public class prog {
	public static void main(String[] args){
		DoubleUnaryOperator oper;
		
		oper = (n) -> Math.abs(n);	// 람다 표현식
		System.out.println(oper.applyAsDouble(-5));
		
		oper = Math::abs;	// 메소드 참조
		System.out.println(oper.applyAsDouble(-5));
	}
}

이미지

  • DoubleUnaryOperator 인터페이스는 한 개의 double 형 매개변수를 전달받아 한 개의 double 형 값을 반환하는 java.util.function 패키지에서 제공하는 함수형 인터페이스이다.
  • applyAsDoubleDoubleUnaryOperator 인터페이스의 메소드이다. 이 인터페이스는 함수형 인터페이스(Functional Interface)로서, 입력으로 double 값을 받아들이고 double 값을 반환한다.
1
2
3
4
5
@FunctionalInterface
public interface DoubleUnaryOperator {
    double applyAsDouble(double operand);
}
// DoubleUnaryOperator 인터페이스를 구현한 클래스의 객체에서 applyAsDouble 메소드를 호출하면, 해당 객체가 나타내는 함수를 입력 값에 적용하여 결과를 반환한다.
  1. prog 클래스의 main 메소드는 두 가지 방법으로 DoubleUnaryOperator를 구현하고 호출한다.
  2. 첫 번째 방법은 람다 표현식을 사용하는 것이다.
    • (n) -> Math.abs(n)는 입력값 n의 절댓값을 반환하는 람다 표현식이다.
    • 이 람다 표현식은 oper 변수에 할당되어 있다.
    • oper.applyAsDouble(-5)oper에 할당된 람다 표현식을 호출하여 5의 절댓값을 반환하고, 결과를 출력한다.
  3. 두 번째 방법은 메소드 참조를 사용하는 것이다.
    • Math::absMath 클래스의 abs 메소드를 참조하는 메소드 참조이다.
    • oper 변수에 Math::abs 메소드 참조를 할당한다.
    • oper.applyAsDouble(-5)oper에 할당된 메소드 참조를 호출하여 5의 절댓값을 반환하고, 결과를 출력한다.

생성자 참조

  • 생성자를 호출하는 람다 표현식도 앞서 살펴본 메소드 참조를 이용할 수 있다. 즉, 단순히 객체를 생성하고 반환하는 람다 표현식생성자 참조로 변환할 수 있다.

다음 예제는 단순히 객체를 생성하고 반환하는 람다 표현식입니다.

1
2
// 예제
(a) -> { return new Object(a); }
  • 위의 예제는 단순히 Object 클래스의 인스턴스를 생성하고 반환하기만 하므로, 생성자 참조를 사용하여 다음과 같이 간단히 표현할 수 있다.
1
2
// 예제
Object::new;
  • 이때 해당 생성자가 존재하지 않으면 컴파일 시 오류가 발생한다. 또한, 배열을 생성할 때에도 다음과 같이 생성자 참조를 사용할 수 있다.
1
2
3
4
// 예제
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식

Function<Integer, double[]> func2 = double[]::new; // 생성자 참조
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.