Effectively Final이 뭐여

Updated:

람다식 공부중에 나온 effectively final에 대해 짧게 포스팅 하고 가자!

java의 anonymous class는 final 이어야만 접근이 가능하다. final은 다 알다시피 상수이다. 변하지 않는 값이다. (값을 말한거지 레퍼런스(참조하고 있는 값)를 말한 것이 아님). 즉 할당이 다시 되지 않는 것이지 참조하고 있는 변수들은 변경 가능하다.

anonymous class 예시

void test() {
  int a = 0;
  Runnable runnable = new Runnable() {
    @Override
    public void run() {
      System.out.println(a);
    }
  };
}

이와 같은 익명 클래스가 있을 때는 우리는 다음과 같이 변수 a에 접근 할 수 없다. 왜냐하면 final이 아니라 접근하지 못했다. 하지만 java8부터는 위와 같이 해도 컴파일 에러가 나지 않는다. a라는 변수에 직접 접근할 수 있다. 람다의경우는 아래와 같다. 컴파일 에러가 나지 않는다.

void test() {
  int a = 0;
  Runnable runnable = () -> System.out.println(a);
}

그렇다면,, final이 아니라면 변경이 가능할까?

void test() {
  int a = 0;
  Runnable runnable = new Runnable() {
    @Override
    public void run() {
      System.out.println(a);
      a++; //컴파일 에러
    }
  };
}

//lambda의 경우
void test() {
  int a = 0;
  Runnable runnable = () -> {
    System.out.println(a);
    a++; //컴파일 에러
  };
}

위와 같이 a라는 변수를 변경 시킬 때 컴파일 에러를 발생시킨다. 에러 내용을 보면 effectively final 라고 한다. 실질적으로 final이라는 것이다. 생략을 했을뿐 final이다. 우리는 어쨋든 익명 클래스 밖에 있는 지역변수를 접근할 때는 무조건 final이어야 한다. 하지만 우리는 꼼수를 써서 지역변수의 final의 레퍼런스를 변경 시키기도 한다.

void test() {
  int[] a = {0};
  Runnable runnable = () -> {
    System.out.println(a[0]);
    a[0]++;
  };
}

위와 같이 배열(혹은 참조할 수 있는 어떤 객체)을 사용하여 우리는 변경 시켰다. 잘 된다. 그리고 또한 아직 까진 문제가 없다. 그런데 왜 익명클래스밖 지역변수를 접근할때는 왜 꼭 final이어야 할까? 흠.. 자바를 설계했던 사람이 괜히 final로 만든 것은 아니였을 것이다. 아무 side effect 없다면 그냥 접근해도 될 텐데 말이다.. side effect 있으니 final로 설계해 만들었을 것 아닌가? 그럼 어떤 side effect가 있어서 꼭 변하지 않는 final이어야 할까?

그건 바로 스레드 세이프하지 않기 때문이다. 예를 들어보자.

int count = 1000;
int[] flag = {0};
Runnable b = new Runnable() {
  @Override
  public void run() {
    if (flag[0] == 0) {
      flag[0] = 1;
      try {
        TimeUnit.MICROSECONDS.sleep(10);
      } catch (InterruptedException e) {
      }
      System.out.println(flag[0]);
    } else {
      flag[0] = 0;
    }
  }
};

Thread[] threads = new Thread[count];
for (int i = 0; i < count; i++) {
  threads[i] = new Thread(b);
}
for (Thread thread : threads) {
  thread.start();
}

위의 예가 적당한예인지는.. 아무튼 보자. 우리는 flag 변수에 1개의 배열을 할당하고 익명클래스 안에서 변경을 하고 있다. 그리고 flag[0] == 0 일때는 1로 바꾸고 어떠한 작업(시간이 걸리는)을 한다고 하고 flag에 첫번째 배열을 출력 해보았다. 만약 스레드 세이프 하다면 모두 1로 출력 되어야 정상이다. 하지만 결과를 그렇지않다. 1도 있고 0도 있고 뒤죽박죽이다.
스레드 세이프 하지 않다는 것이다.!

Tags:

Categories:

Updated:

Leave a comment