1. static 필드: 어떻게 초기화할까?
정적 필드(static)는 객체가 아니라 클래스 전체에 속합니다. 이 값은 해당 클래스의 모든 객체에 대해 단 하나만 존재합니다. 비유하자면 — 회사 모든 직원을 위한 “공용 금고”를 선언하는 것과 같습니다. 누가 오더라도 금고는 모두가 함께 씁니다.
선언 시 초기화
가장 간단하고 흔한 방법은 정적 필드에 선언과 동시에 값을 대입하는 것입니다:
public class User {
private static int userCount = 0; // 여기에서 바로 초기화
// ... 나머지 코드
}
이러한 필드는 클래스 User가 메모리에 처음 로드될 때 초기화됩니다.
정적 블록에서의 초기화
때로는 더 복잡한 초기화 로직이 필요합니다 — 예를 들어 파일에서 데이터를 읽거나 계산을 수행해야 할 수 있습니다. 이때는 정적 초기화 블록을 사용합니다:
public class Config {
public static String configPath;
static {
// 이 블록은 클래스가 로드될 때 단 한 번 실행됩니다
configPath = System.getenv("APP_CONFIG_PATH");
if (configPath == null) {
configPath = "/etc/app/default.conf";
}
System.out.println("Config path 초기화됨: " + configPath);
}
}
정적 블록은 언제 실행될까요?
- 클래스에 처음 접근할 때(예: 첫 객체 생성 또는 임의의 정적 메서드/필드 접근 시).
- 각 클래스마다 한 번만 실행됩니다.
static 필드 접근의 특징
- static 필드는 클래스 이름으로 접근합니다: User.userCount.
- 객체를 통해서도 접근할 수 있지만, 읽는 사람을 혼란스럽게 하므로 좋지 않은 스타일입니다.
예시:
User u1 = new User();
User u2 = new User();
System.out.println(User.userCount); // 권장
System.out.println(u1.userCount); // 동작은 하지만 권장하지 않음!
2. final 필드: 언제, 어떻게 초기화할까?
final은 “이 필드는 한 번만 대입할 수 있으며 이후에는 변경되지 않는다”는 뜻의 변경자입니다. 초기화가 끝나면 값은 불변이 됩니다. 인스턴스(객체) 수준에서는 그 객체의 고정 속성이며, 클래스 수준(static)에서는 공용 상수가 됩니다.
사용 예:
- 상수(예: PI).
- 생성 후 변경할 수 없는 객체의 고유 식별자.
final 필드 초기화 요구사항
Java에는 엄격한 규칙이 있습니다: 모든 final 필드는 선언 시 또는 클래스의 모든 생성자에서 반드시 초기화되어야 합니다.
선언 시 초기화
public class Circle {
public static final double PI = 3.1415926535; // 클래스 상수
private final String id = "CIRCLE"; // 객체 상수
}
생성자에서 초기화
때로는 final 필드의 값이 객체 생성 시점에만 결정됩니다:
public class User {
private final int id;
public User(int id) {
this.id = id; // 생성자에서 final 필드 대입
}
}
중요: 생성자가 여러 개라면, 모든 생성자에서 final 필드를 반드시 초기화해야 합니다!
결합 예시
public class Token {
private final String value;
private final long timestamp;
public Token(String value) {
this.value = value;
this.timestamp = System.currentTimeMillis();
}
}
잘못된 초기화로 인한 컴파일 오류
final 필드 초기화를 잊으면, 컴파일러가 프로그램 빌드를 허용하지 않습니다:
public class Broken {
private final int x; // 초기화되지 않음
public Broken() {
// x에 값이 대입되지 않음!
}
}
// 오류: variable x might not have been initialized
3. static과 final의 조합: 클래스 상수 선언
아주 자주 쓰이는 조합이 public static final 입니다. Java에서는 이 방식으로 클래스 상수 — 프로그램 실행 동안 변하지 않는 값을 — 선언합니다. 이러한 상수는 클래스 전체에 속하며, 해당 클래스의 모든 객체에 대해 동일합니다.
문법과 예시
public class MathUtils {
public static final double PI = 3.1415926535;
public static final String APP_NAME = "MyApp";
}
설명:
- public — 어디서나 접근 가능.
- static — 개별 객체가 아니라 클래스 전체에 속함.
- final — 초기화 후 변경 불가.
사용법
double area = MathUtils.PI * r * r;
System.out.println(MathUtils.APP_NAME);
관례: 상수 이름은 보통 UPPER_SNAKE_CASE로 작성합니다.
4. 코드 예제: static 및 final 필드 초기화 방식
예제 1: 상수가 있는 단순 클래스
public class Constants {
public static final int DAYS_IN_WEEK = 7;
public static final String COMPANY = "Romashka LLC";
}
예제 2: 정적 블록에서 초기화된 static 필드
public class AppConfig {
public static final String DEFAULT_PATH;
static {
// 복잡한 로직을 수행할 수도 있습니다
String env = System.getenv("APP_PATH");
if (env != null) {
DEFAULT_PATH = env;
} else {
DEFAULT_PATH = "/usr/local/app";
}
}
}
예제 3: 객체의 final 필드 — 생성자에서 초기화
public class User {
private static int nextId = 1;
private final int id;
private String name;
public User(String name) {
this.id = nextId++;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
사용 예:
User u1 = new User("Ivan");
User u2 = new User("Maria");
System.out.println(u1.getId()); // 1
System.out.println(u2.getId()); // 2
예제 4: final 필드 초기화가 잘못된 경우의 오류
public class BadExample {
private final int number;
public BadExample() {
// number 초기화되지 않음!
}
}
// 컴파일 오류: variable number might not have been initialized
5. static 및 final 필드 사용 시 모범 사례
public static final은 진짜 상수에만 사용하세요
값이 미래에 변경될 가능성이 있다면(예: 직원 목록) final로 만들지 마세요! 상수는 진정으로 불변이어야 합니다.
public static final int MAX_USERS = 1000; // 좋음
public static final String[] USERS = new String[100]; // 나쁨!
참조 자체인 USERS는 바뀌지 않지만, 배열의 내용은 바뀔 수 있습니다. 이는 예기치 않은 오류로 이어질 수 있습니다.
복잡한 초기화에는 정적 블록을 사용하세요
단순 대입으로 표현할 수 없는 상수(예: 계산이 필요하거나 파일에서 읽어와야 하는 경우)는 정적 블록을 사용하세요.
static 필드를 남용하지 마세요
static 필드는 전역 변수와 같습니다. 너무 많으면 추적하기 어려운 오류가 생기고 유지보수가 어려워집니다.
변경 가능한 객체를 public static final로 만들지 마세요
객체가 가변적이라면 public이며 final로 공개하지 마세요. 모든 코드가 내부 상태에 접근하게 되어, 누군가는 반드시 그 점을 이용하게 됩니다.
6. static 및 final 필드 초기화에서 흔한 실수
오류 1: 초기화되지 않은 final 필드. final 필드를 선언 시 또는 모든 생성자에서 초기화하지 않으면 컴파일 오류가 발생합니다. 예를 들어 생성자가 두 개인데, 그중 하나에서 final 필드에 값을 대입하지 않았다면 오류가 납니다.
오류 2: final 필드 값을 변경하려는 시도. 초기화 이후 final 필드 값을 변경하려 하면 컴파일 오류가 발생합니다.
public class Demo {
private final int x = 5;
public void change() {
x = 10; // 오류: cannot assign a value to final variable x
}
}
오류 3: public이며 변경 가능한 static final 필드. 변경 가능한 객체(String[], int[] 등)를 public static final로 만들면, 어떤 코드든 그 내용을 바꿀 수 있습니다. 이는 캡슐화를 깨고, 발견하기 어려운 버그로 이어질 수 있습니다.
오류 4: 정적 블록에서 비정적 필드 사용. 정적 블록에서는 비정적 필드에 접근할 수 없습니다. 아직 초기화되지 않았고(이 단계에서는 존재조차 하지 않음) 접근할 수 없기 때문입니다.
오류 5: 예상치 못한 초기화 순서. 정적 블록에서 아래쪽에 선언된 정적 필드를 참조하면, 그 필드가 아직 초기화되지 않았을 수 있습니다. 정적 블록에서 사용할 계획이 있다면, 정적 필드를 항상 정적 블록보다 먼저 선언하세요.
GO TO FULL VERSION