[Java] 변수와 메서드 유형-클래스 변수/인스턴스 변수/지역 변수 and 클래스 메서드/인스턴스 메서드
변수의 종류
Java에서 변수의 종류를 결정짓는 중요한 요소는 ‘변수의 선언된 위치’다.
- 인스턴스 변수(Instance variable)
- 인스턴스를 생성할 때 만들어지며, 독립적인 저장 공간을 가진다.
- 인스턴스마다 고유한 값을 가질 수 있다.
- 인스턴스를 생성해야 사용할 수 있다.
- 클래스 변수(class variable)
- 인스턴스 변수 앞에 static을 붙이기만 하면 되므로 스태틱 변수라고도 한다.
- 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지된다.
- 모든 인스턴스가 공통된 저장공간을 공유하므로 한 클래스의 모든 인스턴스들이 공통적인 값을 유지한다.
- 인스턴스를 생성하지 않아도 사용할 수 있다.
- 지역 변수(local variable)
- 메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸된다.
- for문, if문 등의 블럭 {} 내에 선언된 지역 변수는 블럭을 벗어나면 소멸된다.
위 변수들의 생성 시기 및 소멸 시기 등을 이해하려면 JVM의 메모리 구조와 함께 이해해야 한다.
JVM의 메모리 구조
JVM의 구조는 크게 보면, Garbage Collector, Execution Engine, Class Loader, Runtime Data Area로, 4가지로 나눌 수 있다.
본 포스팅에서는 변수와 메서드 유형을 이해하는데 필요한 Runtime Data Area의 Method Area, Heap Area, Stack Area만 따로 보자.
Runtime Data Area는 JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역
- 메서드 영역(method area)
스태틱 영역(static area)라고도 하며, 클래스들의 놀이터다.
프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스 파일을 읽어서 클래스에 대한 정보를 메서드 영역에 저장한다. 이 때, 해당 클래스의 **클래스 변수(class variable)**도 함께 생성된다.
- 힙(heap)
객체들(인스턴스들)의 놀이터다.
프로그램 실행 중 생성되는 인스턴스가 생성되며 인스턴스 변수가 함께 생성된다.
- 호출 스택(call stack)
메서드들의 놀이터다.
메서드가 호출되면 메서드의 작업에 필요한 메모리 공간을 제공한다.
메서드가 수행되는 동안 생성되는 지역 변수들이 저장된다.
메서드가 종료되면 할당되었던 메모리 공간이 반환된다.
클래스 메서드(class method)와 인스턴스 메서드(instance method)
변수와 마찬가지로 메서드 앞에 static이 붙으면 클래스 메서드(또는 스태틱 메서드)고, 붙지 않으면 인스턴스 메서드다. 이 둘의 차이는 단지 인스턴스의 생성 여부다.
클래스 메서드는 클래스 변수와 마찬가지로 인스턴스를 생성하지 않아도 호출할 수 있으며, 인스턴스 메서드는 인스턴스를 먼저 생성해야지만 호출할 수 있다.
클래스를 설계할 때 두 메서드를 나누는 기준이 필요할 텐데, 대부분의 경우 그 기준은 인스턴스 변수의 사용 유무를 보면 된다. 메서드를 선언했는데 내부 로직에서 인스턴스 변수가 필요한 경우는 인스턴스 메서드로 정의하고, 필요 없다면 (웬만하면) 앞에 static을 붙여 클래스 메서드로 정의하는 것이다.
또한 둘은 서로를 호출하는데 있어서도 차이를 보인다. 인스턴스 메서드에서는 로직 내부에서 다른 인스턴스 메서드를 별도의 객체 생성 없이 호출할 수 있으며, 클래스 메서드도 자유롭게 호출 가능하다. 반면, 클래스 메서드에서는 반드시 인스턴스를 생성해야만 인스턴스 메서드를 호출할 수 있다. 왜냐하면 인스턴스 메서드가 존재하는 시점에 클래스 메서드는 항상 존재하지만, 클래스 메서드가 존재하는 시점에 인스턴스 메서드가 존재하지 않을 수도 있기 때문이다. 이는 인스턴스 변수와 클래스 변수를 적용해도 마찬가지이다.
인스턴스 변수는 Heap 영역에 인스턴스가 생성될 때 같이 생성된다. 다른 클래스 혹은 main 메서드에서 클래스 메서드를 호출할 때는 인스턴스가 필요 없기 때문에 애초에 그 클래스의 인스턴스 변수가 메모리 상에 존재하지 않을 수도 있으며 어떤 인스턴스를 가르키는 지도 알 수 없다.
반면에, 다른 클래스 혹은 main 메서드에서 인스턴스 메서드를 호출할 때는 당연히 인스턴스를 생성한 뒤 그 인스턴스로 인스턴스 메서드를 호출하므로 Heap 영역에 인스턴스 변수가 생성되어 있음을 보장할 수 있다.
코드로 보면 아래와 같다.
class MemberCall {
int iv = 10;
static int cv = 20;
int iv2 = cv;
// static int cv2 = iv; // 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.
static int cv2 = new MemberCall().iv; // 이처럼 객체를 생성해야 사용가능.
static void staticMethod1() {
System.out.println(cv);
// System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수를 사용불가.
MemberCall c = new MemberCall();
System.out.println(c.iv); // 객체를 생성한 후에야 인스턴스변수의 참조가능.
}
void instanceMethod1() {
System.out.println(cv);
System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.
}
static void staticMethod2() {
staticMethod1();
// instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드를 호출할 수 없음.
MemberCall c = new MemberCall();
c.instanceMethod1(); // 인스턴스를 생성한 후에야 호출할 수 있음.
}
void instanceMethod2() { // 인스턴스메서드에서는 인스턴스메서드와 클래스메서드
staticMethod1(); // 모두 인스턴스 생성없이 바로 호출이 가능하다.
instanceMethod1();
}
}
// 참고 : <https://github.com/castello/javajungsuk3/blob/master/source/ch6/MemberCall.java>
정리를 하면,
- 클래스 메서드
→ 앞에 static 키워드를 붙임
→ 인스턴스 변수를 사용하지 않음
→ 작업을 수행하는데 필요한 외부 값은 모두 매개변수로 받아서 처리
→ 로직 내부에서 인스턴스 멤버(인스턴스 메서드와 인스턴스 변수)를 사용할 때는 반드시 인스턴스를 생성해야 함
- 인스턴스 메서드
→ 인스턴스 변수를 사용
→ 로직 내부에서 인스턴스 멤버 및 클래스 멤버를 자유롭게 사용 가능
References
- 스프링 입문을 위한 자바 객체지향의 원리와 이해 - 김종민
- Java의 정석 - 남궁성