람다식이란
람다식이란 메서드(함수)를 간단한 식(expression)으로 표현하는 방법이다.
간단한 식 즉, 간단하게 표현하고 싶은 방법이다.
작성 방법은 다음과 같다.
메서드의 이름과 반환 타입을 제거하고 -> 를 블록 {} 앞에 추가한다.
1 2 3 |
int getMax(int a, int b) { return a > b ? a : b; } |
1 2 3 |
(int a, int b) -> { return a > b ? a : b; } |
curly braces (중괄호) 안에 문장이 한 줄 일때는 중괄호를 생략한다.
그리고, 반환 타입일 경우 return도 생략 가능하다.
문장뒤에 세미콜론은 생략한다.
1 2 3 |
(int a, int b) -> { return a > b ? a : b; } |
1 |
(int a, int b) -> a > b ? a : b |
입력 매개 변수의 타입이 추론 가능하면 생략 가능하다.
1 |
(int a, int b) -> a > b ? a : b |
1 |
(a, b) -> a > b ? a : b |
Functional Interface (함수형 인터페이스)
자바스크립트 등 타 언어에서는 람다식을 익명 함수라고 한다.
그러나 자바에서는 함수가 객체 없이 홀로 존재할 수 없으므로 익명 클래스 안에 익명 함수로 표현을 해야 한다. 위에서 최종적으로 작성한 람다식을 다시 함수로 변환하게 되면 다음과 같다.
1 |
(a, b) -> a > b ? a : b |
1 2 3 4 5 |
new Object() { int getMax(int a, int b) { return a > b ? a : b; } }; |
위 오른쪽을 Anonymous Class (익명 클래스)라고 부르며 익명 클래스란 객체의 선언과 생성을 동시에 한다는 것을 의미한다.
하지만 이 경우 객체 참조를 받아서 호출할 수가 없다.
1 2 3 4 5 6 7 |
Object obj = new Object() { int getMax(int a, int b) { return a > b ? a : b; } }; obj.getMax(); // error |
그러므로 하나의 함수를 가진 인터페이스를 만든 다음에 익명 클래스를 생성해야 해당 함수를 호출 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Main { public static void main(String[] args) { MyInterface f = new MyInterface() { @Override public int getMax(int a, int b) { return a > b ? a : b; } }; f.getMax(1, 2); } } @FunctionalInterface interface MyInterface { public abstract int getMax(int a, int b); } |
하나의 함수를 가진 인터페이스를 Functional Interface라고 한다. 위에서 @FunctionInterface 어노테이션을 붙이지 않아도 에러가 나지는 않는다. 하지만 붙이게 된다면 컴파일시에 함수가 하나밖에 없는지를 검사하고 두 개 이상이라면 컴파일시에 에러를 내게 해준다.
다시 위의 익명클래스를 람다식으로 변환하게 되면 다음과 같다. 앞에서 매개변수 타입을 생략한 이유도 참조 변수의 타입으로 추론할 수 있기 때문에 생략한것이며 리턴타입을 생략한것도 같은 이유에서다.
결론적으로 자바에서는 익명클래스와 람다식이 동일하다고 생각하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Main { public static void main(String[] args) { MyInterface f = (a, b) -> a > b ? a : b; f.getMax(1, 2); } } @FunctionalInterface interface MyInterface { public abstract int getMax(int a, int b); } |
람다식의 사용 예제를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Lamda2 { public static void main(String[] args) { List<String> list = Arrays.asList("ccc", "bbb", "ggg", "eee", "ddd"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); System.out.println(list); } } |
sort 함수의 두번째 파라메터는 FunctionalInterface 인 Comparator<T> 타입이다. 따라서 여기에는 익명클래스를 넣어줘야 한다. 익명클래스는 람다식과 동일하므로 다음과 같이 간단히 할 수 있다.
1 2 3 4 5 6 7 8 |
public class Lamda2 { public static void main(String[] args) { List<String> list = Arrays.asList("ccc", "bbb", "ggg", "eee", "ddd"); Collections.sort(list, (o1, o2) -> o1.compareTo(o2)); System.out.println(list); } } |
function 패키지
java.util.function 패키지에 다양한 functional interface가 만들어져 있다.
Functional Interface | 메서드 | 설명 |
---|---|---|
java.lang.Runnable | void run() | 매개변수도 없고 반환값도 없음 |
Supplier<T> | T get() | 매개변수는 없고 반환값만 존재 |
Consumer<T> | void accept(T t) | 매개변수만 있고 반환값이 없음 |
Function<T, R> | R apply(T t) | 매개변수와 반환값 모두 존재 |
Predicate<T> | boolean test(T t) | 매개 변수가 있고, 반환 타입이 boolean |
Supplier<T>의 예를 보자. 우측 람다식을 보면 입력이 없고 반환값은 존재하므로 Supplier 타입으로 매핑할 수 있다.반환값은 int 이므로 Supplier<T> 에서 T 타입은 Integer로 추론이 된다.
1 2 3 4 5 6 |
public class Main { public static void main(String[] args) { Supplier<Integer> s = () -> (int) (Math.random() * 10) + 1; System.out.println(s.get()); } } |
Consumer<T> 의 예를 보자. 매개 변수는 있고 반환값이 없으므로 Consumer 타입으로 매핑가능하다. 입력 타입은 String도 가능하고 Integer도 가능하고 모든게 가능한데 만일 String 타입으로 매핑하면 Consumer<String> 이 되고, accept 함수를 호출할때 String 타입으로 매핑해야 한다.
1 2 3 4 5 6 |
public class Main { public static void main(String[] args) { Consumer<String> c = i -> System.out.println("i is " + i); c.accept("aaa"); } } |
Predicate<T> 의 예를 보자.
1 2 3 4 5 6 |
public class Main { public static void main(String[] args) { Predicate<Integer> p = i -> i % 2 == 0; System.out.println(p.test(3)); } } |
Function<T, R> 의 예를 보자.
1 2 3 4 5 6 |
public class Main { public static void main(String[] args) { Function<Integer, Integer> f = i -> i / 10 * 5; System.out.println(f.apply(13)); } } |