02.람다 표현식
람다 표현식
- 람다 표현식이란 함수형 프로그래밍을 구성하기 위한 함수식이다.
- 간단히 말해 자바의 메소드를 간결한 함수 식으로 표현한 것이다
- 메소드 표현식을 메소드타입, 메소드 이름, 매개변수 타입, 중괄호, return 문을 생략하고, 화살 기호를 넣는다
- 이러한 특징으로 람다식을 이름이 없는 함수 익명 함수라고도 한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int add(int x, int y){
return x + y;
}
// 위의 메소드를 람다식으로 바꾸면
// 메소드 반환 타입, 메소드명 생략
(int x, int y) -> {
return x + y;
}
// '매개변수 타입'도 생략할 수 있다
(x, y) -> {
return x + y;
}
// 함수에 리턴문 한줄만 있는 경우 '중괄호', 'return'도 생략할 수 있다
(x, y) -> x + y;
- 타입을 생략해도 컴파일러가 에러를 띄우지 않는 이유는
- 컴파일러 나름대로 생략된 타입 위치를 추론하여 동작하게 해주기 때문이다.
람다식과 자바스크립트 익명 화살표 함수
- JS의 익명 화살표 함수도 람다 함수의 일종이다.
- 단지 화살표 모양이 자바(->), JS(=>) 이다
1
2
3
4
5
6
7
const Myfunction = {
print: function(){}
};
Myfunction.print = (str) => console.log(str);
let myfunc = Myfunction;
myfunc.print("Hello");
1
2
3
4
5
6
7
8
9
10
interface Myfunction{
void print(String str);
}
public class Main{
public static void main(String[] args){
Myfunction myfunc = (str) -> System.out.println(str);
myfunc.print("Hello");
}
}
- 변수에 함수를 담을 때,
- 자바스크립트는 약타입 언어라 타입에 관계없이 자유롭게 받을 수 있다
- 하지만 자바에는 8가지 타입밖에 없기 때문에 함수 데이터 자체를 담을 수 있을만한 자료형이 딱히 없다
- 그래서 인터페이스를 익명 구현 객체 타입으로써, 해당 인터페이스 타입으로 함수를 받을 수 있게 하였다.
람다식과 함수형 인터페이스
- 람다식 형태를 보면 마치 자바의 메소드를 변수로 선언하는 것 처럼 보이지만, 사실 자바는 메소드를 단독으로 선언할 수 없다
- 형태만 그렇게 보일 뿐이고, 코드를 보면 람다 함수식을 변수에 대입하고 변수에서 메소드를 호출하는 것이다. 마치 객체와 같다.
- 사실 람다식도 결국은 객체이다. 인터페이스를 익명 클래스로 구현한 익명 구현 객체를 짧게 표현한 것 뿐이다.
1
2
Myfunction myfunc = (str) -> System.out.println(str);
myfunc.print("Hello");
객체 지향 방식 vs 람다 표현 방식
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface IAdd{
int add(int x, int y);
}
class Add implements IAdd{
public int add(int x, int y){
return x + y;
}
}
public class Main{
public static void main(String[] args){
Add a = new Add();
int res = a.add(1, 2);
System.out.println(res);
}
}
- 한 번만 사용ㄷ하고 버려질 클래스라면, 번거롭게 클래스를 선언하지 않고, 익명 클래스로 일회용 오버라이딩하여 사용하기도 한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IAdd{
int add(int x, int y);
}
public class Main{
public static void main(String[] args){
IAdd a = new IAdd() {
public int add(int x, int y){
return x + y;
}
};
int res = a.add(1, 2);
System.out.println(res);
}
}
- 람다는 이 익명 클래스 코드를 짧게 표현한 것이다
1
2
3
4
5
6
7
8
9
10
11
12
interface IAdd{
int add(int x, int y);
}
public class Main{
public static void main(String[] args){
IAdd a = (x, y) -> {return x + y}; // 람다식의 끝에 세미콜론을 잊지 말자!!
int res = a.add(1, 2);
System.out.println(res);
}
}
- 아무 클래스나 추상 클래스의 메소드를 람다식으로 줄이거나 하는 행위는 못한다
- 오로지 인터페이스로 선언한 익명 구현 객체만이 람다식으로 표현이 가능하다.
- 그리고 람다식이 가능한 인터페이스를 가리켜 함수형 인터페이스라고 한다
함수형 인터페이스
- 함수형 인터페이스란 딱 하나의 추상 메소드가 선언된 인터페이스이다.
- 람다식 자체가 하나의 메소드를 한줄로 정의하는 표현식이기 때문에, 두 개 이상의 추상 메소드가 있으면 표현할 방법이 없다.
- 단, 인터페이스의 final 상수나, default, static, private 메소드는 추상 메소드가 아니기 때문에 고려하지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 함수형 인터페이스 가능
interface IAdd{
int add(int x, int y);
}
// 불가능
interface Calculator{
int add(int x, int y);
int min(int x, int y);
}
// 구성요소가 많아도 추상 메소드는 하나라 가능
interface IAdd {
int add(int x, int y);
final boolean isNumber = true; // final 상수
default void print1() {} // 디폴트 메소드
private void print2() {} // static 메소드
}
@FunctionalInterface
- 나만의 함수형 인터페이스를 만들 때 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 checking 해주는 기능
- 두 개 이상의 추상화 메소드 선언 시 컴파일 에러 발생.
1
2
3
4
5
@FunctionalInterface
public interface MyFunctional{
public void method();
public void otherMethod(); // 컴파일 오류 발생
}
람다식의 타입 추론
- 컴파일러가 람다 함수식을 보고 추론하여 타입을 유추한다
- 추론의 근거로 사람이 미리 정의해 놓은 정의문을 보고 추론을 하는 것이다
1
2
3
4
5
6
7
8
9
10
11
12
13
interface IAdd{
int add(int x, int y);
}
public class Main{
public static int result(IAdd lambda){
return lambda.add(1, 2);
}
public static void main(String[] args){
int n = result((x,y) -> x + y);
}
}
- 1.. 람다식을 받는 메소드의 매개변수 타입을 본다 -> result 메소드의 매개변수 타입
- 2.. 함수형 인터페이스 정의문을 찾아 추상 메소드 형태를 본다 -> IAdd 인터페이스로 가서 추상화 메소드 확인
- 3.. 추상 메소드에 정의된 타입에 따라 람다 함수식의 타입을 자동으로 판별한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;
@FunctionalInterface
interface Comparator<T> {
int compare(T o1, T o2);
}
class Collections{
public static <T> void sort(List<T> list, java.util.Comparator<? super T> c){
list.sort(c);
}
}
public class Main{
public static void main(String[] args){
List<String> words = Arrays.asList("aaa", "bbb", "ccc", "ddd");
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
}
}
- 위의 sort를 분석해보면
- Collections 클래스의 sort 메소드 정의문을 보면, 람다 함수의 함수형 인터페이스 타입은 java.util.Comparator 이다.
- 람다의 매개변수 타입은 Comparator 인터페이스의 제네릭 T 타입으로 지정되어 있다
- 컴파일러가 타입을 유추하는 순서는 다음과 같다
- 1.. sort 메소드의 첫번째 매개변수 List
형태의 객체가 들어온다 - 2.. 첫번째 매개변수의 타입 지정에 의해 sort 메소드의 제네릭 타입 매개변수는 모두 String이 된다
- 3.. Comparator 인터페이스 제네릭 타입도 String이 된다. 추상 메소드의 매개변수 타입도 String이 된다
- 4.. 최종적으로 람다 함수의 타입 구성은 int형 메소드 반환 타입과 String 형 매개변수 타입 두개로 추론된다.
- 1.. sort 메소드의 첫번째 매개변수 List
- 람다식의 타입을 개발자가 유추하기 쉽게 도와주기 위해 상황에 따라 람다식 파라미터에 타입을 쓰기도 한다.