본문 바로가기

Java/Java SE, EE

[Java/Java SE, EE] 상수(static final) 키워드, final은 상수가 아닌 이유

static final

수학적으로 또는 프로그램에서의 상수(Constant)는 값이 변하지 않는(Immutable) 성격을 갖고 있습니다. 프로그램에서 상수를 정의할 때는 static final 키워드 두 개를 함께 사용합니다.

[접근 지정자 public|protected|private|default] final static [타입] [변수 이름] = [초기 값];

static과 final 키워드의 순서는 중요하지 않으며, 두 개의 키워드가 함께 사용되기만 하면 됩니다.

[접근 지정자 public|protected|private|default] static final [타입] [변수 이름] = [초기 값];

상수를 정의하면, 이를 사용하기 위한 접근 방법이 필요합니다. 따라서 프로그램에서의 상수는 변수에 저장되며, 이 변수에 불변성의 성질을 부여함으로써 상수로써의 역할을 수행하도록 합니다.

이때 static 키워드는 변수의 접근을 프로그램 내부에서 자유롭게 하기 위해 사용합니다. static 키워드로 선언된 필드는 클래스 로더(Class loader)에 의해서 메소드 영역(Method area) 메모리에 올라갑니다. 메소드 영역은 모든 스레드에 걸쳐 동일한 메모리를 공유합니다. 이를 공유 메모리 영역 또는 클래스 영역이라고도 부릅니다. 

final 키워드는 변수의 값을 변경하지 못하도록, Immuatable한 성격을 부여합니다. final 키워드에 의해 선언된 변수는 선언과 동시에, 또는 생성자가 호출되는 시점에 값을 초기합니다. 이 값은 한 번 결정되면 더 이상 수정할 수 없습니다.

final과의 차이점, final은 상수가 아닌 이유

그렇다면 final 키워드가 이미 존재함에도 static final 키워드를 사용하는 이유는 무엇일까요? 이어지는 내용에서 소개하겠지만, 이는 final 키워드에 의해 선언된 변수의 초기화 방법에 힌트가 있습니다.

final 필드는 생성자 내부에서 값을 초기화 할 수 있습니다. 예를 들면, 어떤 클래스 A는 다음과 같이 생성자를 통해 인자로 전달된 값을 사용하여 final 필드 a를 초기화 할 수 있습니다.

public static class A {
   public final int a;
   
   public A(int _a) {
      a = _a;
   }
}

이처럼 final 필드는 불변성의 성질은 갖고 있지만, 객체를 초기화하는 단계에서 값이 결정되는 동적(Dynamic)인 성질 또한 갖고 있습니다. 우리가 일상적으로 표현하는 상수 중 하나인 원주율과 같은 값이 동적으로 할당되어야 할 필요가 있을까요?

A a_of_10 = new A(10);
A a_of_20 = new A(20);

반면, static 키워드를 final 키워드와 함께 사용하면 클래스가 처음 실행될 때 클래스 로더에 의해 static final 필드의 값을 초기화하게 됩니다. 따라서 final 키워드의 Immutable 성질과 static 키워드의 정적(Static) 성질을 동시에 갖게 됩니다. 먼저 소개한 클래스 A에 대한 코드를 다음과 같이 수정해보겠습니다.

public static class A {
   public static final int a;
   
   public A(int _a) {
      a = _a;
   }
}

이전과 달리, 이 코드는 컴파일 에러를 발생시킵니다. 기존 final 필드 a는 static 키워드로 인해 생성자에서 초기화가 불가능하기 때문입니다. 정리하면 static final 필드는 클래스에 대한 참조가 발생하는 순간 클래스 로더에 의해 메모리에 로드되며(static), 불변성의 성질로 인해 메모리에 로드되는 동시에 값의 초기화(final)를 강제합니다. 이 두 개의 키워드의 조합이 상수를 선언할 수 있는 이유입니다.

static final 초기화 방법

상수는 관례적으로 대문자만을 사용하여 이름을 결정합니다. 만약 여러 단어의 조합을 사용해야 한다면 언더바(_) 문자를 사용하여 두 단어를 연결합니다.

상수를 초기화하는 가장 일반적인 방법은, 상수의 초기화와 동시에 값을 결정하는 것입니다.

public final static int SOME_CONSTANT = 10;

 

상수를 초기화하기 위한 다른 방법은 정적 블록(Static block)을 사용하는 것입니다. 정적 블록 또한 클래스 로더에 의해 실행되며, 클래스가 메소드 영역에 로드될 때 실행됩니다. 정적 블록을 사용하는 이유는 상수의 초기화에 복잡한 연산이 포함되거나, 초기화 구문이 길어지는 경우 가독성을 위함입니다.

public final static int SOME_CONSTANT;

static {
   SOME_CONSTANT = 10 * 34 / 8 + 44 % 51 - 6;
}