Java, final 변수 또는 멤버 필드

바뀌지 않는 결정적인, 끝이라는 의미를 담긴 final 키워드가 있다.
변수에 final 키워드를 붙이면 값을 변경할 수 없다.

변수 뿐 아니라 final 키워드는 자바 class, method 포함한 여러 곳에서 붙일 수 있다.
변수에 붙이는 final 키워드에 대해 알아본다.


지역 변수 - final

코드로 final 사용 예제를 살펴본다.

FinalMain.java 예제 1

  • 변수 decision 선언
  • decision 값 할당은 최초 한번만 가능
package finalLocal;

public class FinalMain {
    public static void main(String[] args) {
        final int decision;
        decision = 10; // 최초 한 번만 할당 가능하다.
        // decision = 20; // 컴파일 오류, 이미 할당된 데이터로 변경할 수 없다.
    }
}

FinalMain.java 예제2

  • 변수 초기 값 지정 후 선언
  • 초기 값이 지정 된 final 데이터은 변경 불가능
package finalLocal;

public class FinalMain {
    public static void main(String[] args) {
        final int decision = 10; // 최초 한 번만 할당 가능하다.
        // decision = 20; // 컴파일 오류, 이미 할당된 데이터로 변경할 수 없다.
    }
}

FinalMain.java 예제3

  • 메소드 decisionMethod 작성 후 파라미터로 final 할당
  • main() 에서 메소드 호출 시 인수 값 전달 가능(decisionMethod(10)).
  • 메소드 내부에서 할당 된 지역변수 final 값은 변경 불가능
  • 메소드 내부에서 호출에서 전달 받은 값은 메소드 종료까지 사용 가능
package finalLocal;

public class FinalMain {
    public static void main(String[] args) {
        decisionMethod(10);
    }
    
    static void decisionMethod(final int data) {
        // data = 10; // 컴파일 오류
    }
}

클래스 필드(멤버 변수) - final

Init 클래스를 작성하여 필드 (멤버 변수)에서 사용하는 final 에 대해서 알아보도록 한다.

Init.java 예제 - final

  • 멤버 변수 final 키워드 붙인 value 선언 후
  • 생성자를 통해서 값을 할당
package finalLocal;

public class Init {
    final int value; // 생성자 통해서 할당한다.

    public Init(int value) {
        this.value = value;
    }
}

Init.java 예제 - static final 상수

  • 멤버 변수 상수로 static final 사용 가능
package finalLocal;

public class Init {
    static final int CONST_VALUE = 10000;
    final int value = 10000;

    public Init(int value) {
//        CONST_VALUE = value; // 컴파일 오류, final 변수로 이미 할당되어있음
//        this.value = value; // 컴파일 오류, final 변수로 이미 할당되어있음
    }
}

final 사용하기

클래스를 생성하여 사용해보도록 한다.

Init.java

package finalLocal;

public class Init {
    final int value;

    public Init(int value) {
        this.value = value;
    }
}

Final.java

package finalLocal;

public class Field {
    final static int CONST_VALUE = 100;
    final int value = 10000;
}

Main.java

package finalLocal;

public class FinalMain {
    public static void main(String[] args) {
        // Init 초기화
        System.out.println("Init 생성자 초기화");
        Init construct1 = new Init(365);
        Init construct2 = new Init(1000);
        System.out.println(construct1.value);
        System.out.println(construct2.value);

        // Field 필드 초기화
        System.out.println("Field 필드 초기화");
        Field field1 = new Field();
        Field field2 = new Field();
        Field field3 = new Field();
        System.out.println(field1.value);
        System.out.println(field2.value);
        System.out.println(field3.value);

        // 상수
        System.out.println("상수");
        System.out.println(Field.CONST_VALUE);
    }
}

출력 결과

Init 생성자 초기화
365
1000
Field 필드 초기화
10000
10000
10000
상수
100

생성자 초기화

construct1 생성자를 사용하여 field 초기화 하는 방법으로 각 인스턴스마다 각기 다른 값을 할당하였다. 할당된 final 은 다른 값으로 이제 변경이 불가하다.

힙 영역에는 value 의 값 365 숫자와 1000 담겨 있을 것이다.

필드 초기화

Field 클래스는 선언과 동시에 초기 값을 넣은 값이다.
인스턴스를 생성한 경우 이 멤버 변수는 10000 값이 고정되어 있다.

생성자 초기화와 다르게 필드 초기화는 해당 값을 미리 정해져 있다.
모든 인스턴스가 같은 값을 사용하고 결과적으로 메모리 낭비하게 된다. (내부적으로 최적화가 되어있는 JVM 도 있다.) 개발자 입장에서는 명확한 중복 데이터이고 메모리 낭비이기에 static 영역으로 옮기면 메모리를 아낄 수 있다.

static final

  • Field.CONST_VALUE 는 메소드 영역에 속한 static 영역에 있다. final 키워드를 사용한 초기화 값은 변화지 않는다.
  • static 선언 시 인스턴스와 무관하게 하나만 존재한다. 앞서 말한 중복과 메모리 낭비를 해결할 수 있다.

불필요한 메모리 낭비를 피하고 데이터 공유가 필요한 경우,
final + 필드 초기화 선언하고 static 키워드를 붙여주는 것이 효과적이다.