김찬진의 개발 블로그

23/02/09 [인스턴스변수? 클래스변수? 지역변수?] 본문

1일1배움/Java

23/02/09 [인스턴스변수? 클래스변수? 지역변수?]

kim chan jin 2023. 2. 9. 15:29

인스턴스변수, 클래스변수, 지역변수?

인스턴스변수

  • 인스턴스가 생성될 때 생성된다. 인스턴스변수의 값으르 읽거나 저장하려면 생성자를 사용하여 인스턴스를 생성해야 한다. 인스턴스별로 다른 값을 가질 수 있으므로 각각의 인스턴스마다 고유의 값을 가져야 할 때에는 인스턴스변수로 선언한다.
  • 인스턴스이름.인스턴스변수명을 통해서 접근한다.

클래스변수

  • 인스턴스변수는 생성자를 사용하여 고유의 값으로 초기화할 수 있는 반면 클래스변수는 모든 인스턴스가 공통된 값을 공유한다. 따라서 한 클래스의 모든 인스턴스들이 공통적인 값을 가져야 할 때 클래스변수로 선언한다.
  • 클래스변수는 생성자를 사용하지 않고도 초기화할 수 있다. (기본값, 명시적초기화, 클래스 초기화블럭) 하지만 생성자로 클래스변수 값을 다시 초기화해줄 수는 있다. 한 클래스 내에서 공유하는 클래스변수 값만 바뀔 뿐이다.
  • 클래스가 로딩될 때 생성되어(메모리에 딱 한번만 올라간다.) 종료될 때까지 유지되는 클래스변수는 public을 붙이면 같은 프로그램 내에서 어디서든 접근할 수 있는 전역변수가 된다.
  • 또한 인스턴스 변수의 접근법과 다르게 인스턴스를 생성하지 않고 클래스이름.클래스변수명을 통해서 접근할 수 있다.
  • 클래스변수는 모든 인스턴스가 공통된 값을 공유하므로 여러번 초기화되더라도 가장 나중에 초기화된 값으로 공유된다.

지역변수

  • 메서드 내에서  선언되며 메서드 내에서만 사용할 수 있는 변수.
  • 메서드가 실행될 때 메모리를 할당 받으며 메서드가 끝나면 소멸되어 사용할 수 없게 된다.

 

 

 

인스턴스변수와 클래스변수는 무슨 차이?

인스턴스변수는 인스턴스별로 다른 값을 가질 수 있다.

클래스변수는 모든 인스턴스가 공유하는 값이다.

 

 

 

 


 

 

 

 

변수의 초기화 방법과 초기화 시기

변수를 초기화하는 방법은 4가지가 있다.

 

1. 기본값 (초기화하지 않을 때)

2. 명시적 초기화

3. (인스턴스 또는 클래스) 초기화블럭

4. 생성자

 

만약 4가지 방식을 모두 사용하여 변수를 초기화한다면 어떤 값으로 초기화했다고 봐야할까?

인스턴스변수 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화블럭 -> 생성자
클래스변수 : 기본값 -> 명시적 초기화 -> 클래스 초기화블럭

* 클래스변수를 생성자에 넣고 초기화해도 되긴 하는데, 그건 클래스변수의 본래 의미를 퇴색시킨다. 그래서
클래스변수의 초기화 순서에 생성자가 빠진 것이다.
* 하지만 아래의 코드에서는 생성자에 클래스변수를 넣고 초기화시켰을 때 어떤 결과가 나오는지 알아보기 위해 의도적으로 생성자에 클래스변수를 넣어 초기화시켰다. 마치 기본값 -> 명시적 초기화 -> 클래스 초기화블럭 -> 생성자 순서로 클래스변수가 초기화되는 것 같은 결과를 보여준다.

 

코드로 확인해보자

public class Test{
    int x = 1; // 인스턴스 변수 명시적 초기화
    int y = x; // 인스턴스 변수 명시적 초기화
    static int z = 2; // 클래스 변수 명시적 초기화
	
    // 인스턴스 초기화블럭
    { 
        x = 999; // 1번째 ~ 4번째 각 생성자를 사용하여 인스턴스 생성할 때마다 수행됨
    }
	
    // 클래스 초기화블럭
    static{
        z=888;
    }

    Test(){} // 생성자
        // x == 999 // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 999 -> 1번째 생성자 내 없음 999
        // y == 1 // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 내 없음 1 -> 생성자 내 없음 1
        // z == 9 // 기본값 0 -> 명시적초기화 2 -> 클래스초기화블럭 888 -> 4번째 생성자 9

    Test(int x){ // 생성자
        this.x = x; // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 999 -> 2번째 생성자 4
        // y == 1 // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 내 없음 1 -> 2번째 생성자 내 없음 1
        // z == 9 // 기본값 0 -> 명시적초기화 2 -> 클래스초기화블럭 888 -> 4번째 생성자 9
    }

    Test(int x, int y){ // 생성자
        this.x = x; // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 999 -> 3번째 생성자 5
        this.y = y; // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 내 없음 1 -> 3번째 생성자 6
        // z = 9 // 기본값 0 -> 명시적초기화 2 -> 클래스초기화블럭 888 -> 4번째 생성자 9
    }

    Test(int x, int y, int z){ // 생성자
        this.x = x; // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 999 -> 4번째 생성자 7
        this.y = y; // 기본값 0 -> 명시적초기화 1 -> 인스턴스초기화블럭 내 없음 1 -> 4번째 생성자 8
        this.z = z; // 기본값 0 -> 명시적초기화 2 -> 클래스초기화블럭 888 -> 4번째 생성자 9
    }

    public static void main(String[] args) {
        Test t0 = new Test();      // 1번째 생성자
        Test t1 = new Test(4);     // 2번째 생성자
        Test t2 = new Test(5,6);   // 3번째 생성자
        Test t3 = new Test(7,8,9); // 4번째 생성자

        System.out.println(t0.x + " " + t0.y + " " + t0.z); // 999 1 9
        System.out.println(t1.x + " " + t1.y + " " + t1.z); // 4 1 9
        System.out.println(t2.x + " " + t2.y + " " + t2.z); // 5 6 9
        System.out.println(t3.x + " " + t3.y + " " + t3.z); // 7 8 9
    }
}

참고) 각 타입의 기본값

자료형 기본값
boolean false
char '\u0000'
byte, short, int 0
long 0L
float 0.0f
double 0.0d 또는 0.0
참조형 변수 null

 

 

 

 

 

 

초기화블럭의 용도

인스턴스 초기화블럭: 인스턴스변수의 복잡한 초기화(연산, 반복문 등)에 사용한다. 인스턴스가 생성될 때마다 기본값 -> 명시적 초기화 -> 인스턴스 초기화블럭 -> 생성자 순서로 수행된다. 

클래스 초기화블럭: 클래스변수의 복잡한 초기화(연산, 반복문 등)에 사용한다. 클래스가 처음에 메모리에 단 한번 올라갈 때 기본값 -> 명시적 초기화 -> 클래스 초기화블럭 순서로 수행된다.
public class Test{
    static {
        System.out.println("클래스 초기화블럭 실행");
        System.out.println("");
    }

    {
        System.out.println("인스턴스 초기화블럭 실행");
    }

    public Test(){
        System.out.println("생성자 실행");
    }

    public static void main(String[] args) {
        System.out.println("Test t1 = new Test()");
        Test t1 = new Test();
        System.out.println("");

        System.out.println("Test t2 = new Test()");
        Test t2 = new Test();

        //  클래스 초기화블럭 실행
        //
        //  Test t1 = new Test()
        //  인스턴스 초기화블럭 실행
        //  생성자 실행
        //
        //  Test t2 = new Test()
        //  인스턴스 초기화블럭 실행
        //  생성자 실행
    }
}

기본값도 없고

명시적 초기화는 없지만

클래스 초기화블럭, 인스턴스 초기화블럭

생성자 

순서로 초기화되는 것을 확인할 수 있다.

 

클래스 초기화블럭이 어떻게 클래스변수의 복잡한 초기화를 해주는지 코드로 확인해보자

class Test{
    static int[] arr = new int[10];

    static{
        for(int i=0; i<arr.length; i++){
            arr[i] = (int)(Math.random()*10) + 1;
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<arr.length; i++){
            System.out.println("arr[" + i +"]: " + arr[i]);
        }
    }
    // arr[0]: 8 (임의의 값)
    // arr[1]: 1 (임의의 값)
    // arr[2]: 6 (임의의 값)
    // arr[3]: 5 (임의의 값)
    // arr[4]: 7 (임의의 값)
    // arr[5]: 1 (임의의 값) 
    // arr[6]: 5 (임의의 값)
    // arr[7]: 8 (임의의 값)
    // arr[8]: 9 (임의의 값)
    // arr[9]: 10 (임의의 값)
}

 

인스턴스 초기화블럭이 어떻게 인스턴스변수의 복잡한 초기화를 해주는지 코드로 확인해보자

class Test{
    static int count = 0;
    int serialNo;
    
    // 인스턴스 생성될 때마다 수행
    {
        ++count;
        serialNo = count;
    }

    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        Test t3 = new Test();

        System.out.println("t1의 제품번호(serial no)는 " + t1.serialNo); // 0+1=1
        System.out.println("t2의 제품번호(serial no)는 " + t2.serialNo); // 1+1=2
        System.out.println("t3의 제품번호(serial no)는 " + t3.serialNo); // 2+1=3
        System.out.println("생상된 제품의 수는 모두 " + Test.count + "개 입니다."); // 3
    }
}

 

 

 

 


 

 

 

 

인스턴스 초기화블럭과 클래스 초기화블럭은 무슨 차이?

두 초기화블럭 모두 변수의 복잡한 초기화를 하기 위한 목적이다.

 

차이점은 

인스턴스 초기화블럭은 인스턴스가 생성될 때마다 수행되는 반면,

클래스 초기화블럭은 클래스가 처음에 메모리에 단 한번 올라갈 때 수행된다는 것이다. 

 

 

 

 

 

 

인스턴스 초기화블럭에서 클래스 변수를 사용할 수 있지만,

클래스 초기화블럭에서 인스턴스 변수를 사용할 수 없는 이유?

그 이유는 클래스변수는 클래스가 처음에 메모리에 단 한번 올라갈 때 기본값 -> 명시적 초기화 -> 클래스 초기화블럭 순서로 단 한번 초기화되는데, 

클래스변수가 초기화되는 시점에 인스턴스는 만들어지지 않았기 때문이다.

클래스변수가 초기화된 이후에 생성자를 호출하여 인스턴스가 만들어진 그 때 비로소 인스턴스변수를 사용할 수 있는 것이다.

 

 

 

 


 

 

 

 

추가) 인스턴스변수는 초기화하지 않고 사용해도 되는 반면, 지역변수는 초기화한 이후에 사용해야만 하는 이유는 무엇일까?

class InitTest{
    int x;       // 인스턴스변수
    int y = x;   // 인스턴스변수는 초기화하지 않고 사용 가능
    static int z // 클래스변수
    
    void method1(){
        int i;        // 지역변수
        // int j = i; // 에러, 지역변수는 초기화한 이후 사용해야 함
    }
}

인스턴스변수는 초기화를 하지 않아도 자동적으로 기본값으로 초기화되는 반면,

지역변수는 초기화를 하지 않으면 자동적으로 기본값으로 초기화되지 않기 때문이다.

 

변수의 초기화의 예

설명
int i = 10, j = 10; 같은 타입의 변수는 콤마(,)를 사용해서 함께 선언하거나 초기화할 수 있음
int i = 10, long j = 0; 에러. 타입이 다른 변수는 함께 선언하거나 초기화할 수 없음
int i = 10;
int j = i;
변수 i를 10으로 초기화
변수j를 i로 초기화 즉, 10으로 초기화
int j= i;
int i = 10;
에러. 변수 i가 선언되기 전에 i를 사용할 수 없음

 

 

 

 

 

 

 

 

Comments