Java - final
final 변수와 상수1
변수에 final 키워드가 붙으면 한번 설정한 후 더는 값을 변경할 수 없습니다.
public class FinalLocalMain {
public static void main(String[] args) {
//final 지역 변수1
final int data1;
data1 = 10; // 최초 한번만 할당 가능
// data1 = 20; // 컴파일 오류
//final 지역 변수2
final int data2 = 10;
// data2 = 20; // 컴파일 오류
}
static void method(final int parameter) {
// parameter = 20; 컴파일 오류
}
}
final - 필드(멤버 변수)
final 필드를 필드에서 초기화하면 이미 값을 설정했기 때문에 생성자를 통해서도 초기화 할 수 없습니다. static 키워드와 함께 사용하면 상수가 되고 대문자와 _(언더스코어)를 사용해 이름을 짓는 관례가 있습니다.
public class FieldInit {
// static final 이 붙으면 대문자로 이름을 짓는 관례가 있음.
static final int CONST_VALUE = 10;
final int value = 10;
}
final - 생성자 초기화
final 키워드가 붙은 변수는 한번 초기화가 되고 나서 불변의 상태가 되기 때문에 생성자를 통해서 한번 값을 초기화 할 수 있습니다.
public class ConstructInit {
final int value;
public ConstructInit(int value) {
this.value = value;
}
}
필드 vs 생성자 초기화
public class FinalFieldMain {
public static void main(String[] args) {
//final 필드 - 생성자 초기화
System.out.println("생성자 초기화");
ConstructInit constructInit1 = new ConstructInit(10);
ConstructInit constructInit2 = new ConstructInit(20);
System.out.println(constructInit1.value);
System.out.println(constructInit2.value);
//final 필드 - 필드 초기화
System.out.println("필드 초기화");
FieldInit fieldInit1 = new FieldInit();
FieldInit fieldInit2 = new FieldInit();
FieldInit fieldInit3 = new FieldInit();
System.out.println(fieldInit1.value);
System.out.println(fieldInit2.value);
System.out.println(fieldInit3.value);
//상수
System.out.println("상수");
System.out.println(FieldInit.CONST_VALUE);
}
}
생성자 초기화는 생성자를 통해서 값을 설정할 수 있지만 필드 초기화는 값이 생성과 동시에 똑같은 값이 고정되어 있기 때문에 메모리 낭비가 발생할 수 있습니다.(중복 발생) 아래의 사진처럼 메모리 낭비가 발생하는 부분은 static 키워드와 final 키워드를 조합해 상수로 만들어 해결할 수 있습니다.

final 변수와 상수2
static과 final 키워드를 조합해 상수로 만들면 메모리 절약과 매직 넘버를 제거할 수 있는 이점이 있습니다. 아래의 코드에서 숫자 1000이 무엇을 의미하는지 파악하기 힘든데 상수로 의미있는 이름을 지어준다면 의도를 명확하게 알 수 있고 코드를 재사용할 수 있습니다.
public class ConstantMain1 {
public static void main(String[] args) {
System.out.println("프로그램 최대 참여자 수 " + 1000);
int currentUserCount = 999;
process(currentUserCount++);
process(currentUserCount++);
process(currentUserCount++);
process(currentUserCount++);
}
private static void process(int currentUserCount) {
System.out.println("참여자 수 : " + currentUserCount);
if (currentUserCount > 1000) { // 1000이 뭐지? (벙찌게 만듬) -> 매직넘버(나를 깜짝 놀래키는 숫자)
System.out.println("대기자로 등록합니다.");
} else {
System.out.println("게임에 참여합니다.");
}
}
}
final 변수와 참조
자바는 기본형 변수와 참조형 변수가 있습니다. 기본형 변수는 10, 20같은 값을 저장하고 참조형 변수는 객체의 참조값을 저장합니다. 아래는 살짝 복잡한 예제입니다.
public class Data {
public int value;
}
public class FinalRefMain {
public static void main(String[] args) {
final Data data = new Data();
// Data data = new Data(); 컴파일 에러
//참조 대상의 값은 변경 가능
data.value = 10;
System.out.println(data.value);
data.value = 20;
System.out.println(data.value);
}
}
참조형 변수에 final이 붙었기 때문에 참조값을 다른 값으로 변경하지 못합니다. 하지만 Date 인스턴스에 있는 value는 final이 아니기 때문에 변경이 가능합니다.

아래는 KEEPER R2 프로젝트에서 사용된 코드의 일부분 입니다. 위의 final 변수와 참조를 잘 이해하셨다면 아래의 코드를 보더라도 헷갈리지 않을 것 입니다.
@OneToMany(mappedBy = "member", cascade = REMOVE)
private final List<ElectionCandidate> electionCandidates = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = REMOVE)
private final List<ElectionVoter> electionVoters = new ArrayList<>();