final
- 김영한님의 실전 자바 강의 중 final 챕터를 학습하며 final 키워드의 목적, 변경 불가능성의 필요성, 그리고 지역 변수, 필드, 상수에서의 사용법을 정리함
final 키워드의 목적
변경 불가능성이 왜 필요한가
-
문제 상황
- 프로그램에서 특정 값이 변경되면 버그가 발생하는 경우가 많음
- 개발자의 실수로 중요한 값이 변경될 수 있음
- 여러 곳에서 공유하는 값이 예기치 않게 변경될 수 있음
-
해결책
- 값의 변경을 원천적으로 막아야 함
- 컴파일 시점에 변경을 감지하여 오류를 발생시켜야 함
- 코드의 의도를 명확히 전달해야 함
final 키워드란
final키워드는 변경 불가능을 의미함final이 붙은 변수는 최초 한 번만 값을 할당할 수 있음-
이후 값을 변경하려고 하면 컴파일 오류가 발생함
1 2
final int value = 10; // value = 20; // 컴파일 오류 발생
final의 적용 대상
-
변수
- 지역 변수와 매개변수: 값 재할당 방지
- 인스턴스 필드: 객체별 불변 값 생성
- 클래스(static) 필드: 전역 상수 선언
-
메서드
- 오버라이딩을 방지함
-
클래스
- 상속을 방지함
final 지역 변수
지역 변수에 final 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package final1;
public class FinalLocalMain {
public static void main(String[] args) {
// final 지역 변수는 선언과 초기화를 분리 가능함
final int data1;
data1 = 10; // 최초 한 번만 할당 가능함
// data1 = 20; // 컴파일 오류 발생
// final 지역 변수는 선언과 동시에 초기화 가능함
final int data2 = 10;
// data2 = 20; // 컴파일 오류 발생
method(10);
}
// final 매개변수
static void method(final int parameter) {
// parameter = 20; // 컴파일 오류 발생
}
}
- 규칙
final변수는 최초 한 번만 할당 가능함- 선언과 초기화를 분리할 수 있지만, 최초 할당 이후 값 변경 불가함
final매개변수는 메서드 내에서 값을 변경할 수 없음
final 필드
생성자를 통한 초기화
1
2
3
4
5
6
7
8
9
package final1;
public class Settings {
final int threshold;
public Settings(int threshold) {
this.threshold = threshold;
}
}
- 생성자에서
final필드를 초기화함 - 각 인스턴스마다 서로 다른
final값을 가질 수 있음
필드 선언 시 초기화
1
2
3
4
5
6
package final1;
public class SharedSettings {
final int limit = 10; // 인스턴스 필드
static final int MAX_LIMIT = 10; // 클래스 필드
}
-
인스턴스 final 필드 (
final int limit)- 모든 인스턴스가 동일한 값
10을 가짐 - 각 인스턴스마다 별도의 메모리 공간을 차지함
- 객체가 100개면 동일한 값
10이 100번 저장됨 (메모리 낭비)
- 모든 인스턴스가 동일한 값
-
클래스 final 필드 (
static final int MAX_LIMIT)- 메서드 영역(static 영역)에 단 1개만 존재함
- 모든 인스턴스가 이 하나의 값을 공유함
- 메모리 효율적이며, 전역 상수로 사용하기 적합함
메모리 비교(객체 1000개 생성 시)
final int limit = 10은 4KB 사용 (4byte × 1000)static final int MAX_LIMIT = 10은 4byte만 사용
초기화 방식 비교
| 초기화 방식 | 메모리 위치 | 특징 | 사용 시기 |
|---|---|---|---|
| 생성자 초기화 | 힙 (각 인스턴스) | 객체마다 다른 값 가능 | 객체별로 다른 final 값 필요 시 |
| 필드 초기화 | 힙 (각 인스턴스) | 모든 객체가 같은 값 공유 | 비효율적, static 사용 권장 |
| static final | 메서드 영역 (단 1개) | 전역 공유 | 상수 정의에 적합 |
-
생성자 초기화 - 객체별 다른 값
1 2 3 4
Settings s1 = new Settings(10); Settings s2 = new Settings(20); System.out.println(s1.threshold); // 10 System.out.println(s2.threshold); // 20
- 사용 사례
- 주문 번호, 회원 ID처럼 객체마다 달라야 하는 불변 값
- 사용 사례
-
필드 초기화 - 모든 객체가 같은 값
1 2 3 4
SharedSettings s1 = new SharedSettings(); SharedSettings s2 = new SharedSettings(); System.out.println(s1.limit); // 10 System.out.println(s2.limit); // 10 (같은 값인데 메모리는 따로)
- 문제점
- 모든 객체가 같은 값인데 각 객체마다 메모리를 차지함
- 해결책
static final을 사용함
- 문제점
-
static final - 상수
1
System.out.println(SharedSettings.MAX_LIMIT); // 10 (객체 생성 없이 접근)
메모리 구조 비교

- Settings
- 인스턴스별로 다른
final값을 가질 수 있어 의미가 있음
- 인스턴스별로 다른
- SharedSettings
- 모든 인스턴스가 같은 값(10)을 가지면서 메모리를 중복해서 사용함
- static final
- 메서드 영역에 하나만 존재하므로 가장 효율적임
static final로 상수 정의
-
상수란
- 프로그램에서 절대 변하지 않는 공용 값을 의미함
- 자바에서는
static final키워드를 사용하여 정의함
-
상수 명명 관례
- 대문자로 작성하고, 단어 사이는
_(언더스코어)로 구분함 - 일반 변수(소문자 시작)와 명확히 구분하기 위함
1 2 3 4 5 6 7 8 9
package final1; public class GameConfig { // 게임 설정 상수 public static final int MAX_PLAYERS = 4; // 수학 상수 public static final double PI = 3.14; }
- 대문자로 작성하고, 단어 사이는
상수를 사용해야 하는 이유
-
문제점
- 가독성 저하
- 코드에 있는 숫자가 정확히 어떤 용도인지 파악하기 힘듦
- 유지보수 어려움
- 해당 숫자가 코드 여러 곳에 퍼져 있다면, 값을 변경할 때 모든 곳을 찾아서 수정해야 함
- 하나라도 빠뜨리면 버그가 발생할 수 있음
- 가독성 저하
-
해결책
- 상수를 사용하여 숫자에 의미 있는 이름을 부여하고 한 곳에서 관리해야 함
상수 사용 전후 비교
-
상수 사용 전
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package final1; public class GameMain { public static void main(String[] args) { System.out.println("최대 플레이어 수: " + 4); int currentPlayerCount = 3; joinGame(currentPlayerCount++); joinGame(currentPlayerCount++); joinGame(currentPlayerCount++); } private static void joinGame(int currentPlayerCount) { System.out.println("참가자 수: " + currentPlayerCount); if (currentPlayerCount > 4) { System.out.println("대기열로 이동합니다."); } else { System.out.println("게임에 참가합니다."); } } }
-
상수 사용 후
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package final1; public class GameMainRefactored { public static void main(String[] args) { System.out.println("최대 플레이어 수: " + GameConfig.MAX_PLAYERS); int currentPlayerCount = 3; joinGame(currentPlayerCount++); joinGame(currentPlayerCount++); joinGame(currentPlayerCount++); } private static void joinGame(int currentPlayerCount) { System.out.println("참가자 수: " + currentPlayerCount); if (currentPlayerCount > GameConfig.MAX_PLAYERS) { System.out.println("대기열로 이동합니다."); } else { System.out.println("게임에 참가합니다."); } } }
-
가독성과 유지보수성이 모두 향상됨
final 변수와 참조
참조형 변수에 final 적용
1
2
3
4
5
package final1;
public class Data {
public int value;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package final1;
public class FinalRefMain {
public static void main(String[] args) {
final Data data = new Data();
// data = new Data(); // final 변수이므로 참조 대상 변경 불가
// 참조 대상의 값은 변경 가능함
data.value = 10;
System.out.println(data.value); // 10
data.value = 20;
System.out.println(data.value); // 20
}
}
메모리 구조와 final 참조

-
가능 (
data.value = 20)- 참조하는 대상의 객체 내부 값(
value)은 변경할 수 있음 final은 참조 변수data의 값을 고정하는 것이지, 힙 영역에 있는 객체 자체를 불변으로 만드는 것이 아님
- 참조하는 대상의 객체 내부 값(
-
불가 (
data = new Data())- 참조값(주소) 자체를 변경할 수 없음
- 한 번 가리킨 객체(x001)를 끝까지 가리켜야 함
final 사용 가이드
언제 사용하는가
- 값이 변경되면 안 되는 경우
- 중앙에서 값을 관리하고 싶은 경우
- 코드의 가독성과 안정성을 높이고 싶은 경우
장점
- 코드 안정성 증가 (실수로 값 변경 방지)
- 의도가 명확해짐 (이 값은 변경되지 않는다는 의미)
- 유지보수성 향상 (상수를 한 곳에서 관리)
주의사항
final은 변수 자체의 재할당을 막는 것임- 참조형의 경우 참조 대상 객체의 내부 값은 변경 가능함
- 완전한 불변 객체를 만들려면 모든 필드를
final로 선언하고 setter를 제공하지 않아야 함
연습 문제
-
final 키워드를 변수에 사용하면 어떤 것이 제한될까요?
-
a. 값의 변경
-
final은 변수에 값이 한 번 할당되면 그 값을 다시 변경할 수 없도록 제한하는 키워드임
-
-
참조 변수에 final 키워드를 붙이면 무엇이 고정될까요?
-
a. 참조 변수가 가리키는 주소값
- 참조 변수에 final을 쓰면 변수가 가리키는 주소, 즉 다른 객체를 참조하도록 바꿀 수 없게 됨
- 객체 자체의 속성 값은 변경 가능함
-
-
Java에서 static final로 선언된 변수를 무엇이라고 부를까요?
-
a. 상수
- static은 공유, final은 변경 불가 속성임
- 이 둘을 함께 써서 프로그램 내내 바뀌지 않고 공유되는 ‘상수’를 정의함
-
-
MAX_USERS처럼 프로그램 전반에 걸쳐 사용되는 고정된 값에 static final을 사용하는 주된 이유는 무엇일까요?
-
a. 값의 중앙 집중식 관리와 가독성 향상
-
static final 상수는 값을 한 곳에서 관리하여 코드 이해를 돕고, 필요시 값 변경을 중앙에서 쉽게 할 수 있게 해줌
-
-
변수에 final 키워드를 사용했을 때 얻을 수 있는 중요한 이점은 무엇일까요?
-
a. 개발자의 실수를 통한 값 변경 방지
-
final은 해당 변수의 값이 절대 바뀌지 않음을 보장하여, 개발자가 의도치 않게 값을 변경하는 실수를 컴파일 단계에서 미리 막아주는 이점이 있음
-
요약 정리
final의 목적
- 변경 불가능성을 통한 안정성 확보
- 개발자 실수 방지
- 코드 의도 명확화
final 사용 목적별 분류
| 분류 | 키워드 적용 위치 | 설명 |
|---|---|---|
| 재할당 방지 | 기본형 변수 | 값을 한 번만 할당 가능, 이후 변경 불가 |
| 참조 고정 | 참조형 변수 | 참조하는 객체(주소)를 변경 불가, 객체 내부 상태는 변경 가능 |
| 불변 필드 | 인스턴스 필드 | 생성자 초기화 이후 값 변경 불가 (개별 객체마다 다른 값 가능) |
| 상수 | static 필드 | static final, 전역적으로 공유되는 불변의 값 |
| 확장과 변경 금지 | 메서드와 클래스 | 오버라이딩 불가(메서드), 상속 불가(클래스) |