람다식의 도입으로 인해, 자바는 객체지향언어인 동시에 함수형 언어가 되었다.
람다식이 무엇인지 알아보도록 하자.
람다식(Lamda expression)은 간단히 말해서 메서드를 하나의 '식(expression)'으로 표현한 것이다.
람다식은 '익명함수'답게 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 '->'을 추가한다.
int max(int a, int b){
return a > b ? a : b;
}
위의 코드를 람다식으로 변경하면 아래와 같이 나타낼 수 있다.
(int a, int b) -> {return a > b ? a : b;}
(int a, int b) -> a > b ? a : b
//이 때 문장이 아닌 '식'이므로 끝에 ';'을 붙이지 않는다.
(a, b) -> a > b ? a : b
하나의 메서드가 선언된 인터페이스를 정의해서 람다식을 다루는 것은 기존의 자바 규칙들을 어기지 않으면서도 자연스럽다.
@FunctionalInterface
interface MyFunction{ //함수형 인터페이스 MyFunction을 정의
public abstract int max(int a, int b)
}
📌 @FunctionalInterface을 붙이면, 컴파일러가 함수형 인터페이스를 올바르게 정의하였는지 확인해주므로, 꼭 붙이도록 하자.
함수형 인터페이스가 아래와 같이 정의되어 있을 때
@FunctionalInterface
interface MyFunction{ //함수형 인터페이스 MyFunction을 정의
void myMethod(); //추상 메서드
}
void eMethod(MyFunction f){ //매개변수의 타입이 함수형 인터페이스인 임의의 메서드 작성
f.myMethod(); //MyFunction에 정의된 메서드를 호출
}
.....
MyFunction f = () - > System.out.println("람다식 작성");
aMethod(f); //람다식을 참조하는 참조변수를 매개변수로
//또는 참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.
aMethod(() - > System.out.println("람다식 작성"));
이렇게 람다식을 참조변수로 다룰 수 있다는 것은 메서드를 통해 람다식을 주고받을 수 있다는 것을 의미한다.
함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다.
MyFunction f = (MyFunction) (() -> {}); //양변의 타입이 다르기 때문에 형변환이 필요하다.
람다식 내에서 참조하는 지역변수는 final이 붙지 않았어도 상수로 간주된다.
대부분의 메서드는 타입이 비슷하다. 매개변수가 없거나 한 개 또는 두 개, 반환 값은 없거나 한개이며 제네릭 메서드로 정의하면 매개변수나 반환 타입이 달라도 문제가 되지 않는다.
매개변수와 반환값의 유무에 따라 4개의 함수형 인터페이스가 정의되어 있고, Function의 변형으로 Predicate가 있는데, 반환값이 boolean이라는 것만 제외하면 Function과 동일하다.
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
java.lang.Runnable | void run() | 매개변수도 없고, 반환값도 없다. |
Supplier<T> | T get() | 매개변수는 없고, 반환값만 있다. |
Consumer<T> | void accept(T t) | Supplier와 반대로 매개변수만 있고, 반환값이 없다. |
Function<T,R> | R apply(T t) | 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환한다. |
Predicate<T> | boolean test(T t) | 조건식을 표현하는데 사용되며, 매개변수는 하나. 반환타입은 boolean이다. |
참고로 컬렉션 프레임워크의 인터페이스에도 다수의 디폴트 메서드가 추가되 었는데, 그 중의 일부는 함수형 인터페이스를 사용한다.
람다식이 하나의 메서드만 호출하는 경우에는 '메서드 참조(method reference)'라는 방법으로 람다식을 간략히 할 수 있다.
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
위와 같이 작성된 람다식을 메서드 참조로 나타내면?
Function<String, Integer> f = Integer::parseInt;
람다식을 메서드 참조로 바꾼 예시
BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2);
//메서드 참조
BinFunction<String, String, Boolean> f = String::equals;
MyClass obj = new MyClass();
Function<String, Boolean> f = (x) -> obj.equals(); //람다식
Function<String, Boolean> f = obj::equals; //메서드 참조
하나의 메서드만 호출하는 람다식은 '클래스이름::메서드이름' 또는 '참조변수::메서드이름'으로 바꿀 수 있다.
생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.
Supplier<MyClass> s = () -> new MyClass(); //람다식
Supplier<MyClass> s = MyClass::new; //메서드 참조
그리고 배열을 생성할 때는 아래와 같이 하면 된다.
Function<Integer, int[]> f = x -> new int[x]; //람다식
Function<Integer, int[]> f= int[]::new; //메서드 참조
📌 메서드 참조는 람다식을 마치 static변수처럼 다룰 수 있게 해주며, 코드를 간략히 하는데 유용하므로 많이 사용된다.
자바의 정석 3rd Edition