Java의 final과 static: '불변'과 '공유'의 미학 (feat. JVM 메모리 구조)
final
과 static
. 두 키워드는 문법의 가장 기초적인 부분이지만, 그 역할과 동작 원리를 깊이 이해하는 것은 견고한 코드를 작성하는 핵심입니다.
final
은 **"한 번만 할당할 수 있다"**는 불변의 약속입니다.static
은 **"인스턴스가 아닌 클래스에 속한다"**는 공유의 개념입니다.
이 글에서는 final
과 static
의 각기 다른 역할을 코드와 함께 살펴보고, 더 나아가 이들이 JVM 메모리의 어느 공간에 위치하며 동작하는지 심층적으로 알아보겠습니다.
1. final
- "변경될 수 없는 마지막 값"
final
키워드는 "최종적인", "변경될 수 없는" 이라는 의미를 가집니다. 변수, 메소드, 클래스에 붙어 각기 다른 제약을 부여합니다.
final
의 세 가지 얼굴
final
필드: **상수(Constant)**가 되는 변수.- 선언 시 또는 생성자에서 단 한 번만 초기화할 수 있습니다.
- 일단 값이 할당되면, 그 이후로는 절대 변경할 수 없습니다.
final
메소드: 오버라이딩(Overriding)이 금지된 메소드.- 자식 클래스에서 이 메소드를 재정의할 수 없습니다.
- 메소드의 핵심적인 구현이 변경되는 것을 막고 싶을 때 사용합니다.
final
매개변수: 메소드 내에서 재할당이 금지된 매개변수.- 메소드 안으로 전달된 매개변수 변수에 다른 값을 다시 할당할 수 없습니다.
코드로 살펴보기
아래 코드는 final
키워드가 어떻게 동작하는지 보여줍니다.
public class FinalExam {
// (1) final 필드: 선언과 동시에 초기화
private final String message = "Final Message";
public FinalExam() {
// (2) 컴파일 오류! final 필드는 이미 초기화되었으므로 또 할당할 수 없다.
// this.message = "Final Message~";
// -> Error: Variable 'message' might already have been assigned
}
// (3) final 메소드: 이 메소드는 하위 클래스에서 오버라이딩 불가
public final void showMessage() {
System.out.println(message);
}
// (4) final 매개변수: 메소드 내에서 재할당 불가
public void showMessage(final String message) {
// 컴파일 오류! final 매개변수의 값을 변경할 수 없다.
// message = "New Message!";
// -> Error: Cannot assign a value to final variable 'message'
System.out.println(message);
}
}
2. static
- "인스턴스를 초월한 단 하나의 공유지"
static
키워드는 "정적인"이라는 의미로, 객체(인스턴스)가 아닌 클래스에 소속되도록 만듭니다. static
멤버는 해당 클래스의 모든 인스턴스가 공유하는 단 하나의 공간입니다.
마치 한 학급의 모든 학생(인스턴스)이 함께 보는 **'공용 게시판'**과 같습니다. 개인 다이어리(인스턴스 변수)는 각자 가지고 있지만, 공용 게시판(static
변수)은 단 하나뿐이며 모두가 같은 내용을 봅니다.
코드로 살펴보기 - 학생 학번 자동 부여기
모든 학생 객체가 nextId
라는 공유 변수를 사용하여 순차적으로 학번을 부여받는 예제입니다.
Student 클래스
public class Student {
// static 필드: 모든 Student 인스턴스가 이 변수를 공유한다.
private static int nextId = 1;
// 인스턴스 필드: 각 Student 인스턴스마다 개별적으로 가진다.
private int id;
public void setId() {
this.id = nextId; // 공유 변수 nextId를 개인 id에 할당
nextId++; // 공유 변수 값을 1 증가시킨다.
}
public int getId() {
return id;
}
// 클래스 전체의 공유 자원에 접근
public static int getNextId() {
return nextId;
}
}
실행 코드 (main
메소드)
public static void main(String[] args) {
System.out.println("초기 nextId: " + Student.getNextId()); // 출력: 1
Student minsu = new Student();
minsu.setId(); // minsu.id = 1, nextId는 2가 됨
System.out.println("민수 할당 후 nextId: " + Student.getNextId()); // 출력: 2
Student heejin = new Student();
heejin.setId(); // heejin.id = 2, nextId는 3이 됨
System.out.println("희진 할당 후 nextId: " + Student.getNextId()); // 출력: 3
System.out.println("민수 학번: " + minsu.getId()); // 출력: 1
System.out.println("희진 학번: " + heejin.getId()); // 출력: 2
}
minsu
와 heejin
은 각자 다른 객체이지만, nextId
라는 static
변수를 공유하기 때문에 순차적인 학번 부여가 가능합니다. static
멤버는 객체 생성(new
) 없이 클래스이름.정적멤버
형태로 접근할 수 있습니다.
[심화] JVM 메모리에서 final
과 static
은 어디에 살까?
이들의 동작 방식을 완벽히 이해하려면 변수들이 JVM 메모리의 어느 영역에 저장되는지 알아야 합니다. JVM 메모리는 크게 3가지 영역으로 나뉩니다.
메소드 영역 (Method Area)
- 클래스의 '설계도'가 보관되는 곳입니다. 클래스 정보, 메소드 코드, 그리고
static
변수가 이곳에 저장됩니다. - 프로그램 시작 시 클래스가 로드될 때 생성되며, 모든 스레드가 공유합니다.
- 클래스의 '설계도'가 보관되는 곳입니다. 클래스 정보, 메소드 코드, 그리고
힙 영역 (Heap)
new
키워드로 생성된 모든 객체(인스턴스)가 거주하는 공간입니다.- 인스턴스 변수(멤버 변수)들이 객체 내부에 저장됩니다.
final
이 붙었든 아니든 인스턴스 변수는 모두 힙에 생성된 객체 안에 위치합니다.
스택 영역 (Stack)
- 메소드가 호출될 때마다 해당 메소드만을 위한 '작업 공간'(스택 프레임)이 생성되는 곳입니다.
- 메소드 내의 지역 변수, 매개변수가 이곳에 저장됩니다. 객체를 직접 저장하는 것이 아니라, 힙에 있는 객체를 가리키는 참조 주소를 저장합니다.
- 메소드 실행이 끝나면 해당 스택 프레임은 즉시 사라집니다.
예제 코드를 바탕으로 한 메모리 배치
변수 종류 | 예시 코드 | 저장되는 메모리 영역 | 설명 |
---|---|---|---|
static 필드 |
private static int nextId |
Method Area | 클래스 자체에 속하며, 모든 인스턴스가 공유하는 단 하나의 변수. |
인스턴스 필드 | private int id |
Heap | new Student() 로 생성된 각 객체 내부에 개별적으로 존재. |
final 인스턴스 필드 |
private final String message |
Heap | new FinalExam() 객체 내부에 존재. final 여부와 상관없이 인스턴스 변수는 힙에 위치. |
지역 변수 (참조형) | Student minsu = ... |
Stack | main 메소드의 지역 변수 minsu 는 스택에 저장되며, 힙에 있는 실제 객체를 가리키는 주소값을 가짐. |
final 매개변수 |
final String message |
Stack | showMessage 메소드의 매개변수 message 는 메소드 호출 시 스택에 생성됨. |
결론
final
과 static
은 Java의 근간을 이루는 중요한 키워드입니다.
final
: 불변성을 부여하여 값의 변경을 막고 예측 가능하며 안전한 코드를 만듭니다.static
: 공유성을 부여하여 클래스 레벨의 데이터를 관리하고, 메모리를 효율적으로 사용하게 합니다.
'프로그래밍 언어 > java' 카테고리의 다른 글
[Java] Java의 String은 왜 불변(Immutable)할까? (0) | 2024.08.18 |
---|---|
[Java] equals overriding 과 hash code (0) | 2024.08.11 |
[Java] JVM 메모리 구조 파헤치기 (0) | 2024.07.28 |
객체 지향 언어 (0) | 2024.07.09 |
[자바] 입력받기 (0) | 2024.07.09 |