람다식(Lambda expression)
람다식의 도입으로 이해 자바는 객체지향언어인 동시에 함수형 언어가 되었다.
람다식은 간단히 말해서 메서드를 하나의 식으로 표현한 것이다. 람다식은 함수를 간략하면서도 명확한 식으로 표현할 수 있게 해준다.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 ‘익명함수’라고도 한다.
람다식은 ‘익명함수’ 답게 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 ‘ -> ‘ 를 추가한다.
ex)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//메서드
int max(int a, int b) {
return a > b ? a : b;
}
void printVar(String name, int i){
System.out.println(name+"="+i);
}
int roll(){
return (int) (Math.random() * 6);
}
// 람다
(int a, int b) -> a > b ? a : b;
(String name, int i) -> System.out.println(name+"="+i);
() -> (int) (Math.random * 6)
람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다. 대부분의 경우 생략이 가능하다. 람다식에 반환타입이 없는 이유도 항상 추론이 가능하기 때문이다.
매개변수가 하나뿐인 경우 괄호()를 생략할 수 있다. 단 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.
괄호{} 안의 문장이 하나일 때는 괄호{}를 생략할 수 있다. 이 때 문장의 끝에 ‘;’는 붙이지 않아야 한다. 단 괄호{}안의 문장이 return 문일 경우 괄호{}를 생략할 수 없다.
함수형 인터페이스
람다식이 메서드와 동등한 것 같지만 익명 클래스의 객체와 동등하다.
ex)
1
2
3
4
5
6
7
(int a, int b) -> a > b ? a : b
new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
람다식으로 정의된 익명 객체의 메서드를 어떻게 호출할 수 있나? 알고 있듯 참조변수가 있어야 객체의 메서드를 호출 가능하다.
1
타입 f = (int a, int b) -> a > b ? a : b
이 때 참조변수 f의 타입은 어떤 것이어야 하는가. 참조형이기때문에 클래스 또는 인터페이스가 가능하다. 그리고 람다식과 동등한 메서드가 정의되어 있는 것이어야 한다. 그래야 참조변수로 람다식의 메서드를 호출할 수 있기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface MyFunction{
public abstract int max(int a, int b);
}
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
};
int big = f.max(5, 3);
Myfunction f = (int a, int b) -> a > b ? a : b;
int big = f.max(5, 3);
1
2
3
4
5
6
7
8
9
10
11
List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");
Collections.sort(list, new Comparator<String>() {
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
});
//위를 람다식으로 간단히 처리 가능하다.
Collections.sort(list, (s1, s2) -> s2.compareTo(s1));
함수형 인터페이스가 아래와 같이 구현되어 있을 때.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@FunctionalInterface
interface MyFunction{
void run();
}
class LambdaEx1{
static void execute(MyFunction f){
f.run();
}
static MyFunction getMyFunction(){
MyFunction f = () -> System.out.println("f3.run()");
return f;
}
public static void main(String[] args){
MyFunction f1 = () -> System.out.println("f1.run()");
MyFunction f2 = new MyFunction(){
public void run(){
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute( ()->System.out.println("run()") );
//참조변수 없이 직접 람다식을 매개변수로.
}
//f1.run()
f2.run()
f3.run()
f1.run()
run()
메서드의 매개변수가 MyFunction타입이면, 이 메서드를 호출할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야 한다는 뜻이다. 또는 참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.
람다식을 참조변수로 다룰 수 있다는 것은 메서드를 통해 람다식을 주고받을 수 있다는 것을 의미한다. 즉, 변수처럼 메서드를 주고 받는 것이 가능하다는 뜻이다.
람다식의 타입과 형변환
함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다. 람다식은 익명 객체이고 익명 객체는 타입이 없다. 정확히는 타입은 있지만 컴파일러가 임의로 이름을 정하기 때문에 타입을 알 수 없다. 그래서 대입 연산자의 양변의 타입을 일치시키기 위해 아래와 같이 형변환이 필요하다.
1
MyFunction f = (MyFunction) ( ()->{} );
람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.
컬렉션 프레임웍 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LambdaEx{
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; i++)
list.add(i);
list.forEach(i->System.out.print(i+",");
System.out.println();
list.removeIf(x-> x%2==0 || x%3==0);
System.out.println(list);
list.replaceAll(i->i*10);
System.out.println(list);
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
map.forEach( (k,v)->System.out.print("{"+k+", "+v+"},"));
//map의 모든 요소를 {k,v} 형태로 출력
}