static 키워드는 객체 지향 프로그래밍에서 중요한 역할을 수행합니다. 클래스 인스턴스 간에 공유되는 속성이나 메서드를 정의해서 메모리를 효율적으로 사용할 수도 있지만, 이것은 static 키워드가 사용된 값이 변경되면 모든 인스턴스에 영향을 미친다는 뜻이 됩니다. 하나의 사이드 이펙트도 서비스 장애를 일으킬 수 있기 때문에 신중하게 인지하고 사용하는 것이 중요합니다. 아래 내용을 통해 static을 언제, 어떻게 활용하면 좋을지 생각해볼 수 있기를 기대합니다.
static은 자바에서 인스턴스들이 공유하는 클래스 레벨의 정의에 사용되는 키워드입니다. 주로 static 변수, static 메서드를 선언하는데 사용되며 [클래스 변수, 정적 변수, static 변수], [클래스 메서드, 정적 메서드, static 메서드] 모두 같은 의미로 사용됩니다. 클래스가 로드될 때 단 한 번만 메모리에 할당되고, 이후 생성되는 모든 인스턴스에서 공유되기 때문에 상수값이나 공유해야 할 자원을 관리할 때 유용합니다.
static 키워드가 사용된 코드는 클래스 로더에 의해 JVM 버전에 따라 Method Area(Java 8 이전) 또는 Metaspace(Java 8 이후)에 적재됩니다.
이후 클래스가 JVM에서 언로드 될 때 메모리를 반환하는데, 일반적으로 JVM이 종료될 때까지(프로그램 종료) 메모리에 유지됩니다.
클래스 로더는 다음과 같은 상황에서 static 영역을 초기화합니다.
해당 클래스에서 생성된 인스턴스 전체가 공유하는 변수가 필요할 때 사용합니다.
public class Employee {
private static int employeeCount = 0; // static 변수, 생성자를 호출할 때마다 1씩 증가
private String name;
public String getName() {
return name;
}
public Employee(String name) {
this.name = name;
employeeCount++;
}
public int getTotalEmployeeCount() {
return employeeCount;
}
public static void main(String[] args) {
Employee kim = new Employee("Kim");
Employee park = new Employee("Park");
System.out.println("[" + kim.getName() + "] " + "Total Employees: " + kim.getTotalEmployeeCount());
System.out.println("[" + park.getName() + "] " + "Total Employees: " + park.getTotalEmployeeCount());
}
}
# 실행결과
[Kim] Total Employees: 2
[Park] Total Employees: 2
Process finished with exit code 0
static 메서드는 인스턴스 생성 없이 호출할 수 있는 메서드입니다.
static으로 선언된 클래스 속성이 아닌 인스턴스의 속성에는 접근할 수 없고, 값을 외부에서 주입할 수밖에 없기 때문에 객체지향 원칙과 상충되는 부분이 존재합니다.
하지만 java.lang.Math 등 개발에 유용한 유틸리티성 클래스를 만드는데 사용할 수 있습니다.
유틸리티 클래스는 재사용성, 모듈성을 가지고 있기 때문에 적절하게 사용하면 개발 효율을 높일 수 있습니다.
public class Calculator {
public static int add(int number1, int number2) { // static 메서드, 두 수의 합 반환
return number1 + number2;
}
public static void main(String[] args) {
System.out.println("Sum: " + Calculator.add(10, 20));
System.out.println(Math.max(10, 20)); // 유틸리티 클래스, 더 큰 수를 반환
}
}
# 실행결과
30
20
Process finished with exit code 0
static 블록은 클래스가 JVM의 클래스 로더를 통해 최초 메모리 적재 시점에만 한번 호출되는 블록입니다.
DB 연결, 특정 라이브러리 초기화 등 어플리케이션에서 한번만 호출해주면 되는 상황에 사용할 수 있습니다.
public class Employee {
public static int employeeCount;
static { // static 블록, 처음 한번만 호출됨
System.out.println("클래스 최초 로딩");
employeeCount = 100;
}
public static void main(String[] args) {
System.out.println("bookCount: " + Employee.employeeCount);
System.out.println("bookCount: " + Employee.employeeCount);
System.out.println("bookCount: " + Employee.employeeCount);
}
}
# 실행결과
클래스 최초 로딩
bookCount: 100
bookCount: 100
bookCount: 100
Process finished with exit code 0
static final은 상수를 선언할 때 사용되는 키워드입니다.
static 초기화와는 다르게 static final로 선언한 기본형 또는 문자열 리터럴 상수는 이미 컴파일 타임에 값으로 변환되어 코드에 들어갑니다.
따라서 이러한 상수는 런타임에 클래스 로더에 의한 별도의 초기화가 필요하지 않습니다.
public class Speaker {
public static final int MAX_VOLUME = 100;
public static void main(String[] args) {
System.out.println("MAX_VOLUME: " + MAX_VOLUME);
}
}
# 실행결과
MAX_VOLUME: 100
Process finished with exit code 0
이렇듯 static 키워드는 적절하게 사용했을 때 프로그램의 메모리 효율성, 유지보수성 향상, 실행 속도 단축 등의 효과를 얻을 수 있습니다.
하지만 캡슐화, 다형성에 위배될 수 있고 메모리 릭이나 동시성 문제가 발생할 수 있는 등 오히려 사용을 지양하는 것이 나을 수 있습니다.
프로그램 구조와 객체지향에 대한 정확한 이해를 바탕으로 static을 사용해야 합니다.