목차
Java static 키워드
static은 필드나 메서드가 인스턴스가 아니라 클래스에 속한다는 뜻이다. 객체마다 다른 값은 인스턴스 필드로, 같은 클래스의 객체가 공유할 값은 정적 필드로 표현한다. static을 붙였다는 이유만으로 값이 불변이거나 접근이 동기화되는 것은 아니다. 또한 정적 메서드가 무조건 더 빠르다는 주장은 선택 기준이 되기 어렵다. 상태의 소유와 API 의미를 기준으로 정한다.
public class StaticExample {
private static int created = 0;
private final int id;
StaticExample() {
created++;
id = created;
}
static int createdCount() { return created; }
int id() { return id; }
public static void main(String[] args) {
StaticExample first = new StaticExample();
StaticExample second = new StaticExample();
System.out.println(first.id()); // 1
System.out.println(second.id()); // 2
System.out.println(StaticExample.createdCount()); // 2
}
}
created는 두 객체가 공유한다. id는 각 객체에서 정해져 이후 바뀌지 않는다. 이 예제는 단일 스레드에서만 단순하다. 여러 스레드가 동시에 생성자를 호출하면 created++가 원자적이지 않아 번호가 중복될 수 있다. 동시 생성이 필요하면 AtomicInteger 또는 적절한 동기화로 번호 발급을 보호한다. 정적 상태가 테스트 사이에 남는 문제도 고려한다.
정적 메서드가 볼 수 있는 것
정적 메서드에는 특정 객체의 this가 없으므로 인스턴스 필드를 바로 읽을 수 없다. 객체를 인자로 받거나 새 객체를 만들어야 한다.
class Price {
private final int amount;
Price(int amount) { this.amount = amount; }
int amount() { return amount; }
static int plusTax(int base, int tax) { return base + tax; }
}
plusTax는 객체 상태를 읽지 않는 계산이라 정적 메서드가 자연스럽다. 반면 amount()는 각 Price의 값이므로 인스턴스 메서드다. 정적 메서드는 하위 클래스에서 인스턴스 메서드처럼 오버라이드되지 않는다. 런타임 다형성이 필요한 동작이라면 인스턴스 메서드로 모델링한다.
정적 중첩 클래스와 초기화
class Registry {
private Registry() { }
private static class Holder {
static final Registry INSTANCE = new Registry();
}
static Registry instance() { return Holder.INSTANCE; }
}
Holder는 바깥 Registry의 인스턴스 없이 사용할 수 있는 정적 중첩 클래스다. 원문의 “static 클래스는 외부 클래스에서 사용이 안된다”는 설명은 반대다. 바깥 클래스가 Holder.INSTANCE에 처음 접근할 때 Holder가 초기화되는 방식을 활용한다. 이 패턴은 지연 초기화 싱글턴의 한 방법이지만, 상태가 없는 객체까지 무조건 싱글턴으로 만들 필요는 없다.
정적 초기화 블록과 정적 필드 초기화는 클래스가 초기화될 때 소스에 적힌 순서대로 실행된다. 클래스 초기화 시점은 단순히 소스 파일이 로드된 순간과 같다고 단정할 수 없다. 인스턴스 초기화 블록은 객체를 만들 때마다 생성자 본문 전에 실행된다. 초기화 순서가 필요한 복잡한 코드는 추적하기 어려우므로 생성자나 명시적 팩터리 메서드로 분리하는 편이 읽기 쉽다.
| 목적 | 권장 형태 | 주의 |
|---|---|---|
| 고정된 상수 | static final 불변 값 | 참조 객체 내부도 불변인지 확인 |
| 객체별 상태 | 인스턴스 필드 | 객체 수명에 맞춰 관리 |
| 공유 카운터 | 정적 필드 + 동시성 제어 | 테스트 격리, 경쟁 조건 |
| 상태 없는 계산 | 정적 메서드 | 다형성이 필요한지 확인 |
표의 첫 행처럼 static final List는 리스트 참조만 고정할 뿐 내부 원소를 변경할 수도 있다. 공개 API에 가변 목록을 그대로 내놓지 않는다.