포스트

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
참고 사이트

인파-람다 표현식(Lambda Expression) 완벽 정리