static 멤버와 클래스 상태
인스턴스 필드는 객체마다 존재하지만 static 멤버는 클래스 로더가 읽은 클래스 단위로 공유됩니다.
모든 객체가 공통으로 사용하는 상수와 상태 없는 유틸리티에는 적합합니다.
반대로 변경 가능한 전역 상태를 static 필드에 두면 실행 순서와 테스트가 서로 영향을 줄 수 있습니다.
static 메서드는 특정 객체의 this를 갖지 않아 인스턴스 필드에 직접 접근할 수 없습니다.
이 절에서는 클래스 자체의 책임과 개별 객체의 책임을 분리합니다.
static 필드는 모든 인스턴스가 공유한다
객체를 몇 개 만들든 클래스마다 하나의 값이 존재합니다.
카운터처럼 공유가 의도된 경우에도 동시 접근 정책이 필요합니다.
static 메서드에는 this가 없다
호출 대상 객체가 없으므로 인스턴스 필드와 메서드에 바로 접근할 수 없습니다.
필요한 객체를 매개변수로 받거나 책임을 인스턴스 메서드로 옮깁니다.
티켓 두 개를 만든 뒤 각 객체의 ID와 클래스 전체의 발급 수를 함께 출력합니다. 객체별 값과 공유 카운터가 언제 증가하는지 비교하면 static의 소유 경계가 보입니다.
public class ClassCounter {
static final class Ticket {
private static int nextId = 1;
private final int id = nextId++;
int id() { return id; }
static int issuedCount() { return nextId - 1; }
}
public static void main(String[] args) {
Ticket first = new Ticket();
Ticket second = new Ticket();
System.out.println(first.id());
System.out.println(second.id());
System.out.println(Ticket.issuedCount());
}
}static 메서드 호출에는 특정 객체가 선택되지 않으므로 this도 없습니다. 인스턴스 상태가 필요하다면 객체를 매개변수로 받거나 그 동작 자체를 인스턴스 메서드로 옮기는 편이 책임을 분명하게 합니다.
1
2
2nextId는 Ticket 클래스 하나에 공유되고 id는 각 Ticket 객체에 따로 저장됩니다.
상수는 static final로 표현한다
객체마다 복제할 필요 없는 변하지 않는 값에 적합합니다.
가변 객체를 static final로 두면 참조만 고정되고 내부는 바뀔 수 있다는 점을 기억합니다.
모든 객체가 공유하고 바뀌지 않는 값은 static final 상수로 표현합니다. 반대로 실행 중 바뀌는 전역 static 상태는 테스트와 동시성에서 호출 순서에 따라 결과가 달라지므로, 소유 객체를 만들 수 없는지 먼저 검토합니다.
class Geometry {
static final double PI = Math.PI;
static double circleArea(double radius) {
return PI * radius * radius;
}
}
// static 메서드에는 this가 없다.
// static void broken() { System.out.println(this); } // 컴파일 오류static은 편의 키워드가 아니라 객체가 없어도 존재해야 하는 책임인지 판단한 뒤 사용해야 합니다.