ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • java anonymous 와 lambda Closure 다른점
    카테고리 없음 2023. 4. 21. 15:27
    우리는 저번에 자바의 Closure에 대해 살짝 맛봤다. 대충 어떤 느낌인지는 알 듯하다. 다시 잠깐 살펴보자.
    private static Function<String, Integer> function() {
      int a = 100;
      return new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
          return Integer.parseInt(s) * a;
        }
      };
    }
    
    Function<String, Integer> function = function();
    Integer apply = function.apply("3");
    System.out.println(apply);
    
    //아래는 람다
    private static Function<String ,Integer> function(){
      int a = 100;
      return s -> Integer.parseInt(s) * a;
    }
    
    저번에 말했듯이 Closure는 범위와 관련있다. 범위를 확장한다고 하면 좀 더 쉽게 다가 올 수 있다. 더 정확히는 복사를하지만 그냥 쉽게 생각하자. 이걸 capture라 하는데.. 뭐 그건 컴파일러가 하는 일이니 알고만 알자! 위의 예제는 anonymous 클래스와 lambda의 Closure 예제이다. 간단한 코드이기 때문에 자바를 할 줄안다면 누구나 알수 있는 그런 코드이다. 위의 코드는 별의미 없고 그냥 다시 한번 보자는 의미로 넣었다. 그런데 anonymous클래스 의 Closure 와 lambda의 Closure에는 조금 차이점이있다. 그걸 알아보자. 우리는 개발자니 소스부터 보는게 이해가 훨씬 빠르니 소스부터 부자.
    private String someVariable = "9000";
    
    public void anonymousClosure() {
      String someVariable = "1000";
      Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
          return Integer.parseInt(someVariable);
        }
      };
    }
    public void lambdaClosure() {
      String someVariable = "1000";
      Function<String,Integer> function = i -> Integer.parseInt(someVariable);
    }
    
    다음과 같은 코드가 있다고 가정하자. 맨 위에 있던 예제와 비슷하다. 하지만 여기서 중요한건 someVariable이 라는 전역변수와 메서드 안에 있는 똑같은 someVariable이라는 지역변수이다. 변수명이 같은 변수이다. apply()메서드 안에있는 someVariable는 anonymousClosure() 메서드안의 someVariable를 가르키고 있다. idea나 이클립스를 도움받아 위치로 이동할 수 있으니 해보자. 마찬가지로 lambda식안에 있는 someVariable도 lambdaClosure() 메서드안의 변수를 가르키고 있다. 둘다 똑같다. 엥 그럼 뭐가 다르다고 하는거지. 눈치 빠른 사람은 눈치 챘겠지만 우리는 클래스안에 있는 전역변수 즉, someVariable = "9000"; 이놈을 가르키고 싶다면 어떻게 해야 될까? 음..this는 자기 자신을 말하는거니까 this를 하면 되겠지?
    //생략
    
    Function<String, Integer> function = new Function<String, Integer>() {
      @Override
      public Integer apply(String s) {
        return Integer.parseInt(this.someVariable);
      }
    };
    //...
    
    엥 컴파일 에러가 난다. (너무 당연한가?) 맞다. 저 this는 익명클래스의 자기 자신을 가르킨다. 즉, this는 Function 클래스의 자신이다. 익명 클래스 안에는 someVariable라는 변수가 없다. 그래서 에러가 발생한다. 그럼 람다는 어떨까?
    Function<String,Integer> function = i -> Integer.parseInt(this.someVariable);
    
    잘된다. 컴파일 에러가 나지 않는다. idea의 도움 받아보면 전역변수 someVariable으로 이동한다. lambda식 안에는 별도의 스코프가 없다. 스코프가 없다고 하기엔 그렇고 확장?한다고 해야될까? 뭐라고 해야되지... 스코프가 없다면 람다안의 변수를 외부에서 쓸수있을 것이라고 생각할 수 있으니 확장이 더 맞을 수 도 있겠다. 그럼 익명 클래스 안에서 전역 변수 someVariable를 쓰고 싶다면 쓸 수는 있다. 아래와 같이 말이다.
    Function<String, Integer> function = new Function<String, Integer>() {
      @Override
      public Integer apply(String s) {
        return Integer.parseInt(ClosureExample.this.someVariable);
      }
    };
    
    좀 귀찮을 뿐 쓸 수는 있다. 근데 여기서는 다른점을 찾는거니.. 다음 소스를 보자. 비슷한 코드이긴하다. 그래도 넣은이유는 람다를 모르는 사람에게 위의 소스는 컬리브레이스가 없어서 그런거 일 거라고 생각할 수도 있어서 넣어봤다.
    public void anonymousClosure() {
      Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
          System.out.println(this.toString());
          return null;
        }
      };
    }
    public void lambdaClosure() {
      Function<String,Integer> function = i -> {
        System.out.println(this.toString());
        return null;
      };
    }
    
    @Override
    public String toString(){
      return this.someVariable;
    }
    
    이거 또한 람다 안의 toString은 해당 클래스의 toString을 찾지만 익명클래스의 apply() 메서드안의 toString()은 Function의 toString을 찾는다. 뭐 이건 위의 예제와 비슷하니 다음으로 넘어가자. 다음 것도 비슷한 내용이긴 하지만...
    public void anonymousClosure() {
      Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
          System.out.println(toString("toString"));
          return null;
        }
      };
    }
    public void lambdaClosure() {
      Function<String,Integer> function = i -> {
        System.out.println(toString("toString"));
        return null;
      };
    }
    
    public String toString(String str){
      return str;
    }
    
    위와 같이 toString의 파라미터가 한개 있는 메서드를 만들어보자. 저걸 우리는 오버로딩이라고 부른다. 코드를보면 익명클래스안에서 toString("toString")을 호출하고 람다에서도 똑같이 toString("toString")을 호출 하고 있다. 람다에서는 별다른 문제가 없는데 익명클래스에서는 컴파일 에러가 난다. toString은 파라미터가 없는 메서드라고 에러 메시지에 나온다. 즉 외부 클래스에 있는 toString(String str) 을 찾지 못한다. 만약 또 호출 하고 싶다면 아래와 같이 하면 된다. 위에서 했으니 대충 알 것이다.
    System.out.println(ClosureExample.this.toString("toString"));
    
    우리는 이렇게 둘의 차이점을 살펴봤다. 다시 정리하자면 lambda 같은 경우에는 스코프가 확장되어 마치 스코프가 없는 것처럼 보여진다. 물론 람다안의 변수는 외부에서 사용할 수 없다. 이게 anonymous 클래스와 lambda의 차이점이라고 할 수 있겠다. 뭐 개발할때 크게 도움은 안되더라도 알아두면 좋을 거 같다. 큰사람이 되려면 알아야 겠지?

    댓글

Designed by Tistory.