Post

JAVA - Static이란? Final과 비교

Spring 스터디를 하다가 의문점이 생겼다.

Static과 Final의 차이는 뭘까? 사실 깊게 생각하면 확연히 다른데, 헷갈리니 메모를 해야겠다고 생각했다.

아주 좋은 글이 있어서 이해한 내용을 토대로 적어야겠다.

https://djkeh.github.io/articles/Why-should-final-member-variables-be-conventionally-static-in-Java-kor/


final

final키워드는 곧 상수를 의미합니다.

위키피디아에서는 다음과 같이 정의합니다.

Final은 Entity가 오로지 한 번 할당될 수 있음을 의미합니다.

3가지 경우로 나눌 수 있습니다.

  1. Final 변수

    • 해당 변수가 생성자나 대입연산자를 통해 한 번만 초기화 가능합니다. 상수를 만들 때 유용합니다.
    1
    
     private final int value;
    
  2. Final 메소드

    • 해당 메소드를 오버라이드하거나 숨길 수 없음을 의미합니다.
  3. Final 클래스

    • 해당 클래스는 상속할 수 없음을 의미합니다. 문자 그대로 상속 계층 구조에서 ‘마지막’ 클래스입니다.
    • 예를 들어서 java.lang.System, java.lang.String 등이 있습니다.
    1
    2
    
     public final class String{}
     public class examString extends String {} //불가능합니다.
    

세부 분석

  1. Final 멤버 변수 반드시 상수는 아닙니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     public class Test{
         private final int class_value;
    
         public Test(int value){
             this.class_value = value;
         }
    
         public int getValue(){
             return class_value;
         }
    
     }
    
    

    만약 Test라는 인스턴스 객체가 100개 있다면, 각 객체에서 class_value라는 변수가 100개 있을 것이고, 그 값들은 다 다를 것입니다. 그렇기 때문에 클래스 레벨의 ‘상수’라고 할 수는 없습니다. 다만, 각 인스턴스 내부에서 값은 변경할 수 없을 것입니다.

    그래서 상수란 static final을 함께 쓰는 것입니다.

  2. private 메소드와 final 클래스의 모든 메소드는 명시하지 않아도 final처럼 작동합니다.

오버라이드가 불가능하기 때문입니다.

final 클래스는 상속을 금지시키기에 오바라이드가 불가능합니다.

하지만 private에서 final을 명시할 수 있는데, 불필요해도 컴파일러에서 막지 않는 의미는 의미 구분을 위해서 쓰는 경우가 있기 때문입니다.

  • private : 자식 클래스에서 안 보입니다. (오버라이드 금지)
  • final : 자식 클래스에서 보이지만, 오버라이드가 금지됩니다.

비슷한 예로 인터페이스 메소드에 public을 붙이거나, final 클래스 메소드에 final을 붙이는 등의 경우도 문제가 생기지 않습니다.


Static 키워드

static 전역, 정적의 의미로 통용됩니다.

static은 해당 데이터의 메모리 할당을 컴파일 시간에 할당할 것을 의미합니다.

즉, 동적데이터와 달리 프로그램 실행 직후부터 끝날 때까지 메모리 수명이 유지됩니다.

사용법은 다음과 같이 나뉩니다.

  1. static 멤버 변수

    • 클래스 변수라고도 부릅니다.
    • 모든 해당 클래스는 같은 메모리를 공유합니다.
    • 특정한 인스턴스에 종속되지 않습니다.
    • 인스턴스를 만들지 않고 사용 가능합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    
     public class MyCalcu{
         public static int add(int x,int y){
             return x + y
         }
    
         Mycalcu.add(1,2) // 인스턴스 없어도 가능.
     }
    
    
  2. static 메소드

    • 클래스 메소드라고도 부릅니다.
    • 오버라이드 불가합니다.(당연하게도 다양한 인스턴스가 접근이 가능하므로)
    • 상속 클래스에서 보이지 않습니다.
  3. static 블록

    • 클래스 내부에 만들 수 있는 초기화 블록입니다.
    • 클래스가 초기화될 때 실행되고, main() 보다 먼저 수행됩니다.
  4. static 클래스

    • 일반적인 클래스
    • 중첩 클래스(nested 클래스)에만 사용할 수 있습니다.
    • static nested class : static으로 정의된 nested class
    • inner class : static으로 정의되지 않은 nested class
    • 부모클래스의 멤버 필드 중에서 static 필드에만 접근할 수 있습니다.
  5. static import

    • 다른 클래스에 존재하는 static 멤버들을 불러올 때 사용합니다.
    • 멤버 메소드를 곧바로 사용할 수 있습니다.

static 블록 ?

코드 한방이면 이해가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class testmain {
    public static void main(String[] args) {
        Init.b = 3;; //static initializer block 호출
        System.out.println("Init.b = " + Init.b);
        System.out.println("-------- instance 생성 전 --------");
        Init block = new Init();
        System.out.println("--------                 --------");
        new Init();
        System.out.println("-------- instance 생성 후 --------");


    }

}
class Init extends MySuper{
    private int a;
    public int [] arr = new int[5];
    public static int b;

    //instance initializer block(인스턴스 초기화 블럭)
    {
        System.out.println("++++ initializer block 실행 ++++");
        a = 11;
        // b = 22; 불가능

        for(int i = 0 ; i < 5 ; ++i){
            arr[i] = i*3;
        }

        System.out.println("인스턴스 변수 a = " + a);
        System.out.println("인스턴스변수 arr 길이 = " + arr.length);
        System.out.println("클래스 변수 b = " + b);
    }

    //static initialzier block(클래스 초기화 블럭)
    static {
        // a = 1; error
        b = 2;
        System.out.println("Init.b = " + Init.b);
        System.out.println("++++ static initialization block 실행");
    }

    Init(){
        super();
        System.out.println("생성자");
    }

}

class MySuper{
    MySuper(){
        System.out.println("Mysuper의 생성자");
    }
}



결과는 다음과 같습니다.

"error"

참고로 super() 부모 클래스의 생성자를 호출합니다.

사담으로 super는 this처럼 부모 클래스로부터 상속받은 멤버를 참조할 때 사용하는 참조 변수입니다. 부모 클래스와 자식 클래스의 멤버의 이름이 같은 경우 사용합니다.


보통 static final을 합쳐 쓰는데 안 그런 경우가 있을까?

대표적으로 Spring의 DI가 있습니다.

각 인스턴스마다 서로 다른 final 멤버 변수를 생성자에서 초기화 시키는 사용하는 경우입니다.

DI기법을 통해 클래스 내부에 외부 클래스 의존성을 집어 넣는 경우가 있습니다.

1
2
3
4
5
6
7
8
9
10
11
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

자세한건 Spring공부하면서 다시 다루겠습니다.


반대로 static만 사용하면 ?

기술적으로 가능하지만, 동시성 프로그래밍을 어렵게 만들고, 값이 계속 변할 수 있으므로 테스트하기 어려울 수 있다는 단점이 있습니다.

This post is licensed under CC BY 4.0 by the author.