본문 바로가기
Java/Java Basic

[Java Basic] 15. 클래스 생성자와 초기화블럭

by Rosmary 2022. 7. 10.
728x90
반응형

 

 

 

 

필자가 이전에 클래스에 대한 첫 포스팅을 진행하면서, 신호등에 대한 객체를 정의한 클래스를 예시로 들었다. 신호등 클래스를 선언하면서, 객체의 특성을 나타내는 인스턴스 변수들을 클래스 내에 미리 정의해주었다. 그리고 특성 중 변경이 필요한 내용은, 특성을 정의한 변수를 인스턴스를 통해 직접 변경을 진행해주어야 한다. 

 

 

 

 

그런데, 각 신호등의 색상을 인스턴스화 한 뒤 하나씩 바꿔주려니 코드도 길어진다. 그리고 클래스 내 특성이 수 십 개 정의되어 있고, 이들을 직접 하나하나 코드로 변경해야한다면 인스턴스 변수값 변경 코드만으로도 몇 백 줄은 금방 채울 수 있을 것이다. 클래스로부터 인스턴스 생성 시, 인스턴스 특성 값을 생성과 동시에 지정할 수 있는 방법은 없을까? 

 

또한 필자는 신호등의 인스턴스가 생성되면, print_info() 매서드가 자동으로 출력되도록 만들고 싶다. 이 역시 생성한 인스턴스를 통해야만 매서드를 사용할 수 있기 때문에 각 인스턴스마다 매서드 실행 코드를 작성해야 한다는 불편함이 있다.

 

이번 포스팅에서 다룰 생성자와 클래스 초기화는 위의 두 문제를 말끔하게 해결할 수 있는 솔루션이다. 하나씩 알아보자.

 

 

 

1. 생성자(Constructor)

 

Java의 클래스는 생성자라는 것을 제공한다. 클래스로부터 인스턴스를 만들 때 아래의 코드 형태를 띈다.

 

[인스턴스화 코드 포맷]

클래스명 인스턴스변수명 = new 클래스명 ();

 

인스턴스변수명에 저장되는 값은 new 클래스명()인데, 매서드 관련 포스팅을 보셨던 분들이라면 이 모양이 마치 매서드 호출하는 코드와 매우 유사한 것을 알 수 있다.

 

[매서드 호출 코드 포맷]

매서드명 (입력인자);

 

위의 스크린샷 예시에서 new SignalLight() 부분을 보면, "저 소괄호 내에 입력 인자를 넣을 수 있지 않을까"라는 생각이 든다. 정답을 먼저 말하자면 위의 내용은 가능하다. 대신 이 내용이 클래스 내에 정의가 되어 있어야 한다.

 

그럼, SignalLight()는 어떻게 실행되는 것일까? 필자는 클래스 내에 어디에도 SignalLight라는 이름으로 매서드를 생성한 적이 없는데 말이다. 사실 Java는 클래스 생성 시, 클래스 내부에 SignalLight()라는 생성자를 기본값으로 생성한다.

 

 

 

 

즉, 생성자는 인스턴스 생성에 필요한 기본값을 정의하는 일종의 매서드와 유사한 역할을 하는 녀석이라 보면 된다. 단, 매서드와의 차이점은 생성자 정의 시 반환값을 별도로 정의해주지 않으며, 반드시 생성자 이름이 클래스명과 동일해야 한다는 것이다.

 

class 클래스명

{

    클래스명(입력인자)

}

 

그런데, 위와 같이 기본 생성자로만 클래스가 정의된 경우, 인스턴스 생성 시 입력인자를 소괄호 내에 지정할 수 없다. 별도로 정의가 되어 있지 않기 때문에.

 

 

 

 

어느 정도 에러를 읽을 수 있는 분들이라면 이제 무엇이 원인인지 알 것이다. 클래스 내의 기본 생성자 내용을 수정해주면 된다.

 

 

 

 

여기서 조금 더 내용을 확장해보자. 필자는 인자값이 입력되지 않았을 때 생성되는 인스턴스는 색상이 빨간색을 유지하도록 정의하고 싶다. 만약 위의 코드에서 필자가 new SignalLight(); 코드를 작성하면 에러가 발생한다. 

 

 

 

생성자의 경우도 매서드와 동일하게 오버로딩이 적용된다. 따라서 생성자에 대해 입력 인자의 수 또는 데이터 타입이 다른 생성자를 추가로 정의해준다면, 위의 에러는 더 이상 발생하지 않게 된다. 생성자에 대한 내용은 이 포스팅을 참고하자.

 

 

 

 

그런데, 생성자 역시 인자가 늘어날수록 번거로워지는 부분이 있다. Class에 속성 및 생성자를 몇 개 더 추가해보자.

 

 

생성자의 수가 늘어날수록 중복되는 코드 또한 늘어나는 것을 확인할 수 있다. 다행히 Java의 생성자 내 코드에서는 다른 생성자 코드를 호출하여 중복 코드를 감소시킬 수 있는 방법을 제공한다.

 

생성자 내에서 다른 생성자를 호출하는 코드는 this() 라는 예약어를 사용한다. 위의 예시를 보면 this()의 괄호안에 생성자에 정의된 입력 매개변수가 지정된 것을 확인할 수 있다. this()로 다른 생성자의 내용을 호출함으로써 생성자 정의 코드 중 중복되는 내용을 감소시킬 수 있다. 

 

this()는 반드시 생성자 코드의 맨 앞에 작성되어야 한다. 아래의 스크린샷은 this()로 다른 생성자를 호출하는 코드를 두 번째 줄에 작성했을 때 에러가 발생하는 원인에 대해 출력되는 내용이다. "생성자 호출은 반드시 생성자 코드의 첫 줄에 위치해야한다"

 

 

 

 

 

2. this 예약어

 

String 입력 인자가 포함된 생성자를 보자. 클래스의 인스턴스 변수인 color는 입력값인 color_input으로 대체된다. 따라서 입력값의 변수명과 인스턴스 변수명은 동일하게 지정하는 것이 효율적이다. 안 그러면 Java 프로그래머들은 코딩보다도 변수명을 만드는 것에 더 많은 시간을 보내게 될 수도 있기 때문이다. 

 

그런데, 인스턴스 변수명과 입력값 변수명을 동일하게 지정하자니, 문제가 생긴다.

 

 

 

 

color = color에서 Java는 어떤것이 인스턴스 변수이고, 어떤 것이 입력 변수인지 알 수 있을까? 모른다. 이를 명확하게 구분하기 위해 this 라는 예약어가 사용된다.

 

this는 클래스로부터 생성되는 인스턴스가 존재하는 경우, 클래스 생성자 또는 클래스 매서드 내에서 해당 인스턴스를 나타내는 예약어다. 따라서 생성자 내에서는 this를 사용하여 인스턴스 변수에 접근할 수 있지만, 생성자나 매서드 외부에서는 this라는 에약어를 Java가 전혀 인식하지 못한다.

 

 

 

 

전체적인 코드에 this 예약어를 추가하여 편집하면 아래와 같이 변경할 수 있다.

 

 

 

 

3. 클래스 초기화 블럭

 

생성자와 this 예약어를 활용하면 객체 인스턴스 생성 시 각 인스턴스의 특성 값을 미리 지정할 수 있는 것을 확인했다. 그럼, print_info()와 같은 매서드도 인스턴스 생성 시 바로 실행되도록 만들 수 있을까?

 

지금까지 배운 내용을 토대로 하면 단순히 클래스 매서드인 print_info()를 오버로딩 생성자 내에 추가하면 된다.

 

 

 

하지만 위의 방법 역시, 생성자의 수가 늘어나면 늘어날수록 추가해야하는 매서드 호출 코드가 늘어나게되고, 추후 매서드 이름에 변경이 가해져야하는 상황이 오면 이를 수정하기가 꽤나 번거로워진다. 

 

Java의 클래스는 초기화 블럭이라는 것을 제공한다. 초기화 블럭이란, 클래스가 메모리의 메서드 에어리어(Method Area)에 로딩될 때, 또는 클래스로부터 인스턴스가 생성되어 Heap에 적재된 직후, 공통적으로 진행할 내용을 정의하는 부분이라고 보면 된다. 

 

클래스 초기화 블럭은 static 초기화 블럭과 인스턴스 초기화 블럭으로 나뉘며, 사용법은 아래와 같다.

 

 

[static 초기화 블럭]

class 클래스명

{

    static {   클래스 생성 시 수행할 코드 작성;  }

}

 

[인스턴스 초기화 블럭]

class 클래스명

{

    {   클래스 생성 시 수행할 코드 작성;  }

}

 

 

static이 붙은 초기화 블럭은 클래스가 메모리에 로딩될 때 딱 한 번 실행되는 코드며, 인스턴스 초기화블럭은 인스턴스 생성 시 수행되는 코드다. 따라서 아래의 예시와 같이 static 초기화 블럭에 정의된 내용은 단 한 번만 실행되며, 필자가 생성한 SignalLight의 인스턴스 수와 동일하게 일반 초기화 블럭이 출력된다.

 

 

 

이제 위의 코드에서 인스턴스 초기화 블럭 안에 print_info() 매서드를 추가해보자. 아마 모든 인스턴스가 색상 특성을 red로 가지고 있을 것이 확인될텐데, 이를 통해 인스턴스 초기화 블럭은 main 매서드 내에서 클래스 생성자가 실행되기 전에 수행되는 것을 알 수 있다.

 

 

 

참고로 클래스 코드 내에서 초기화 블럭의 위치도 매우 중요하다. 특히 클래스 변수와의 위치에 따라 초기화 블럭이 제대로 된 값을 출력하지 못하는 경우도 있다. 아래의 예시를 보자. 필자가 초기화 블럭의 위치를 클래스 변수보다 앞에 위치한 코드다.

 

 

 

인스턴스 변수가 초기화되지 않았음에도 기본값으로 지정된 것을 확인할 수 있는데, 클래스 내에 위치하는 인스턴스 변수는 별도로 리터럴 값을 명시해주지 않더라도 데이터 타입의 기본형 값으로 초기화된다.

 

새 인스턴스 생성을 위해, SignalLight red_light = new SignalLight(); 코드가 실행될 때, static이 붙지 않은 초기화 블럭과 인스턴스 변수 생성은 클래스 내에서 순차적으로 진행된다. 즉, 위의 코드는 인스턴스가 생성될 때, 아래의 순서로 Heap 메모리에 적재된다.

 

-  red_light 인스턴스의 메모리 로딩 및 인스턴스 변수 초기화(int -> 0, 참조형인 String -> null)

- 초기화 블럭 내에서  System.out.println() 매서드 실행

- 초기화 블럭 내에서 this.print_info() 매서드 실행

    -> duration = 0, shape = null 이 화면에 출력됨.

-  int duration = 30;, String shape = "red"; 실행

 

따라서 초기화 블럭 사용 시에는 반드시 변수 뒤에 위치하는 것이 좋다. 생성자 및 매서드와의 위치는 크게 상관이 없는데, 매서드의 어느 위치에 놓이더라도 초기화 블럭이 항상 먼저 수행되기 때문이다.

 

 

 

참고로 초기화 블럭은 생성자에서 공통적으로 진행되는 매서드 뿐만 아니라 변수 선언 코드 입력도 가능하다. 따라서 static으로 선언된 변수와 초기화블럭을 사용하면, 아래와 같이 생성되는 신호등 객체에 대한 일련번호를 부여하는 코드도 작성이 가능하다.

 

 

 


 

 

다음 포스팅에서는 클래스 상속에 대해 알아보려 한다.

 

 

 

 

Fin.

 

 

반응형

댓글