final인데 값이 바뀔수도, 안바뀔수도

이세민·2024년 12월 16일
post-thumbnail

Final

java에서 상수를 선언하기 위해 final을 통해 변수를 선언한다.

final이 사용된 변수는 초기화 된 후에 다시 값을 바꿀 수 없다.

???

하지만 Reflection을 사용하면 강제적으로 final의 값을 "바꿀 수도" 있다.

import java.lang.reflect.Field;

public class ConstantValues {

    final int fieldInit = 42;
    final int instanceInit;
    final int constructorInit;

    {
        instanceInit = 42;
    }

    public ConstantValues() {
        constructorInit = 42;
    }

    static void set(ConstantValues p, String field) throws Exception {
        Field f = ConstantValues.class.getDeclaredField(field);
        f.setAccessible(true);
        f.setInt(p, 9000);
    }

    public static void main(String... args) throws Exception {
        ConstantValues p = new ConstantValues();

        set(p, "fieldInit");
        set(p, "instanceInit");
        set(p, "constructorInit");

        System.out.println(p.fieldInit + " " + p.instanceInit + " " + p.constructorInit);
    }

}

ConstantValues에는 3가지 방식으로 초기화 되는 3개의 final 변수가 있다. setAccessible(true)를 통해 final 변수에 대한 접근 제한을 해제하고 필드의 값을 9000으로 설정하는 코드이다.
위 코드를 실행하면 실행 결과는 42 9000 9000으로, fieldInit의 값만 바뀌지 않은것을 알 수 있다.

외않되

컴파일한 바이트 코드를 intelliJ의 디컴파일러 또는 javap를 통해 클래스 파일의 내용을 확인해보면, 그 이유를 알 수 있다.

  final int fieldInit;
    descriptor: I
    flags: (0x0010) ACC_FINAL
    ConstantValue: int 42

  final int instanceInit;
    descriptor: I
    flags: (0x0010) ACC_FINAL

  final int constructorInit;
    descriptor: I
    flags: (0x0010) ACC_FINAL

  중략...
  (print 부분)
	33: pop
    34: bipush        42
    36: aload_1
    37: getfield      #13                 // Field instanceInit:I
    40: aload_1
    41: getfield      #16                 // Field constructorInit:I

fieldInit은 ConstantValue를 통해 미리 값이 들어가 있을 뿐만 아니라 사용되는 자리에 42라는 값 자체로 하드코딩되어 있는 모습을 볼 수 있다. Reflection으로 값을 바꿔도 출력이 바뀌지 않은 이유는 저렇게 42라는 값 자체가 바이트 코드에 들어있어서 반영되지 않았던것이다.

intellj의 디컴파일러를 통하면, 42가 그대로 들어가 있는걸 보고 더 쉽게 알 수 있다.

쓸모가 있나

위 예시처럼 final 필드의 값을 "컴파일 단계에서 알 수 있도록" 넣어주면 변수를 참조하는 대신 그 값 자체를 대입하여 컴파일 되기 때문에, 성능면에서 약간의 이득을 볼 수 있다.

final Example example = new Example(); 처럼 객체를 필드로 가지는 경우엔 컴파일 단계에서 Example 인스턴스의 메모리 위치를 알 수 없으므로 저러한 최적화가 일어나지 않는다.

profile
gsm 8기 고등학생

0개의 댓글