static
`static` 키워드는 주로 메모리 관리를 위해 사용되며, 변수나 메서드와 같은 멤버가 특정 클래스의 인스턴스에 속하는 것이 아니라 클래스 자체에 속함을 의미한다. 따라서 객체의 인스턴스를 생성하지 않고도 정적 멤버에 접근할 수 있다.
일반적으로 우리가 정의한 클래스는 메모리의 Static 영역에 생성되며, `new` 연산자를 통해 생성한 객체는 Heap 영역에 할당된다. Heap 영역에 생성된 객체는 Garbage Collector에 의해 자동으로 관리되지만, Static 영역에 생성된 데이터는 Garbage Collector의 관리 대상이 아니다.
Static 영역의 데이터는 프로그램이 종료될 때까지 메모리에 유지되며, 이는 모든 객체가 해당 데이터를 공유할 수 있는 장점을 제공한다. 하지만 Static 메모리는 프로그램이 종료될 때까지 해체되지 않기 때문에 과도하게 사용하면 메모리 효율이 떨어지고, 시스템 성능에 악영향을 미칠 수 있다.
적용 대상
- 정적 변수 (static variable): 클래스의 모든 객체가 공유하는 변수로, 객체 생성 없이 접근 가능
- 정적 메서드 (static method): 객체와 독립적으로 동작하며, 클래수 수준에서 호출할 수 있는 메서드
- 정적 블록 (static block): 정적 변수를 초기화하거나 복잡한 초기화 작업이 필요한 경우 사용
- 정적 클래스 (static nested class): 외부 클래스의 정적 멤버와 관련된 작업을 수행하며, 외부 클래스의 비정적 멤버에는 접근할 수 없음
static Fields(or Class Variable)
Java에서 필드를 `static`으로 선언하면 해당 필드의 단일 복사본이 생성되어 해당 클래스의 모든 인스턴스에서 공유된다.
클래스를 몇 번 인스턴스화하더라도 static 필드는 항상 하나의 복사본만 존재한다. 이 정적 필드의 값은 같은 클래스의 모든 객체에 의해 공유된다. 메모리 관점에서 정적 변수는 힙 메모리에 저장된다.
예를 들어, 여러 인스턴스 변수를 가진 클래스가 있다고 가정해보자. 이 클래스에서 생성된 각 객체는 해당 변수들의 자체 복사본을 가진다. 하지만 객체가 몇 개 생성되었는지 추적하려면 static 변수를 사용하는 것이 좋다. 이렇게 하면 새로운 객체가 생성될 때마다 카운터를 증가시킬 수 있다.
public class Car {
private String name;
private String engine;
public static int numberOfCars;
public Car(String name, String engine) {
this.name = name;
this.engine = engine;
numberOfCars++;
}
// getters and setters
}
위 코드에서 `numberOfCars`라는 정적 변수는 `Car` 클래스가 인스턴스화될 때마다 증가한다. 예를 들어, 두 개의 `Car` 객체를 생성하면 카운터 값이 2가 된다.
@Test
public void whenNumberOfCarObjectsInitialized_thenStaticCounterIncreases() {
new Car("Jaguar", "V8");
new Car("Bugatti", "W16");
assertEquals(2, Car.numberOfCars);
}
위에서 보듯, static 필드는 다음과 같은 상황에 유용하다.
- 변수의 값이 객체와 독립적일 때
- 값이 모든 객체 간에 공유되어야 할 때
static 필드 접근 방법
- 인스턴스를 통해 접근
Car ford = new Car("Ford", "V6");
ford.numberOfCars++;
- 클래스를 통해 접근(권장)
Car.numberOfCars++;
후자의 방식이 선호되며, 이는 해당 변수가 인스턴스 변수가 아니라 클래스 변수임을 더 명확히 나타낸다.
static Methods(or Class Methods)
static 필드와 마찬가지로, static 메서드도 객체가 아닌 클래스에 속한다. 따라서 클래스를 인스턴스화하지 않고도 호출할 수 있다. 일반적으로 static 메서드는 인스턴스 생성과 독립적인 작업을 수행할 때 사용된다.
예를 들어, static 메서드를 사용하여 클래스의 모든 인스턴스에서 공통으로 사용되는 코드를 공유할 수 있다.
static void setNumberOfCars(int numberOfCars) {
Car.numberOfCars = numberOfCars;
}
주요 사용 사례
- 유틸리티 클래스 생성: static 메서드는 유틸리티나 도우미(helper) 클래스를 만들 대 유용하다.
- JDK의 `Math` 클래스 (`Math.sqrt()`), Apache의 `StringUtils` 클래스, Spring Framework의 `CollectionUtils` 클래스 등
- 공유 로직 구현: 여러 인스턴스에서 공통으로 사용해야 하는 코드를 static 메서드로 구현하여 중복을 방지할 수 있다.
동작과 특징
- 컴파일 시간에 결정
- static 메서드는 컴파일 시간에 결졍되며, 이는 메서드 오버라이딩(overriding)이 불가능하다는 것을 의미한다.
- 메서드 오버라이딩은 런타임 다형성(Runtime Polymorphism)의 일부이기 때문에 static 메서드와는 동작 방식이 다르다.
- 인스턴스가 없는 호출 가능
- static 메서드는 객체를 생성하지 않고 `ClassName.methodName()` 형식으로 호출할 수 있다.
- 인스턴스와 static 간의 상호작용 규칙
- 인스턴스 메서드
- 인스턴스 변수와 다른 인스턴스 메서드에 직접 접근할 수 있다.
- static 변수와 static 메서드에도 직접 접근할 수 있다.
- static 메서드
- static 변수와 다른 static 메서드에 접근할 수 있다.
- 인스턴스 변수와 인스턴스 메서드에는 직접 접근할 수 없다. 이를 위해선 특정 객체의 참조(reference)가 필요하다.
- 인스턴스 메서드
Ths static Code Blocks
일반적으로 static 변수는 선언 시 바로 초기화한다. 하지만 여러 문장으로 구성된 논리가 필요한 초기화가 있을 경우 static 블록을 사용할 수 있다.
아래는 `List` 객체를 미리 정의된 값으로 초기화하기 위해 static 블록을 사용하는 예제이다.
public class StaticBlockDemo {
public static List<String> ranks = new LinkedList<>();
static {
ranks.add("Lieutenant");
ranks.add("Captain");
ranks.add("Major");
}
static {
ranks.add("Colonel");
ranks.add("General");
}
}
위 코드에서 알 수 있듯, `List` 객체를 선언과 동시에 모든 초기값으로 초기화하는 것은 불가능하다. 따라서 static 블록을 사용하여 초기화를 수행한다.
동작 방식
- 여러 static 블록
- 클래스는 여러 개의 static 블록을 가질 수 있다.
- JVM은 클래스가 로드될 때 static 변수와 static 블록을 선언된 순서대로 실행한다.
- 위 예제에서, 두 개의 static 블록이 순서대로 실행되며 `ranks` 리스트가 값으로 채워진다.
- 초기화의 필요성
- 복잡한 초기화 로직이 필요하거나, 선언과 동시에 초기화할 수 없는 경우 static 블록을 활용한다.
- 예: 데이터베이스 연결 설정, 파일 읽기 등
주요 사용 사례
- static 변수 초기화
- 단순 할당 외의 추가 논리가 필요한 경우 사용한다.
- 예를 들어, 조건문, 반복문, 또는 값을 계산하여 static 변수에 할당하는 경우
- 예외 처리와 초기화
- 초기화 과정에서 예외 처리가 필요한 경우 static 블록 내에서 처리할 수 있다.
public class Config {
public static Properties properties = new Properties();
static {
try {
properties.load(new FileInputStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
static Inner Classes
Java에서는 클래스 안에 클래스를 정의할 수 있다. 이를 통해 관련 요소들을 한 곳에 그룹화하여 코드를 더 깔끔하고 읽기 쉽게 관리할 수 있다.
Nested Class 구조
- Static Nested Class
- `static`으로 선언된 중첩 클래스
- 외부 클래스의 static 멤버에만 접근할 수 있다.
- Inner Class (비-static 클래스)
- `static`이 아닌 중첩 클래스이다.
- 외부 클래스의 모든 멤버(인스턴스와 static 모두)에 접근할 수 있다. private 멤버에도 접근 가능하다.
static Nested Class 특징
- 일반 클래스와 유사하지만 외부 클래스 안에 포함되어 있어 캡슐화를 높인다.
- 외부 클래스의 static 멤버에만 직접 접근할 수 있으며, 인스턴스 멤버에는 객체 참조를 통해서만 접근 가능하다.
- 외부 클래스와 독립적으로 동작할 수 있어, 더 적은 메모리와 더 적은 결합(coupling)을 요구한다.
예제 1: 싱글톤 패턴 구현
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
- 동기화(synchronization)를 사용하지 않아도 된다.
- static nested class가 외부 클래스의 static 메서드를 통해 간접적으로 초기화된다.
- Lazy initialization을 제공하며, SingletonHolder는 필요할 때 로드된다.
예제 2: 정적 중첩 클래스와 외부 클래스 간의 멤버 접근
public class Pizza {
private static String cookedCount;
private boolean isThinCrust;
public static class PizzaSalesCounter {
private static String orderedCount;
public static String deliveredCount;
PizzaSalesCounter() {
System.out.println("Static field of enclosing class is "
+ Pizza.cookedCount); // Static 필드 접근 가능
System.out.println("Non-static field of enclosing class is "
+ new Pizza().isThinCrust); // 객체 참조를 통해 접근
}
}
Pizza() {
System.out.println("Non private static field of static class is "
+ PizzaSalesCounter.deliveredCount);
System.out.println("Private static field of static class is "
+ PizzaSalesCounter.orderedCount);
}
public static void main(String[] args) {
new Pizza.PizzaSalesCounter();
}
}
출력 결과
Static field of enclosing class is null
Non private static field of static class is null
Private static field of static class is null
Non-static field of enclosing class is false
- static nested class는 외부 클래스의 static 멤버에 접근 가능
- 인스턴스 멤버는 객체 참조를 통해 접근 가능
사용 이유
- 캡슐화 향상
- 특정 클래스가 외부 클래스 내에서만 사용된다면 static nested class를 사용하여 코드를 그룹화하고 외부 접근을 제한할 수 있다.
- 가독성과 유지보수성
- 관련 코드를 외부 클래스 가까이에 두어, 코드의 이해와 유지보수를 더 쉽게 만든다.
- 메모리 효율성
- 외부 클래스의 인스턴스 멤버에 의존하지 않으므로 외부 클래스와 독립적이다.
- 따라서 힙 메모리나 스택 메모리를 사용하지 않아 효율적이다.
- 외부 클래스와의 결합도 감소
- static으로 선언된 nested class는 외부 클래스의 객체와 결합되지 않으므로 더 독립적으로 동작할 수 있다.
Non-static variable error
static context에서 non-staitc 변수를 사용하려할 때 발생한다.
오류 원인
- static 변수와 non-static 변수의 차이
- static 변수는 클래스 로드 시 JVM에 의해 초기화되며, 클래스 자체에 속한다.
- non-static 변수는 객체 인스턴스에 속하며, 객체 생성 후 접근할 수 있다.
- static context
- static 메서드나 블록은 클래스 레벨에서 실행되며, 객체에 의존하지 않는다.
- 따라서 static 컨텍스트에서는 non-static 변수에 직접 접근할 수 없다.
public class MyClass {
int instanceVariable = 0; // Non-static 변수
public static void staticMethod() {
System.out.println(instanceVariable); // 오류 발생
}
public static void main(String[] args) {
MyClass.staticMethod();
}
}
참조
https://www.baeldung.com/java-static
https://velog.io/@limjaewoo/Java-static
'Java' 카테고리의 다른 글
[Java] try-with-resources (0) | 2024.11.24 |
---|---|
[Java] Stream (1) | 2024.11.17 |
[Java] Enum이란? (0) | 2024.11.01 |
[AssertJ] Custom Exception (0) | 2024.10.31 |
[AssertJ] Exception Assertions (0) | 2024.10.31 |