[Java] static 제어자
static이란?
static은 클래스의 멤버(필드, 메서드, 이너 클래스)에 사용하는 제어자다. 클래스의 객체가 생성된 이후에 사용할 수 있는 상태가 되는 멤버를 인스턴스 멤버(instance member)라고 하며, 인스턴스 멤버는 멤버 앞에 static 제어자가 붙지 않은 것을 말한다. 반면 앞에 static이 붙어 있는 멤버를 정적 멤버(static member)라고 한다. 정적 멤버는 객체를 생성할 필요 없이 클래스 로더가 클래스 로딩을 끝내고 클래스 영역에 클래스를 적재하면 사용이 가능하며, '클래스명.멤버명'만으로 쓸 수 있다.
인스턴스 필드와 정적 필드
class A {
int m = 1;
static int n = 2;
}
A a = new A();
System.out.println(a.m); // 1
System.out.println(A.n); // 2
인스턴스 필드와 정적 필드의 메모리 구조를 통해 차이점을 알아보자.
두 필드는 각 필드의 저장 위치 때문에 사용 방법에 차이가 있다. 인스턴스 필드인 m의 저장 공간은 힙 메모리에 있는 객체 내부에 생성되므로 m을 사용하기 위해서는 반드시 객체를 먼저 생성해야 한다. 또한 힙 메모리에 존재하기 때문에 반드시 해당 저장 공간에 값을 읽거나 쓰기 위해서는 '참조 변수명.인스턴트 필드명'과 같이 참조 변수명을 사용해서 접근해야한다.
반면, 정적 필드인 n은 클래스 내부에 저장 공간을 지니고 있기 때문에 객체 생성 없이 '클래스명.정적 필드명'과 같이 사용할 수 있다. 이 때 알아야할 점은 객체 내부에 있는 정적 필드인 n은 정적 영역(클래스 영역)의 저장 공간에 있는 n의 참좃값을 가지고있고, 실제 저장 공간은 정적 영역 내부에 있다는 것이다.
정적 필드 또한 '참조 변수명.정적 필드명'으로 접근 가능하지만, 인스턴트 필드와 구분하기 위해서 이러한 방법은 권장되지않는다.
정적 필드의 공유성
A a1 = new A();
A a2 = new A();
a1.n = 3;
a2.n = 4;
System.out.println(a1.n); // 4
System.out.println(a2.n); // 4
A.n = 5;
System.out.println(a1.n); // 5
System.out.println(a2.n); // 5
정적 필드의 특징 중 꼭 기억해야 할 것은 '정적 필드는 객체 간 공유 변수의 성질이 있다'는 것이다.
앞에서 메모리 구조를 기반으로 정적 필드에 대해서 설명했다싶이 실제 데이터값은 정적(클래스) 영역의 클래스 A 안에 실제 데이터 값, 힙 메모리의 객체 안에는 실제 데이터값의 위칫값을 저장한다. 즉, a1 객체의 정적 필드 n과 a2 객체의 정적 필드 n은 모두 클래스 A에 저장된 값을 똑같이 가리키고 있는 것이다. 따라서 정적 필드가 객체 간의 공유 변수 성질을 가진다고 얘기할 수 있다.
정적 메서드
class A {
int n;
static int m;
void abc() {
// n, m, bcd(), cde() 사용 가능
}
static void bcd() {
// b, cde() 사용 가능
}
static void cde() {
// b, bcd() 사용 가능
}
}
public class StaticPractice {
public static void main(String[] args) {
A.bcd(); // 객체를 생성하지 않아도 사용 가능
A.cde(); // 객체를 생성하지 않아도 사용 가능
A a = new A();
a.abc(); // 객체를 생성해야 사용 가능
}
}
인스턴스 메서드는 반드시 객체를 생성한 후에 사용할 수 있지만, 정적 메서드는 정적 필드와 마찬가지로 클래스가 로딩된 이후에 바로 '클래스명.정적 메서드명'으로 사용 가능하다. 메모리 구조에서 인스턴스 메서드와 정적 메서드는 첫번째 메모리 영역에 동일하게 존재하지만, 인스턴스 메서드는 인스턴스 메서드 영역, 정적 메서드는 클래스 내부에 존재하는 것이 차이점이다.
인스턴스 메서드와 정적 메서드가 사용 가능한 데이터에도 차이점이 있다. 인스턴스 메서드 객체가 생성된 이후에 사용 가능하므로 인스턴스 메서드 내부에서는 인스턴스/정적 필드, 인스턴스/정적 메서드 모두 사용 가능한 반면, 정적 메서드는 객체가 생성되기 전에도 사용 가능하므로 정적 메서드 내부에서는 정적 필드, 정적 메서드만 사용 가능하다.
정적 초기화 블록
class A {
static int a;
static {
a = 1;
System.out.println("클래스 A가 로딩되었습니다.");
}
}
public class StaticInitialBlock {
public static void main(String[] args) {
System.out.println(A.a); // "클래스 A가 로딩되었습니다." \n "1"
}
}
정적 필드는 객체의 생성 이전에도 사용할 수 있어야 하므로 생성자가 호출되지 않은 상태에서도 초기화할 수 있어야 한다. 그래서 정적 필드를 초기화하기 위한 문법이 있는데, 이것이 정적 초기화 블록이다.
정적 초기화 블록(static {})은 클래스가 메모리에 로딩될 때 가장 먼저 실행되므로 여기에 정적 필드의 초기화 코드를 넣어 두면 클래스가 로딩되는 시점에 바로 초기화할 수 있다.
위의 예시만 보면 static int a = 1
으로 바로 초기화하는 것이 낫지않나라고 생각할 수 있겠지만, 복잡한 계산을 통해 초기화를 해야할 경우에는 정적 초기화 블록 내에서 계산을 수행한 후 초기화를 하는 것이 더 효율적일 것이다.
Reference
- 자바 완전 정복 | 김동형 지음