03.람다 표현식2
람다식 예제
Thread 호출
1
2
3
4
5
Thread thread = new Thread( () -> {
for(int i = 0; i < 10; i++){
System.out.println(i);
}
});
enum을 깔끔하게
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum Operation{
PLUS("+"){
public double apply(double x, double y){ return x + y;}
},
MINUS("-"){
public double apply(double x, double y){ return x - y;}
},
TIMES("*"){
public double apply(double x, double y){ return x * y;}
},
DIVIDE("/"){
public double apply(double x, double y){ return x / y;}
};
private final String symbol;
Operation(String symbol){ this.symbol = symbol;}
@Override
public String toString(){ return symbol;}
public abstract double apply(double x, double y);
}
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
import java.util.function.DoubleBinaryOperator;
enum Operation{
PLUS("+", (x,y) -> x + y),
MINUS("-", (x,y) -> x - y),
TIMES("*", (x,y) -> x * y),
DIVIDE("/", (x,y) -> x / y);
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symbol, DoubleBinaryOperator op){
this.symbol = symbol;
this.op = op;
}
@Override
public String toString() { return symbol; }
public double apply(double x, double y){
return op.applyAsDouble(x, y);
}
}
public class Main{
public static void main(String[] args){
Operation.PLUS.apply(2, 3);
}
}
- 1.. apply 메소드 호출
- 2.. PLUS의 “+” 와 람다식을 Operation의 매개변수로 할당
- 3.. 함수형 인터페이스 DoubleBinaryOperator 안의 applyAsDouble 메소드가 호출
람다식의 형변환
- 사실 람다식은 익명 객체이고 타입이 없다.
- 정확히는 함수형 인터페이스로 람다식을 참조하는 것이다.
- 그래서 인터페이스 타입의 변수에 람다식을 할당하는 행위에는 캐스팅 연산자가 생략되어 있다.
- 이러한 특징을 이용해서 람다식을 모든 클래스의 최상위 클래스인 Object 클래스로 형변환이 가능하다.
1
2
3
IAdd func = (() -> {});
IAdd func = (IAdd)(() -> {});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface IAdd{
int add(int x, int y);
}
public class Main{
public static void main(String[] args){
IAdd lambda = (x, y) -> x + y;
System.out.println(lambda);
// Object 타입으로 업캐스팅하기 위해서 두 번의 캐시팅이 필요하다
Object lambda_obj = (Object) ((IAdd) ((x, y) -> x + y));
System.out.println(lambda_obj);
}
}
람다표현식의 한계
- 1.. 람다는 문서화를 할 수 없다
- 람다 자체는 이름없는 함수이기 때문에 메소드나 클래스와 다르게 문서화할 수가 없다.
- 그래서 코드 자체로 동작이 명확히 설명되지 않거나, 람다가 길거나 읽기 어렵다면 쓰지 않는 방향으로 리팩토링 해야한다
- 2.. 람다는 디버깅이 다소 까다롭다
- 람다식은 기본적으로 익명 구현 객체 기반이기 때문에, 익명 객체 특성상 디버깅 할 때 콜 스택(call stack) 추적이 매우 어렵다
- 예를들어 0을 나누는 오류가 나타나는 코드를 일반 for문과 람다식을 이용한 표현을 코드를 실행하면 차이가 있다
- 이는 람다가 내부적으로 수행하는 작업이 더 많기 때문에 발생하는 현상이다.
- 코드가 복잡해 질수록 어디에서 문제가 발생했는지 확인하기가 어려워지게 된다.
- 이는 곧 성능과 연결되기도 한다.
1
2
3
4
5
6
7
8
9
public static void main(String[] args){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for(Integer i : list){
for(int j = 0; j < i; j++){
System.out.println(i / j);
}
}
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(i -> {
IntStream.range(0, i).forEach(j -> {
System.out.println(i/j);
});
});
}
- 3.. stream에서 람다를 사용할 시 for문보다 성능이 떨어진다
- 0부터 10000까지 단순 순회하는 로직을 보면 차이가 적지 않다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args){
// 람다식 stream 순회
long startTime = System.nanoTime(); // 코드 시작 시간
IntStream.range(1, 10000).forEach((value) -> {});
long endTime = System.nanoTime(); // 코드 끝난 시간
long durationTimeSec = endTime - startTime;
System.out.println("람다식 stream 순회 : " + durationTimeSec + "n/s");
// 일반 for문 순회
startTime = System.nanoTime();
for(int i = 0; i < 10000; i++){}
endTime = System.nanoTime();
durationTimeSec = endTime - startTime;
System.out.println("일반 for문 순회 : " + durationTimeSec + "n/s");
}
1
2
람다식 stream 순회 : 13870700n/s
일반 for문 순회 : 43900n/s
- 4.. 람다를 남발하면 코드가 지저분해질 수 있다
- 기존에는 동작 행위를 클래스에서 메소드로 정의해 놓고, 실행부에서 갖다 쓰는 형태
- 람다는 동작 행위를 실행부에서 지정하는 식
- 따라서 람다식을 남발하다 보면 비슷하게 생긴 함수를 계속 중복 생성하는 경우가 많다
- 보통 람다식 로직이 두줄 세줄 넘어가면 실행부의 코드가 지저분해진다
1
2
3
4
5
6
7
8
9
10
11
12
interface OperationStrategy{
// (int x, int y) -> int
int calculate(int x, int y);
}
// Template
class OperationTemplate{
int caluclate(int x, int y, OperationStrategy cal){
int res = cal.calculate(x, y);
return res;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args){
int x = 100;
int y = 30;
OperationContext cxt = new OperationContext();
int res = cxt.calculate(x, y, (x1, y1) -> x1 + y1);
System.out.println(res);
int res = cxt.calculate(x, y, (x1, y1) -> x1 - y1);
System.out.println(res);
int res = cxt.calculate(x, y, (x1, y1) -> x1 * y1);
System.out.println(res);
int res = cxt.calculate(x, y, (x1, y1) -> x1 / y1);
System.out.println(res);
}
- 5.. 재귀로 만들 경우에는 다소 부적합하다
- 람다식을 통해 재귀함수를 구축하면 컴파일 에러가 난다
1
2
3
4
5
6
7
public static void main(String[] args){
UnaryOperator<Long> factorial = (x) -> {
x == 0? 1: x * factorial.apply(x - 1);
};
factorial(1);
}
1
java: not a statement