Static, 넌 누구냐?

몇 달전, 모 스타트업 기업의 면접에서 Static이 무엇이냐 질문을 받은 적이 있습니다.
저는 여기서 이렇게 답했죠.

"클래스 전역에서 참조할 수 있게....어버ㅓㅂ"
"안쓰고 main 함수에서 호출하면 자바 컴파일러에서 오류가 뜨기 때문입니ㄷ...."

답변에서 알 수 있듯, 당시의 저는 Static 키워드를 쓰는지, 도대체 Static이 무엇인지 전혀 알지 못하고 쓰고 있었습니다.

그렇기에 오늘은 아픈 옛 기억을 되살리면서 Static이 무엇이고 왜 쓰는건지 간단한 코드를 통해 알아보고자 합니다.

Static

Static의 의미를 구글에 검색해 보면 다음과 같은 답변이 나옵니다.

the particular member belongs to a type itself, rather than to an instance of that type
(타입 그 자체에 속하는 특정 멤버다, 그 타입의 인스턴스라기 보다는..)

static으로 선언한 변수와 메서드는 각각 클래스 변수, 클래스 메서드라고 불립니다.
일반적으로 저희가 생성한 클래스는 Static 이라는 영역에 저장되고, new를 통해 생성된 인스턴스는 Heap 영역에 저장됩니다.
우리가 작성한 클래스나, 해당 클래스 안에서 static 키워드로 선언된 멤버 변수는 프로그램이 시작될 때 딱 한 번만 메모리에 저장되게 됩니다. 반면에 클래스의 인스턴스는 호출되는 시점에 Heap 영역에 저장되게 됩니다.

Static vs Heap

이 두 영역의 큰 차이점은 GC(가비지 콜렉터)의 수거 대상이 되냐의 차이가 있습니다.
Static 영역에 할당된 값은 GC의 관리 영역 밖에 있으므로 많이 사용할 경우 메모리 낭비가 생길 수 있다는 특징이 있습니다.
대신, 한 번 선언해놓으면 여러 곳에서 요긴하게 쓸 수 있는 이점이 있습니다.

Heap 영역에 할당된 값은 쓰레기차가 정기적으로 수거해 가기 때문에 메모리 측면에서 이점이 있습니다. 그러나 하나의 클래스 타입에 여러 인스턴스를 할당하게 되면, 여러 값들이 Heap 영역에 할당이 되기 때문에 이 역시도 메모리 낭비가 있을 수 있습니다. 또한 GC가 쓰레기를 수거해 가는 작업도 결국 시스템 성능에 영향을 주기 때문에 신경을 쓰셔야 합니다.

코드로 알아볼까요?

다음과 같이 클래스를 작성해 보고 main 함수에서 불러와보겠습니다.

class Ddong {
    static String DEFAULT_NAME = "똥";
    String name;

    Ddong(String name) {
        this.name = name;
    }

    static void run() {
        System.out.println("뿌지직");
    }

    void Seolsa() {
        System.out.println("뿌지지지지지지직");
    }
}

public class Main {
    public static void main(String[] args) {
		System.out.println(Ddong.DEFAULT_NAME); -- (1)
        System.out.println(Ddong.name); -- (2)
        Ddong.run(); -- (3)
        Ddong.Seolsa(); -- (4)
    }
}

1, 2, 3, 4 번의 결과값은 각각 어떻게 될까요?

일단 프로그램이 시작되면, Static 메모리에 Ddong 클래스와 그의 클래스 변수인 DEFAULT_NAME, 클래스 메서드인 run() 이 Static 메모리 영역에 올라가게 됩니다.

여기서 Main 클래스와 main() 함수 또한 static 영역에 저장됩니다.

이 시점에서 (1)번 로직을 실행시키면 이미 Static 영역에 DEFAULT_NAME이 있기 때문에 정상적으로 사용할 수 있습니다.

(2)번 로직은 어떤가요? 아마도 컴파일러가 호다닥 오류를 알려줬을 것 같은데요. 오류를 확인해봅시다.

Non-static field 'name' cannot be referenced from a static context
해석) main() 함수는 static context인데, 왜 여기서 있지도 않은 Non-static field인 name을 참조하려 하는것이여!

한 마디로 집에 오자마자 마중 나오지도 않은 뽀삐를 찾는 꼴이죠. 여기서 뽀삐를 챙기려면, 뽀삐를 애초에 들고 다니거나(static), 뽀삐를 호출하면(new) 되죠.

클래스 내의 name과, Seolsa()는 Non-Static Field로 인스턴스를 생성한 이후에 복사되는 인스턴스 변수, 인스턴스 메서드입니다.

따라서 이 변수, 메서드를 성공적으로 호출하려면 다음과 같이 인스턴스를 만들고 호출하시면 됩니다.

Ddong ddong = new Ddong("ㄸㅗㅇ");
System.out.println(ddong.name);
ddong.Seolsa();

여기서 다음과 같이 만약 인스턴스를 여러개 생성하고 ddong1의 name값을 바꾼다면 어떻게 될까요?

Ddong ddong = new Ddong("ㄸㅗㅇ");
Ddong ddong2 = new Ddong("ㄸㅗㅇ");
Ddong ddong3 = new Ddong("ㄸㅗㅇ");
System.out.println(ddong.name);
ddong2.name = "똥2";
System.out.println(ddong.name);

당연히 ddong2의 이름을 바꾸고 ddong의 이름을 호출했으니 "ㄸㅗㅇ"이 출력되겠죠?

반면에 static 키워드를 붙인 클래스 변수를 바꿔봅시다.


Ddong ddong = new Ddong("ㄸㅗㅇ");
Ddong ddong2 = new Ddong("ㄸㅗㅇ");
Ddong ddong3 = new Ddong("ㄸㅗㅇ");
System.out.println(ddong.DEFAULT_NAME);
ddong2.DEFAULT_NAME = "똥2";
System.out.println(ddong.DEFAULT_NAME);

<Console>
똥
똥2

반면에 static 키워드를 붙인 클래스 변수는 static영역에서의 같은 주소값을 참조하기 때문에 다른 인스턴스에서 바꾼 내용도 바뀌게 되는 것입니다.

철수, 유리 개개인의 이름을 바꾸는건 서로 영향을 안주지만, 교실에 있는 칠판 내용을 지우는건 모두에게 영향을 주는 법이니까요.

결국 사용자의 용도에 맞게 클래스 변수와 인스턴스 변수를 적절히 사용하는 것이 중요해보입니다. 다음에는 Kotlin의 Object와도 어떻게 다른지 시간이 있다면 포스팅해보겠습니다.

References

https://mi-nya.tistory.com/251 자바 Static 정리
https://opentutorials.org/course/4074/27016 생활 코딩 - 객체 지향 프로그래밍

0개의 댓글