자바는 모든 코드를 클래스로 감싸서 구성한다. 심지어 main 함수까지 클래스 안에 존재하게 된다.
그러나 모든 클래스 인스턴스에서 공유하고 접근할 수 있는 변수가 필요할 수 있다. 이때의 이 변수를 클래스 변수라고 하고 보통 static 변수라고 한다.
static 변수 역시 클래스 안에 존재한다. 보통 이 변수를 자주 사용하는 클래스 안에 존재하는데 그 클래스의 인스턴스 선언 때마다 새로이 선언되지는 않고 전체 프로그램에서 딱 하나만 존재하게 된다.
class InstCnt {
static int instNum = 0;
...
static 변수는 클래스와는 무관하지만 공간을 빌린 대가는 있다.
먼저, static 변수가 private으로 선언된 경우에는 그 클래스의 인스턴스에서만 접근 가능하다.
private으로 선언되지 않으면 클래스 외부에서도 접근이 가능한데 접근 방법은 두 가지가 있다.
클래스 이름으로 접근하는 것이 더 선호된다.
class AccessWay {
static int num = 0;
AccessWay() { incrCnt(); }
void incrCnt() {
num++; // 클래스 내부에서 직접 접근
}
}
class ClassVarAccess {
public static void main(String[] args) {
AccessWay way = new AccessWay();
way.num++; // 외부에서 인스턴스 이름으로 접근
AccessWay.num++; // 외부에서 클래스 이름으로 접근
}
}
JVM은 프로그램이 실행될 때, 모든 클래스를 읽어 들이지 않는다.
코드를 하나씩 읽어나가면서, 읽어야할 클래스가 있을 때 해당 클래스를 읽어 들인다.
따라서 클래스 변수 또한 해당 클래스가 읽혀질 때에 JVM에 의해 초기화가 되고 메모리에 별도로 존재하게 된다.
인스턴스 별로 가지고 있을 필요가 없는 변수, 즉 클래스 변수로 만들어야 하는 경우는 아래와 같다.
그리고 그 값이 외부에서도 참조해야 한다면 public으로 선언한다.
대상이 변수에서 메소드로 변했을 뿐, 클래스 변수와 성격이 같다.
인스턴스 메소드는 인스턴스 변수를 활용하고 연관을 가지고 있어야 인스턴스 메소드로써의 역할을 다 할 수 있다.
그러나 단순 기능 제공이 목적이고, 인스턴스 변수와 관련 지을 필요가 없는 메소드들은 static으로 선언하는 것이 옳다.
class SimpleCalculator {
static final double PI = 3.14;
static double add(double n1, double n2) {
return n1 + n2;
}
static double min(double n1, double n2) {
return n1 - 2;
}
static double calCircleArea(double r) {
return PI * r * r;
}
static double calCirclePeri(double r) {
return PI * (r * 2);
}
}
그렇다면 클래스 메소드에서 해당 클래스의 인스턴스 변수에 접근할 수 있을까?
정답은 접근할 수 없다. 일단 인스턴스와 클래스 메소드는 서로 다른 메모리 공간에 저장이 된다. 또한, 접근할 수 있다고 하더라도 클래스 메소드에서 값을 바꾼다고 하면 어떤 인스턴스 변수에 접근해야 하는 지의 문제도 남아있다.
System.out.println()
에 대해 살펴보자.
먼저, System.out
에서 "System"이 대문자로 시작하는 것으로 보아 클래스의 이름이고 "out"은 클래스 변수임을 유추할 수 있다.
또한, "println"은 "out"이 참조하는 인스턴스의 메소드이다.
사실, "System"은 java.lang 패키지에 속한 클래스이다. 우리가 이 패키지를 불러오지 않아도 컴파일러가 자동으로 이 패키지를 불러오고 우리는 이름만으로도 접근할 수 있다.
이러한 이유가 있는데 java.lang에는 시스템과 관련된 중요한 패키지들이 많이 있어 우리가 실수로 동일한 이름을 쓰지 못하도록 막는 것이다.
이번엔 public static void main()
을 살펴보자.
static으로 선언한 이유는 main 메소드가 인스턴스마다 있을 필요가 없기 때문이다. main 메소드는 시스템에서 하나만 존재해야 한다.
public으로 선언한 이유는 약속이라고 생각하면 된다. main 메소드는 외부로 부터 시작되는 메소드이기 때문에 상징적으로 public 선언을 해준다.
그렇다면 main 메소드를 어디에 위치시킬 것인가.
두 가지 옵션이 있다.
보편적으로는 1번의 방법이 선호된다. (그렇다고 main이라는 클래스를 만드는 것은 좋지 않다.)
만약 2번의 방법으로 만들어진 경우, main 메소드가 위치한 클래스에 따라서 실행 방법이 달라진다.
static 변수는 선언과 동시에 초기화를 시켜야 한다. 앞에서 말했듯이 초기화 단계를 인스턴스 선언 시에 넣는다면, 그 때마다 새로이 값이 초기화되기 때문이다.
class DateOfExecution {
static String date;
state {
LocalDate nDate = LocalDate.now();
date = nDate.toString();
}
...
}
이처럼 다른 곳에서 값을 얻어와서 static 변수를 초기화해야 하는 경우가 있다. 이런 경우에는 선언과 동시에 초기화를 시킬 수 없다.
그런 경우에 static 초기화 블록을 사용한다.
DateofExecution
클래스가 읽혀질 때, static 변수 date
가 메모리 상에 저장이 되고 그 바로 static 초기화 블록이 실행되면서 date
변수를 초기화 시킨다.