우테코 7기 프리코스 1주차 영상 피드백에, "상수를 왜 private static final 로 해야하나요?" 이 제기되었다. 그래서 좀 궁금했다. 왜일까?
알아보니, 상수 선언 시 private static final 키워드를 사용하는 것은 단순한 관례가 아닌 명확한 이유가 있다. 각각의 키워드가 가진 특징과 장점을 예제와 함께 살펴보자.
매직 넘버나 매직 스트링은 코드에 직접 작성된 의미를 알 수 없는 값들이다. 이러한 값들에 이름을 부여하면 코드의 의도를 명확히 전달할 수 있다.
public class PaymentCalculator {
public int calculateFee(int price) {
return price + (price * 10 / 100); // 10이 무슨 의미인지 모호함
}
}
// 좋은 예
public class PaymentCalculator {
private static final int VAT_RATE = 10; // 부가가치세율이라는 의미가 명확함
public int calculateFee(int price) {
return price + (price * VAT_RATE / 100);
}
}
static 키워드를 사용한 변수는 JVM Runntime Data area 의 Method Area 에 저장된다. 그리고 모든 인스턴스가 하나의 메모리를 공유한다. 간단하게 그림으로 확인하자
간단한 예제 코드로 저장 위치를 확인하면
class Student {
static String schoolName = "Seoul High"; // Method Area에 저장
String name; // Heap Area에 저장
void study() {
int hour = 2; // Stack Area에 저장
}
}
위와 같다.
오케이. Method Area 가 모든 인스턴스가 공유하는 영역이므로, static 으로 생성한 변수는 인스턴스가 생성 될 때마다 heap 에 새로운 공간을 차지하는 게 아니라, Method area 공간을 참조하는 형식으로 동작한다는 것을 알았다.
정말 그런 지 한 번 학습 테스트를 작성해보자.
public class StaticClass {
private static Integer MAX_BALUE = new Integer(Integer.MAX_VALUE);
}
public class NonStaticClass {
private Integer maxValue = new Integer(Integer.MAX_VALUE);
}
JVM 내부에서 리터럴이나 값이 여러 번 사용될 경우 메모리를 절약하기 위해 이들을 재사용할 수 있다,
따라서, 9버전부터 deprecated 된 new Integer(); 생성자 호출을 통해서 인스턴스를 생성한다.
이를 통해 heap 메모리에 NonStaticClass 의 maxValue 변수가 시행 횟수만큼 저장됨을 보장한다.
class StaticMemoryTest {
private static final int TRIAL_COUNT = 1_000_000;
@Test
void static_키워드_사용() {
long memoryUsage = getMemoryUsage(StaticClass.class);
System.out.println("static 사용: " + memoryUsage / 1024 + "KB");
}
@Test
void static_키워드_미사용() {
long memoryUsage = getMemoryUsage(NonStaticClass.class);
System.out.println("static 미사용: " + memoryUsage / 1024 + "KB");
}
// 메모리 사용량을 측정한다.
private <T> long getMemoryUsage(Class<T> clazz) {
Runtime runtime = Runtime.getRuntime();
List<T> instances = new ArrayList<>();
// 이전 메모리
long beforeMemory = runtime.totalMemory() - runtime.freeMemory();
try {
for (int i = 0; i < TRIAL_COUNT; i++) {
instances.add(clazz.getDeclaredConstructor().newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
// 인스턴스 생성 후 메모리
long afterMemory = runtime.totalMemory() - runtime.freeMemory();
return afterMemory - beforeMemory;
}
}
Integer 는 일반적으로 16바이트 내외의 크기를 가진다. 따라서 static 변수와 non-static 변수는
16 * 1,000,000byte = 16000 KB 정도의 차이가 날 것이다.
결과는 16893KB. 시행 횟수마다 수치가 조금씩 틀려서 정확하진 않지만, 근사치라고 보아도 될 것같다.
final 키워드가 붙은 변수는 재할당 이 불가능하다.
따라서, 해당 키워드를 사용함으로써
효과를 얻을 수 있다.
위와 같은 이유로 사용한다. 땅땅.
많은 도움이 되었습니다