[자바/Java] Static 이란?

resilient

·

2022. 3. 16. 17:47

728x90
반응형

최근 싱글톤 패턴에 대해 공부하게 되었습니다. 싱글톤을 이해하기 위해서는 Static의 개념을 확실히 알고 있어야 한다는 블로그 글을 보았고, static에 대해서 이번 기회에 확실히 정리해보려고 합니다. 

 

0. 먼저, Static이란?

 

public static void main(String[] args)

Java를 사용하다보면 정말 많이 보는 키워드가 하나 있습니다. 바로 Static인데요. main함수를 선언할 때도 사용하는 아주 익숙한 키워드입니다. 

 

Static을 사용한다는 것은 메모리에 한번 할당되고 프로그램이 종료될 때 해제된다는 것을 의미합니다. 메모리 영역과 관련지어서 설명을 해보겠습니다.

 

사진출처: 망나니개발자님 블로그

 

먼저 JVM구조에서 메모리 영역을 보면 Static영역과 Heap영역, Stack영역이 있습니다. 여기서 Static영역과 Heap영역을 따로 빼서 아래 사진으로 살펴보겠습니다.

 

우리가 만드는 Class들은 Static영역에 생성됩니다. 이 클래스들을 new 연산을 통해 객체를 만들어서 사용하는데 생성된 객체는 Heap영역으로 들어가죠. Heap영역의 메모리는 Garbage Collector(GC)에 의해서 정리를 받습니다. 하지만 Static 키워드를 통해서 Static영역에 할당된 메모리는 모든 객체들의 공유할 수 있다는 장점을 갖지만, GC의 관리를 받지 못하고, 정리 또한 받지 못하게 되죠. 때문에 Static키워드를 많이 사용하면 프로그램이 종료될 때까지 메모리가 할당된 채로 존재하게 되므로 비효율적이라고 할 수 있습니다.

 

1. Static 변수 (정적 변수) 란?

 

말 그대로 프로그램이 실행될 때 메모리에 고정적으로 할당되어, 프로그램이 종료될 때 해제되는 변수입니다. Static을 사용하는 이유로는 크게 두 가지가 있는데요. 메모리 효율성과 공유 개념을 들 수 있습니다.

 

1-1. 먼저 Static 변수의 메모리 효율성에 대해 알아보겠습니다.

 

예를 들어, 아래와 같이 성이 "신" 씨인 집안 클래스가 있다고 가정해보겠습니다.

class HouseShin {
    String lastname = "신";
}

public class Sample {
    public static void main(String[] args) {
        HouseShin skc = new HouseShin();
        HouseShin skd = new HouseShin();
    }
}

 

위와 같이 클래스를 만들고 main함수에서 객체를 생성하면 객체마다 객체변수인 lastname을 저장하기 위한 메모리가 각각 할당됩니다. 하지만 잘 생각해보면 성은 다 똑같은데 lastname을 각각 할당해야 할 필요가 있을까요? 모두 lastname = "신" 일 텐데요.

 

이렇게 항상 값이 변하지 않고 고정되는 경우에 Static을 사용하면 메모리를 효율적으로 사용할 수 있습니다. 아래 예시를 보겠습니다.

 

class HouseShin {
    static String lastname = "신";
}

public class Sample {
    public static void main(String[] args) {
        HouseShin skc = new HouseShin();
        HouseShin skd = new HouseShin();
    }
}

 

위와 같이 HouseShin클래스 안의  lastname변수에 Static키워드를 붙여주면 됩니다. 이렇게 되면 자바는 lastname에 메모리 할당을 딱 한 번만 해주기 때문에 메모리를 아껴서 사용할 수 있죠. 

 

만약 HouseShin 클래스의 lastname 값이 앞으로도 변경되지 않는다면 Static 키워드 앞에 final을 붙여주면 됩니다. 바로 final static 키워드이죠. 아시다시피 final 키워드는 한 번 설정되면 그 값을 변경할 수 없는 상수를 선언하는 키워드입니다.

 

1-2. 다음으로는 Static의 공유 기능에 대해 알아보겠습니다.

 

위에서 설명한 내용의 연장선으로, Static으로 설정하면 같은 곳의 메모리 주소만을 바라보기 때문에 Static 변수의 값을 공유할 수 있게 됩니다. 아래 예시는 한번 클릭했을 때 Count가 증가하는 Counter클래스입니다.

 

class Counter  {
    int count = 0;
    Counter() {
        this.count++;
        System.out.println(this.count);
    }
}

public class Sample {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
    }
}

 

위 Counter 클래스를 실행하면1,1의 결과 값이 나옵니다. c1, c2 객체를 생성할 때 생성자에서 객체 변수의 count값을 1씩 증가시키더라도 c1과 c2의 count변수는 서로 다른 메모리를 가리키고 있기 때문에 원하는 값이 나오지 않죠. 객체 변수는 항상 독립적인 값을 가져야 하기 때문에 당연한 결과가 나온 것입니다. 자, 이제 원하는 결과가 나오게끔 수정을 해보겠습니다.

 

class Counter  {
    static int count = 0;
    Counter() {
        count++;  // count는 더이상 객체변수가 아니므로 this를 제거
        System.out.println(count);
    }
}

public class Sample {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
    }
}

위와 같이 count에 Static키워드를 붙여줬습니다. count는 더 이상 객체 변수가 아니게 되고, 객체를 생성할 때 count를 가리키는 메모리는 모두 같습니다. 따라서 결과 값은 1,2 가 나오게 됩니다.

 

일반적으로 Static 변수는 상수들만 모아서 사용을 하고 상수의 변수명은 대문자와_를 조합해서 이름을 짓습니다. 또한 상속을 방지하게 위해 대부분 final class로 선언을 하죠.

 

2. Static 메서드

 

Static 메서드는 객체의 생성 없이 호출이 가능하고, 객체에서의 호출이 가능은 하지만 지양하고 있습니다. 일반적으로 유틸리티 관련 함수들이 Static메서드로 구현을 하는 것이 적합하죠.

 

아래 예시를 보겠습니다.

class Counter  {
    static int count = 0;
    // int count = 0; 은 안된다.
    Counter() {
        count++;
        System.out.println(count);
    }

    public static int getCount() {
        return count;
    }
}

public class Sample {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();

        System.out.println(Counter.getCount());  // 스태틱 메서드는 클래스를 이용하여 호출
    }
}

 

Counter 클래스에 getCount()라는 Static 메서드를 작성해봤습니다. 메소드 앞에 Static을 붙이면 객체 생성없이 클래스를 통해 메소드를 직접 호출할 수 있습니다. static int count = 0 아래에 주석으로 int count = 0 은 안된다라고 작성해놨습니다. 왜 안될까요? 아래의 예시를 통해 알아보겠습니다.

 

public class Test {
    private String name1 = "skc";
    private static String name2 = "skd";

    public static void printName(){
        // System.out.println(name1); 불가능한 호출 
        System.out.println(name2);
    }
}

 

Static메서드에서 접근하기 위한 변수는 반드시 static변수로 선언되어야 합니다. 이유는 간단합니다.

 

name1은 new연산을 통해 객체가 생성된 후에 메모리가 할당되는데 printName은 Static메소드 이기 때문에 객체의 생성 없이 접근할 수 있는 함수입니다. 따라서 할당되지 않은 메모리 영역에 접근을 하려고 하기 때문에 문제가 발생하게 되고 name1을 호출할 수 없는 것이죠.

 

부모 클래스의 Static메서드를 자식 클래스에서 다시 정의하면 오버라이딩(Overriding)이 아닌 하이딩(Hiding)이 되어 해당 메소드의 호출 환경에 따라 2개의 메소드를 모두 사용할 수 있게 되어버립니다. 때문에 Static 메서드도 Static변수와 마찬가지로 상속을 방지하기 위해 final class로 선언을 하고 대부분 유틸 관련된 함수를 모아둘 때 많이 사용합니다. 

 

3. 정리

 

이번 시간에는 Static이 무엇인지에 대해서 자세히 살펴보는 시간을 가졌습니다. 다음 시간에는 Static을 활용한 Class와 싱글톤 패턴이 어떤 관계가 있는지, 둘의 특징이 무엇인지에 대해서 알아보겠습니다.

 

감사합니다.

반응형