final 이란?
이름 그대로 ‘끝’ 이라는 뜻이다.
변수에 final 키워드가 붙으면 더 이상 값을 변경할 수 없다.
final 지역변수
1
2
3
4
5
6
7
| final int number;
number = 10; // 최초 한 번은 할당 가능
// number = 20; // 컴파일 오류: 이미 값을 할당했기 때문
//선언과 동시 초기화
fianl int number2 = 20;
// number2 = 30; // 컴파일 오류: 이미 값을 할당했기 때문
|
final
이 붙은 지역 변수는 최초 한 번만 값을 할당할 수 있습니다.
- 한 번 값을 할당한 이후에는 다시 값을 변경할 수 없습니다.
- 선언과 동시에 초기화 하면 이미 값이 정해진 상태이기 때문에 이후 재 할당 불가
final 매개변수
1
2
3
4
| static void print(final int number) {
// number = 20; // 컴파일 오류
System.out.println(number);
}
|
final
이 붙은 매개변수는 메서드 내부에서 값을 변경할 수 없다. 따라서 메서드 호출 시점에 사용된 값이 끝까지 사용
final 필드(맴버 변수)
JAVA에서 final
키워드를 필드(맴버 변수)에 사용하면 값 변경이 불가능한 필드를 만들 수 있다.
그런데, 어디서 값을 초기화하느냐에 따라 사용방식이 달라지며 의미도 달라진다.
생성자를 통한 final
필드 초기화
1
2
3
4
5
6
7
| public class ConstructInit {
final int value;
public ConstructInit(int value) {
this.value = value; // 생성자를 통한 초기화
}
}
|
1
2
3
4
| ConstructInit c1 = new ConstructInit(10);
ConstructInit c2 = new ConstructInit(20);
System.out.println(c1.value); // 10
System.out.println(c2.value); // 20
|
final
필드는 생성자를 통해 단 한 번만 값을 초기화할 수 있음
- 생성자에서 값을 정하기 때문에, 인스턴스마다 다른 값을 가질 수 있음
- 이후에는 값을 변경할 수 없음
필드에서 바로 초기화
1
2
3
4
5
6
7
8
9
| public class FieldInit {
final int value = 10;
/*public FieldInit(int value) {
//초기값이 이미 들어가 있으면 생성자로 못넣음
this.value = value;
//final은 값이 한번 세팅되면 끝
}*/
}
|
1
2
3
4
| FieldInit f1 = new FieldInit();
FieldInit f2 = new FieldInit();
System.out.println(f1.value); // 10
System.out.println(f2.value); // 10
|
- 여러 인스턴스를 생성하더라도
value
는 항상 동일한 10
- 이 경우, 불필요한 중복 메모리 사용이 발생할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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);
//funal 필드 - 필드 초기화
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);
}
}
생성자 초기화
10
20
필드 초기화
10
10
10
|
ConstructInit
은 생성자로 final
필드 초기화함 → 인스턴스마다 값 다름
FieldInit
은 필드에서 직접 초기화함 → 인스턴스 모두 같은 값 가짐
필드 초기화 vs 생성자 초기화
- 생성자 초기화: 인스턴스마다 다른 값 할당 가능함. 유연성 높음
- 필드 초기화: 인스턴스마다 같은 값 반복 생성됨 → 중복 + 메모리 낭비
- JVM이 내부 최적화할 수도 있지만, 명시적으로 static 쓰는 게 좋음
- static final 상수: 클래스 로딩 시 한 번만 생성됨 → 효율적, 중복 없음
✅
필드 초기화는 인스턴스마다 같은 값을 계속 생성하므로, 메모리 낭비를 줄이고자 할 경우 static
을 붙이는 것이 좋음
static final 상수
1
2
3
| public class FieldInit {
static final int CONST_VALUE = 10;
}
|
static
+ final
조합은 클래스가 공유하는 상수를 의미
- 모든 인스턴스가 아니라 클래스 로딩 시점에 딱 한 번 초기화되며, 공유
- 메모리 낭비 없이, 코드의 명확성을 높임
- 상수이므로 대문자 + 언더스코어( _ )로 변수명을 짓는 것이 자바 관례
왜 static final을 쓰는지?
매직 넘버(Magic Number)란? 코드 안에 의미 없는 숫자(또는 문자)를 직접 써놓는 걸 말함.
이 숫자가 무슨 의미인지 코드만 봐선 알기 어렵고 여러 군데에서 쓰이면 변경 시 실수 위험도 큼.
1
| if (currentUserCount > 1000) { ... } // ← 1000이 뭘 의미하는지 모름
|
이럴 때 static final
상수를 사용하면 코드가 더 명확해짐:
1
| if (currentUserCount > Constant.MAX_USERS) { ... }
|
MAX_USERS
라는 이름 덕분에 의미 명확해짐
- 같은 값이 반복되더라도 한 곳만 바꾸면 전체 반영됨 → 유지보수 편함
- 실무에서는 설정값, 제한값, 기준값 등은 거의 다
public static final
상수로 관리함
- 같은 값이 반복된다면
final
만 쓰지 말고, static final
로 상수화하는 것이 좋음
final 변수와 참조형 타입
기본형 변수에서의 final
1
2
| final int number = 10;
// number = 20; // 컴파일 오류
|
final
이 붙은 기본형 변수는 값 자체를 변경할 수 없음
- → 10이란 값을 고정시킨 것
참조형 변수에서의 final
1
2
3
4
| final Data data = new Data();
// data = new Data(); // 참조값 변경 불가
data.value = 10; // 객체 내부 값은 변경 가능
data.value = 20; // 객체 내부 값은 변경 가능
|
final
이 붙은 참조형 변수는 객체의 참조값 자체는 변경 불가
- 하지만 참조 대상 객체의 내부 값은 변경 가능
- 즉, 다른 객체를 가리키게는 못하지만, 현재 가리키는 객체의 필드는 수정할 수 있음
✅
data는 Data 객체의 주소를 고정한 것. 하지만 data.value 는 final
이 아니기 때문에 자유롭게 바꿀 수 있음!
실무에서의 예시
불변 id 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Member {
private final String id;
private String name;
public Member(String id, String name) {
this.id = id;
this.name = name;
}
public void changeData(String id, String name) {
// this.id = id; // 컴파일 오류
this.name = name; // 가능
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| public class MemberMain {
public static void main(String[] args) {
Member member = new Member("myId", "kim");
member.print();
member.changeData("myId2", "seo");
member.print();
}
}
id:myId,name:kim
id:myId,name:seo
|
id
는 final
이기 때문에 생성자에서만 한 번 할당 가능
name
은 변경 가능
- 이렇게 하면 중요한 식별자(id)가 실수로 변경되는 걸 방지할 수 있음
구분 |
final 의미 |
기본형 변수 |
값 자체를 변경할 수 없음 |
참조형 변수 |
참조 주소 변경 불가 (다른 객체로 바꿀 수 없음) |
하지만 내부 필드는 변경 가능 |
|
실무 활용 |
중요한 값(id 등)은 final 로 선언해서 불변성 보장하고, 컴파일 타임에 실수 차단 가능 |
✅
final
은 단순한 키워드가 아니라 **의도를 명확히 표현하고, 실수를 줄이는 중요한 설계 도구**
출처: 김영한의 실전 자바 - 기본편