자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

음... a는 초기화가 선언되지 않았으니 디폴트 값 0일 테고 b는 a에서 1 올린 값과 같아지니 b = 1 가 될 테고 c값은  1인 a를 다시 1 올린 값과 같아지니 c = 2 이 돼서 a = 2, b = 1 c = 2 가 되겠구나!라고 생각할 수 있습니다. 

 

자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

 

그런데 말이죠. 실제로 값을 보니 a =2, b = 2, c = 1입니다 응? 왜 b랑 c의 값이 왜 이러지 라고 생각할 수 있을 것입니다. 

 

사실은 그렇습니다. 클래스 변수와 인스턴스 변수들 중 항상 클래스 변수가 먼저 초기화된 뒤에 인스턴스 변수가 초기화됩니다. 바로 이 부분이 오늘의 핵심 내용인 거죠.

 

보다 정확하게 말하면 클래스 변수는 클래스가 메모리에 올라갈 때 선언되고 인스턴스 변수는 객체가 생성될 때 실행됩니다. 그래서 객체 1을 선언하기 전 이미 a = 1 , c = 1입니다.

 

이를 확인하기 위해 객체를 생성하지 않은 채 아래의 코드를 메인에다가 입력하면  

System.out.println("a = "+variable_lecture.a);

System.out.println("b = "+variable_lecture.c);

 

a = 1

c = 1  이 출력이 됩니다. 

 

이제 객체를 생성하겠습니다. int b = ++a  코드가 실행됩니다. 결과적으로 a = 2, b = 2, c = 1 이 됩니다.  

 

그럼 다시 한번 객체를 생성해보겠습니다. 이제 객체는 총 2개입니다. 그렇다면 2번째 객체를 생성하고 난 뒤의 a, b, c 값은 어떻게 될까요? 그전에 다시 한번 클래스 변수와 인스턴스 변수에 대해 복습해 보자면 클래스 변수는 클래스가 메모리에 올라갈 때 선언되며 모든 객체가 공통으로 가지는 값이라고 했고 인스턴스 변수는 객체가 생성될 때 각 객체마다 가지는 독립적인 값입니다. 

 

2번째 객체를 생성했습니다. 과연  a, b, c의 값이 어떻게 변했을까 살펴보도록 합시다. 

 

자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

 

일단 2번 객체에서 나온 값은 a =3, b = 3, c = 1입니다. 왜 그런 걸까요? 사실은 이미 객체 1개를 생성하기도 전에 

static int a; static int c = ++a; 는 선언되었기 때문에 객체가 생성된다고 다시 이 코드가 실행되지 않습니다. 분명히 클래스가 메모리에 올라갈 때 한번 실행되기 때문이죠.   

 

그래서 객체 2가 생성될 때 int b = ++a; 코드만 실행됩니다. 앞서 a = 2 가 됐기 때문에 b = 3 ,  a = 3 이 됩니다. 

 

다시 한번 이전에 생성했던 객체 1을 살펴보겠습니다. 분명히 a = 2, b = 2, c = 1 이였는데...

 

자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

 

응????  a = 3 이 되었습니다. 근데 b = 2 여전히 변함없습니다. 네 또 한번 말하자면 클래스 변수는 모든 객체가 공통으로 가지는 값입니다. 객체 2로 인해 클래스 변수 a = 3 이 되었고 이는 객체 1 에도 영향을 주기 때문이죠.

 

즉 객체 1이 생성된 후 클래스 변수 a = 2, c = 1입니다. 그리고 객체 2를 생성할 때 a = 2 , c = 1 인 상태에서 시작하게 됩니다. 반대로 인스턴스 변수 b는 값이 초기화되지 않은 상태인 b = 0에서 새로 시작하게 됩니다. 

 

 

 

그리고 마지막으로 다들 아시겠지만 지역변수는 초기화가 반드시 필요합니다. 그리고 메서드 내에서만 존재하고 메서드가 실행될 때 메모리에 올라갔다가 메서드 종료 때 메모리에서 사라져 버립니다. 

 

자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

 

두 메서드에서 int x는 제각기 다른 존재입니다. 그리고 지역변수는 초기화 없이 사용할 수 없습니다. 

 

자바 변수 초기화 이유 - jaba byeonsu chogihwa iyu

지역변수를 초기화하지 않으면 에러가 발생합니다. 지역변수를 반드시 초기화해줘야 하는 이유는 지역변수는 메서드 내에서 계산을 위해 쓰일 목적으로 만들어지는데 초기화되어 있지 않으면 메서드를 실행 시 예기치 않은 값이 될 수 있기 때문입니다. 그래서 컴파일러가 이러한 상황이 발생하는 것을 피하기 위해 프로그래머에게 초기화할 것을 요청하게 됩니다. 

변수를 선언하고 처음으로 값을 저장하는 것을 변수 초기화 라고 한다. 변수의 초기화는 경우에 따라 필수적일수도 선택적일수도 있지만 가능하면 선언과 동시에 적절한 값으로 초기화하는 것이 바람직하다. 멤버변수는 초기화를 하지 않아도 변수의 타입에 맞는 기본값으로 초기화가 이루어지지만 지역변수는 사용하기 전에 반드시 초기화가 이루어져야 한다.

class InitTest {
  int x;      // 인스턴스 변수
  int y = x;  // 인스턴스 변수

  void method() {
    int i;      // 지역변수
    int j = i;  // 에러, 지역변수를 초기화하지 않고 바로 사용했기 때문
  }
}

각 타입의 기본값(default value)

자료형기본값
class InitBlock {

  // 클래스 초기화 블럭
  static {

  }

  // 인스턴스 초기화 블럭
  {

  }
}
8false
class InitBlock {

  // 클래스 초기화 블럭
  static {

  }

  // 인스턴스 초기화 블럭
  {

  }
}
9'\u0000'
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
0,
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
1,
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
20
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
30
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
40.0f
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
50.0d or 0.0renference valuenull

멤버변수를 초기화하는 방법은 다음과 같다.

  • 명시적 초기화(explicit initialization)
  • 생성자(constructor)
  • 초기화 블럭(initialization block)
    • 인스턴스 초기화 블럭 : 인스턴스 변수를 초기화하는데 사용
    • 클래스 초기화 블럭 : 클래스를 초기화하는데 사용

2. 명시적 초기화

변수 선언과 동시에 초기화 하는 것을 명시적 초기화(explicit initialization), 변수의 초기화라고 한다. 가장 기본적이면서도 간단한 초기화 방법이므로 여러 초기화 방법 중에서도 가장 우선적으로 고려되어야 한다.

class Car {
  int door = 4;                 // 기본형 변수의 초기화
  Engine engine = new Engine(); // 참조형 변수의 초기화
}

3. 초기화 블럭

초기화 블럭의 종류에 따라 정리하면 다음과 같다.

  • 클래스 초기화 블럭

    • 클래스를 초기화하는데 사용한다.
    • 클래스가 처음 메모리에 로딩될 때 한번만 수행된다.
  • 인스턴스 초기화 블럭

    • 인스턴스 변수를 초기화하는데 사용한다.
    • 생성자와 같이 인스턴스가 생성될 때 수행된다.
    • 인스턴스 초기화 블럭이 생성자보다 먼저 수행된다.

초기화 블럭 내에는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있으므로, 초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.

class InitBlock {

  // 클래스 초기화 블럭
  static {

  }

  // 인스턴스 초기화 블럭
  {

  }
}

인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행되야 하는 코드를 넣는데 사용 한다. 아래와 같이 모든 생성자에서 공통으로 수행되어야 하는 문장들이 있을 때, 이 문장들을 각 생성자마다 써주기보다는 인스턴스 블럭에 넣어줌으로써 코드가 보다 간결하게 작성할 수 있다.

Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
// 인스턴스 초기화 블럭을 통해 중복 제거
{
  count++;            
  serialNo = count;   
}

Car() {
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  this.color = color;
  this.gearType = gearType;
}

초기화 블럭 예제 1 : 초기화 순서

예제를 통해 초기화 순서가 어떻게 진행되는지 보자.

public class BlockTest {
  public static void main(String[] args) {

    System.out.println("BlockTest blockTest = new BlockTest()");
    BlockTest blockTest = new BlockTest();

    System.out.println();

    System.out.println("BlockTest blockTest2 = new BlockTest()");
    BlockTest blockTest2 = new BlockTest();

  }

  // 클래스 초기화 블럭
  static {
    System.out.println("static{} class init block");
  }

  // 인스턴스 초기화 블럭
  {
    System.out.println("{} : instance init block");
  }

  // 생성자
  public BlockTest() {
    System.out.println("BlockTest() : constructor");
  }
}
static{} class init block
BlockTest blockTest = new BlockTest()
{} : instance init block
BlockTest() : constructor

BlockTest blockTest2 = new BlockTest()
{} : instance init block
BlockTest() : constructor

콘솔화면에서와 같이 클래스 초기화 블럭, 인스턴스 블럭, 생성자 순으로 초기화가 진행된다. 초기화 블럭의 경우 메모리에 로딩될 때 한번만 초기화가 이루어지고, 인스턴스 블럭과 생성자는 인스턴스가 생성될 때마다 초기화가 수행된다.

초기화 블럭 예제 2 :
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
6 초기화 블럭(클래스 초기화 블럭)

public class StaticBlockTest {
  public static void main(String[] args) {
    for (int i = 0; i < arr.length; i++) {
      System.out.println("arr["+i+"] : " + arr[i]);
    }
  }

  // 명시적 배열 초기화
  static int[] arr = new int[10];

  // 클래스 초기화 블럭 : 배열의 각요소들을 random()로 임의의 값을 채움
  static {
    for (int i = 0; i < arr.length; i++) {
      arr[i] = (int) (Math.random() * 10) + 1;
    }
  }
}
arr[0] : 7
arr[1] : 1
arr[2] : 2
arr[3] : 10
arr[4] : 3
arr[5] : 10
arr[6] : 3
arr[7] : 2
arr[8] : 2
arr[9] : 1

위의 예제에서는 명시적 초기화를 통해 배열

Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
7을 생성하고, 클래스 초기화 블럭을 이용해서 배열의 각 요소들을
Car() {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  color = "white";
  gearType = "auto";
}

Car(String color, String gearType) {
  count++;            // 코드 중복
  serialNo = count;   // 코드 중복
  this.color = color;
  this.gearType = gearType;
}
8을 사용해 임의의 값을 채우도록 했다. 이처럼 배열이나 예외처리가 필요한 초기화에는 명시적 초기화만으로는 복잡한 초기화 작업을 할 수 없기 때문에 클래스 초기화 작업을 사용하도록 하는 것이 좋다.