- 자바 람다 표현식(Lambda Expression)이란? -
Java 8에서 추가된 람다 표현식(Lambda Expression)과 함수형 인터페이스 그리고 메서드 참조에 대해 알아보자.
1. 람다식(Lambda Expression)
- 람다식이란 익명객체를 생성하기 위한 표현식을 말한다.
- 간단히 말해 메서드를 하나의 간결한 식으로 표현한 것이라고 할 수 있다.
- Java 8부터 사용 가능하며 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍이 가능하게 되었다.
- 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높여준다.
1-1. 람다 표현식 작성하기
자바에서는 화살표(->) 기호를 사용하여 람다 표현식을 작성할 수 있다.
▷ 문법
(매개변수목록) -> {함수몸체}
▷ 예제 1 ) 기본 문법 예제
// 기존 방식
반환타입 메서드이름 (매개변수타입 변수){
...
}
// 람다식
(매개변수타입 변수) -> {
...
}
기본 문법에 대해 배워봤고, 그 다음에는 람다식에는 몇가지 특징이 존재하니 알고 넘어가자.
- 메서드이름과 반환타입의 경우에는 생략할 수 있다.
- 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있다. (대부분 생략이 가능하다.)
- 매개변수가 하나인 경우에는 괄호(())를 생략할 수 있다.
- 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 중괄호({})를 생략할 수 있다. (이때 세미콜론은 붙이지 않음)
- 함수의 몸체가 하나의 return 문으로만 이루어진 경우에는 중괄호({})를 생략할 수 없다.
- return문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 된다. (이때 세미콜론은 붙이지 않음)
▷ 예제 2 ) 람다식 작성 예제
// 1. 기존 메서드
int min(int x, int y) {
return x < y ? x : y;
}
// 2. 메서드명과 반환 타입 생략
(int x, int y) -> {
return x < y ? x : y;
}
// 3. return문 대신 표현식 사용 그리고 중괄호의 생략
// 이때는 문장이 아니므로 끝에 세미콜론은 생략한다.
(int x, int y) -> x < y ? x : y
// 4. 매개변수의 타입이 추론이 가능한 경우 생략
(x, y) -> x < y ? x : y
1-2. 함수형 인터페이스 (Functional Interface)
람다식을 다루기 위한 인터페이스로, 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입을 함수형 인터페이스라고 부른다.
▷ 문법
참조변수의타입 참조변수의이름 = 람다 표현식
람다식은 메서드 그 자체로 볼게 아니라 익명 클래스의 객체와 동등하다고 볼 수 있다.
// 람다 표현식
(x, y) -> x < y ? x : y
// 익명 함수
new Object() {
int min(int x, int y) {
return x < y ? x : y;
}
}
익명 객체의 메서드와 람다식의 매개변수, 반환값이 일치하면 익명 객체를 람다식으로 대체할 수 있다.
람다식으로 정의된 익명 객체의 메서드를 호출하려면 참조변수가 필요하다.
이 때, 참조변수의 타입은 클래스 또는 인터페이스가 가능한데, 람다식과 동등한 메서드가 정의되어 있어야한다.
여기서 우리는 함수형 인터페이스를 사용할 것이다.
▷ 예제 1 ) 함수형 인터페이스 사용
public class Main {
// 1. 함수형 인터페이스의 선언
@FunctionalInterface
interface LambdaFunction{
int min(int x, int y);
}
public static void main(String[] args) {
LambdaFunction lambdaFunction = (x, y) -> x < y ? x : y; // 2. 추상 메서드의 구현
System.err.println(lambdaFunction.min(3, 4)); // 3. 함수형 인터페이스의 사용
}
}
함수형 인터페이스는 추상클래스와 달리 단 하나의 추상 메서드만을 가져야한다. (하지만 default와 static 메서드의 개수에는 제약이 없다.)
함수형 인터페이스 라는걸 명시해 주지 않으면 일반 인터페이스로 인식해 추후 코드가 꼬여 에러가 발생할 수 있으나,
이를 방지하기 위해 @FunctionalInterface 를 선언해 주는 것이다.
@FunctionalInterface 어노테이션은 해당 인터페이스가 함수형 인터페이스라는것을 선언해주어 저렇게 명시된 함수형 인터페이스에 두 개 이상의 메서드가 선언되면 자바 컴파일러는 오류를 발생시킨다.
그리고 2.번으로 가게되면 함수형 인터페이스의 추상 메서드를 람다식을 사용해 간결하게 구현하였으며,
3번으로가 해당 함수형 인터페이스를 사용한 것이다.
자바에서는 기본적으로 4가지의 함수형 인터페이스를 지원하고있다.
- Supplier<T> : 매개변수 없이 반환값 만을 갖는 함수형 인터페이스이다. (T get()를 추상 메서드로 가진다.)
- Consumer<T> : 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스이다. (void accept(T t)를 추상메서드로 가진다.)
- Function<T, R> : T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스이다. (R apply(T t)를 추상메서드로 가진다.)
- Predicate<T> : T를 매개변수로 받아 처리한 후 Boolean을 반환한다. (Boolean test(T t)를 추상 메서드로 가진다.)
자세한 내용은 아래 오라클 공홈과 한글로 잘 설명해놓은 블로그를 참조하자.
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
https://mangkyu.tistory.com/113
1-3. 메서드 참조 (Method Reference)
- 메서드 참조는 람다 표현식이 단 하나의 메서드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 도와준다.
- 함수형 인터페이스를 람다식이 아닌 일반 메서드를 참조시켜 선언하는 방법이다.
- 참조가능한 메서드는 일반 메서드, static 메서드, 생성자가 있다.
- 메서드 참조를 사용하면 람다식과 마찬가지로 함수형 인터페이스로 반환된다.
▷ 문법
클래스이름::메소드이름
참조변수이름::메소드이름
▷ 예제 1 ) static 메서드 참조 (Math.min)
public class Main {
@FunctionalInterface
interface LambdaFunction{
int min(int x, int y);
}
public static void main(String[] args) {
// 1. 람다식 사용
LambdaFunction lambdaFunction = (x, y) -> Math.min(x, y);
System.err.println(lambdaFunction.min(3, 4));
// 2.메서드 참조 사용
LambdaFunction lambdaFunction = Math::min;
System.err.println(lambdaFunction.min(3, 8));
}
}
위 예제를 보면 알겠지만 람다식은 단순히 두개의 값을 Math.min() 메서드의 매개값으로 전달하는 역할만 하는것뿐 별다른 의미는 없다.
이럴때 함수형 인터페이스의 매개변수 타입, 개수, 반환형과 사용하려는 메서드의 매개변수 타입, 개수, 반환형이 일치한다면 2번 처럼 메서드 참조를 사용하여 이를 간결하게 표현할 수 있다.
▷ 예제 2 ) 일반 메서드 참조
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!!");
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(System.out::println);
System.out.println() 메서드는 반환값이 없고, 파라미터로 String을 받는 메서드라 Consumer라는 함수형 인터페이스에 참조시킬 수 있다.
그리고 forEach는 함수형 인터페이스인 Consumer를 매개변수로 받기 때문에 System.out 메서드 참조를 사용할 수 있다.
▷ 예제 3 ) 생성자 참조
- 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변환할 수 있다.
Supplier<String> supplier = () -> new String(); // 람다식 표현
Supplier<String> supplier = String::new; // 메서드 참조
Supplier<Object> supplier = () -> new Object(); // 람다식 표현
Supplier<Object> supplier = Object::new; // 메서드 참조
1-4. 기타 예제
▷ 예제 1 ) Thread 표현식
new Thread(new Runnable() {
public void run() {
System.out.println("전통적인 방식의 일회용 스레드 생성");
}
}).start();
new Thread(()->{
System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
}).start();
▷ 예제 2 ) 람다 표현식과 메서드 참조의 비교
DoubleUnaryOperator oper;
oper = (n) -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));
oper = Math::abs; // 메소드 참조
System.out.println(oper.applyAsDouble(-5));
▷ 예제 3 ) 배열 생성 시 생성자 참조
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new; // 생성자 참조
댓글