자바는 OOP언어이지만 jdk 1.8이후로 함수형 언어의 기능을 추가했다.
int max(int a, int b) {
return a > b ? a : b;
}
(a, b) -> a > b ? a : b
(int a, int b) -> a > b ? a : b
=> (a, b) -> a > b ? a : b
(a) -> a * a
=> a -> a * a
(int i) -> {
System.out.println(i);
}
=> (int i) -> System.out.println(i)
🖐 중괄호 내의 한줄짜리 문장이 return문이라면 중괄호 생략 불가
⭕️
(int i, int b) -> {
return a + b;
}
❌
(int i, int b) -> return a + b
but 위에서 반환값이 있으면 return문을 생략할 수 있다고 했다. 이는 즉 return을 붙일 거면 중괄호도 붙이라는 거고, return을 생략할 거면 중괄호도 생략할 수 있다는 말이다. 그러면 위 코드에서 아래문장은 이렇게 바꿔 써도 된다.
(int i, int b) -> a + b //매개변수 타입도 생략가능
자바의 언어적 특성상 함수만 독립적으로 존재할 수 없고, 클래스 내부의 메서드로만 존재할 수 있다. 때문에 람다식은 익명 클래스의(선언과 동시에 생성되는 클래스) 객체와 동등하다.
뭐가 되었든 람다식도 메서드이므로 참조변수가 있어야 람다식을 호출할 수 있는데, 자바에서는 이 참조변수의 타입을 함수형 인터페이스로 받기로 했다.
@FunctionalInterface
키워드를 붙이면 컴파일러가 함수형 인터페이스를 올바르게 작성했는지 체크해준다.//max라는 이름의 추상 메서드 하나만 선언되어있는 함수형 인터페이스
@FunctionalInterface
interface MyFunction {
public abstract int max(int a, int b);
}
//익명 클래스로 람다식을 구현했고, 참조변수의 타입은 MyFunction이라는 이름의 함수형 인터페이스 타입
MyFunction f = new MyFunction {
public int max(int a, int b) {
return a > b ? a : b;
}
}
int value = f.max(3,5); //5
이 패키지에는 FunctionalInterface들이 선언되어있다. 함수형 인터페이스를 매개변수로 받는 메서드를 사용할 때 매번 사용자가 함수형 인터페이스를 정의해서 사용할 필요 없이 자주 쓰이는 기능을 담은 함수형 인터페이스들을 가져다 쓰면 되도록 자바에서 제공해주는 패키지이다.
참고로 패키지 내에 있는 인터페이스를 보면 추상 메서드 단 하나만 선언되어 있지 않다는 걸 확인할 수 있다. 원래라면 인터페이스에 추상 메서드만 선언할 수 있었지만, 자바 1.8버전부터 인터페이스에 디폴트 메서드와 static 메서드를 선언하는 게 가능해졌다.
이 디폴트 메서드들을 이용하여 두 개 이상의 람다식을 이어붙이거나 하는 등의 다양한 작업을 수행할 수 있다.
java.util.function
패키지에는 Predicate라는 함수형 인터페이스가 있다. 이 인터페이스는 조건식을 람다식으로 표현하는 데 사용된다.
사진을 보면 Predicate 인터페이스에는 test라는 추상 메서드가 선언되어 있는데, 이를 다음과 같은 코드로 사용할 수 있다.
Predicate<String> isEmptyStr = s -> s.length() == 0;
String s = "";
if (isEmptyStr.test(s)) {
System.out.println("빈 문자열입니다.");
}
위처럼 Predicate
는 boolean
값을 리턴하기 위한 조건식을 람다식으로 구현할 수 있는 틀을 제공해준다.
그런데 이러한 함수형 인터페이스를 컬렉션 프레임워크도 사용하고 있는데, 용례는 다음과 같다.
사진은 Collection
클래스의 removeIf
메서드 코드를 캡처한 것이다.
removeIf
메서드가 인자로 Predicate
를 구현한 객체를 받는 것을 볼 수 있다. 우리가 작성한 람다식을 갖고 있는 Predicate
타입 익명 객체가 인자로 넘어가면 그 조건식에 따라 true를 일치하면 요소를 삭제하는 코드이다.
이 외에도 Iterable인터페이스의 forEach(Consumer<T> action)
등 다양한 메서드에서 람다식을 매개변수로 받고 있으니 사용중인 메서드가 어떤 함수형 인터페이스 타입을 받고 있는지 코드도 확인해보면 좋을 것 같다.
import java.util.function.Function;
public class MethodReferenceEx {
public static void main(String[] args) {
// 3. 원래 람다식은 s -> new MyClass(s) 인데,
// Function<String, MyClass>를 통해 람다식의 매개변수의 타입과 리턴타입을 알 수 있으니 매개변수를 생략하고,
// '클래스명::메서드명' 으로 변경해줄 수 있다. 이게 메서드 참조를 사용하는 방식.
MyClass myClass = makeMyClass(MyClass::new); // = makeMyClass(s -> new MyClass(s));
System.out.println("name is: " + myClass.getName()); // name is: method reference
}
// 1. makeMyClass란 메서드에 매개변수로 Function인터페이스 타입의 람다식을 받겠다고 작성했다.
// 이 람다식은 String타입 매개변수를 받고, MyClass타입을 리턴한다.
private static MyClass makeMyClass(Function<String, MyClass> lambda) {
// 2. 람다식에 "method reference"이란 String 값을 인자로 전달한다.
String name = "method reference";
return lambda.apply(name);
}
}
public class MyClass {
String name;
public MyClass(String name) {
this.name = name;
}
public String getName() {
return name;
}
}