IT/언어

[Java] 생성자(Constructor)의 기본과 사용법

개발자 두더지 2023. 5. 13. 23:23
728x90

일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 

1. 생성자(Constructor)의 기본


 Java의 생성자(Constructor)란 클래스로부터 인스턴스를 만들 때 에 실행되는 처리이다. 생성자(Constructor)이라는 단어는 영어의 “만들다”와 “사람”이므로, 건설업자, 제조업자 등과 같은 의미를 지닌다. 인스턴스를 만드는 사람과 같은 의미이다.

 

1-1. 생성자(Constructor)는 특별한 메소드와 같은 것

 생성자(Constructor)는 인스턴스가 만들어질때에 실행되는 특별한 메소드라고 자주 설명된다. 그러나 메소드와 다르게 다음과 같은 특징이 있다.

  • 클래스명과 동일한 이름을 가진다.
  • 메소드로서 반환값을 가지지 않는다(그리고 도중에 return도 되지 않는다).
  • 인스턴스가 만들어 질 때 반드시 실행된다.
  • 이러한 것들 외에도 메소드와 동일한 것도 가능하다.
  • 클래스는 몇 가지 생성자(Constructor)를 가지고 있다(=생성자(Constructor)의 오버라이드)
  • 액세스 수식자로 생성자(Constructor)를 호출할 수 있는 범위를 컨트롤 할 수 있다.
  • 생성자(Constructor)를 실행했을 때에 발생한 예외도 throw할 수 있다.

 

1-2. 생성자(Constructor)의 구문

[액세스 수식자] 클래스명([인수]) [throw할 예외] {
	[생성자에서 하고 싶은 처리]
}

  생성자(Constructor)의 구문은 다음과 같다 []로 감싼 부분은 필수가 아닌 부분이다.

 보면 알 수 있을거라고 생각되지만, 메소드의 선언/작성법과 거의 비슷하다. 그러나 자세히 보면, 메소드에서는 필수인 반환값이 존재하지 않는다. void를 쓸 필요도 없거나한다.

 즉,생성자(Constructor)에서 최저한으로 필요한 것은 클래스명과 ()과 {}로 쓰는 정도이다. 생성자(Constructor)의 안에는 처리를 작성하는 것도 괜찮고, 비어있는 상태도 괜찮다.

 

1-3. new 연산자로 생성자(Constructor)를 호출

 생성자(Constructor)는 new연산자를 사용하여 호출한다. 반대로 new연산자를 사용하지 않으면 호출되지 않는다는 뜻이 된다.

 new이라는 키워드를 사용하는 것은 생성자(Constructor)를 사용하여 인스턴스를 “새롭게” 만드는 처리이기 때문이다. “연산자”라고 부르는 것에서 부터 알 수 있듯, new연산자의 반환값은 생성자(Constructor)로 만들어진 클래스의 인스턴스이다.

 new연산자의 구문은 아래와 같다. 생성자(Constructor)에는 인수가 있는 것과 없는 것이 있으므로 인수는 필요에 따라 사용하면 된다.

new 클래스명([인수])

 또한, Java프로그래머는 new를 “인스턴스를 만든다”이라는 의미의 동사로써 사용하므로, 익숙해지자. 예를 들어 “new한다” “new할 수 있다” “null포인트 에러가 발생하는 것은 여기서 new하지 않았기 때문이다”라는 말 등에도 사용된다.

 

1-4. 생성자(Constructor)의 예

 이것이 Java에서 가장 간단한 생성자(Constructor)의 예이다. 굉장히 심플하다.

class ConstructorSample1 {
	ConstructorSample1() {
	}
}

 그럼 당장 이 생성자(Constructor)를 사용해보자. 그러나 이거뿐이라면생성자(Constructor)가 움직이고 있는지 잘 모르기 때문에 print처리를 추가해보자.

class ConstructorSample1 {
	ConstructorSample1() {
		System.out.println("ConstructorSample1のコンストラクタ");
	}

	public static void main(String[] args) {
		System.out.println("생성자 호출 전");
		new ConstructorSample1(); 
		System.out.println("생성자 호출 후");
	}
}

 new된 타이밍에서생성자(Constructor)가 실행된다는 의미라는 것은 이전에 설명했기에 알고 있을 것이라고 생각된다. 그럼 인수가 있는 생성자(Constructor)도 만들어 동작시켜보자.

class ConstructorSample2 {
	String name;

	ConstructorSample2(String str) {
		name = str;
	}

	public static void main(String[] args) {
		ConstructorSample2 sample1 = new ConstructorSample2("sample");
		ConstructorSample2 sample2 = new ConstructorSample2("샘플");

		System.out.println(sample1.name); // → "sample"이 표시
		System.out.println(sample2.name); // → "샘플"이 표시
	}
}

 생성자(Constructor)의 안에서는 인수의 String을 인스턴스의 필드에 설정하고 있다. main에서는 인스턴스를 두 개 만들어, 생성자(Constructor)를 호출 했을 때의 인수를 각각 다르게 하고 있다. 그 후에 실행하고 있는 print문에서는 각각의 생성자(Constructor)에 설정한 다른 String값이 표시된다.

 이것으로 인스턴스의 초기 상태를 생성자(Constructor)에 설정할 수 있다는 의미에 대해서 이해했을 것이라고 생각된다. 이것이 가장 보통의 생성자(Constructor) 사용법이다.

 물론, 인스턴스의 필드는 new한 후에도 설정, 변경할 수 있다. 그러나 new한 직후에 필드 값을 설정하면 바로 인스턴스를 사용할 수 있으므로 편리하다.

 


2. 생성자(Constructor)의 이모저모


 이 장에서는 생성자(Constructor)에 대해서 조금 상세하게 설명하고자 한다. 조금 알기 애매한 부분이 있을 것인데, 순서대로 따라가면서 이해해보자.

 

2-1. 기본 생성자(Constructor)

 이전 장에서 만든 인수와 처리가 없는 생성자(Constructor)는 프로그래머가 클래스에 생성자(Constructor)를 만들지 않으면 실제 Java가 뒤에서 몰래 만든다.

 이러한 생성자(Constructor)를 “기본 생성자(Constructor)”라고 부른다. 프로그래머가 생략한 경우에 만들어지는 기본적이나 생성자(Constructor)이므로, 그렇게 불리는 듯하다.

 예를 들어 아래와 같이 생성자(Constructor)가 없는 클래스에서도 new를 할 수 있다. 이것은 당연하게 받아들일 수 있지만, 앞의 설명없이 봤다면 조금 특이하지 않은가?

class ConstructorSample3 {
	public static void main(String[] args) {
		new ConstructorSample3();
	}
}

 생성자(Constructor)를 쓰지 않았는데도 new가 가능한 것인가….라고 말하자면, 기본 생성자(Constructor)는 보이지 않아도 어쨋든 존재하기 때문이다.

 

2-2 인수를 바꿔서 생성자(Constructor)를 오버로드하기

 지금까지의 예에서는 하나의 클래스에 하나의 생성자(Constructor)만 만들어 봤다. 그러나 클래스에는 프로그래머가 필요한 만큼 생성자(Constructor)를 몇 개도 만들 수 있다.

 즉, 아래와 같이 생성자(Constructor)의 인수를 바꿔주면, 생성자(Constructor)를 오버로드 할 수 있다.

class ConstructorSample4 {
	// ①인수가 없는 생성자
	ConstructorSample4() {
	}

	// ②String의 인수가 있는 생성자
	ConstructorSample4(String str) {
	}

	// ③String과 int의 인수가 있는 생성자
	ConstructorSample4(String str, int i) {
	}

	public static void main(String[] args) {
		new ConstructorSample4(); // ①를 호출
		new ConstructorSample4("sample1"); // ②를 호출
		new ConstructorSample4("sample2", 123); // ③를 호출
	}
}

 생성자(Constructor)의 오버로드는 메소드의 오버로드와 동일한 사고방식이다. 그러므로 인수의 데이터형과 인수의 정의 순서가 다르면, 각각 다른 생성자(Constructor)가 된다.

 그리고, 클래스에 몇 개의 생성자(Constructor)가 있다면, new했을 때에 그중에 하나가 호출되는 것이 된다.

 생성자(Constructor)를 오버로드하는 것은 클래스의 초기화 구조를 바꾸고 싶을 때가 있기 때문이다. 보통은 기본값으로 괜찮지만, 특정한 경우에 초기값을 지정하고 싶은 경우 등 말이다.

 

2-3. 생성자(Constructor)를 생성자(Constructor)에서 호출하는 this()

 생성자(Constructor)를 생성자(Constructor)에서 호출…이라는 도대체 알 수 없는 말일지도 모르겠지만, 프로그램을 효율적으로 만들기 위해서는 사용되길 바라는 기술이다.

2-3-1. 생성자(Constructor)에서의 처리를 하나로 정리하고 싶을 때

 예를 들어, 아래와 같이 몇 개의 생성자(Constructor)를 가진 클래스가 있다고 가정하자. 각각의 생성자(Constructor)에 필드 name과 amount의 초기화 처리가 있다.

class ConstructorSample5 {
	String name;
	int amount;

	// 생성자①
	ConstructorSample5() {
		name = "sample";
		amount = 100;
	}

	// 생성자②
	ConstructorSample5(String str) {
		name = str;
		amount = 100;
	}

	// 생성자③
	ConstructorSample5(String str, int i) {
		name = str;
		amount = i;
	}
}

 그러나 동일한 처리를 복사/붙여넣기로 여러 개 만들고 싶지 않을 것이다. 즉 이러한 처리한 처리를 되도록 하나로 정리하고 싶을 것이다. 이 상태에서 어떤 생성자(Constructor)에서 다른 생성자(Constructor)를 호출하는 것으로 효율적으로 만들 수 있게 된다.

2-3-2. this()로 효율적인 프로그램을 만든 예

 아래의 예에서는 인수가 두개 있는 생성자(Constructor)3을, 1과 2에서 this()로 호출하고 있다.

class ConstructorSample5 {
	String name;
	int amount;

	// 생성자①
	ConstructorSample5() {
		this("sample"); // 생성자②를 호출
	}

	// 생성자②
	ConstructorSample5(String str) {
		this(str, 100); // 생성자③를 호출
	}

	// 생성자③
	ConstructorSample5(String str, int i) {
		this.name = str;
		this.amount = i;
	}
}

 이렇게 작성하는 것으로 필드로의 값을 설정하고 있는 곳을 하나로 설정했으므로 프로그램 자체가 짧아지고 간략해진다.

 메소드를 호출하는 것과 비슷하지만, 생성자(Constructor)의 안에서 호출하는 방법은 클래스명이 아닌 this()를 사용한다. 이것을 활용하면, 하나의 생성자(Constructor)에서의 처리를 다른 곳에서도 사용할 수 있으므로 효율적으로 프로그래밍을 할 수 있다.

2-3-3. this()의 두 가지 룰

 또한, this()에는 아래와 같은 두 가지 규칙이 있으므로 주의하길 바란다. 이 룰을 지키지 않으면 컴파일 에러가 된다.

  • 생성자(Constructor)의 “선두행“이 되어야한다.
  • 하나의 생성자(Constructor)내에서는 “한 번만” 호출할 수 있다.

 

2-4. 생성자(Constructor)의 액세스 수식자

 생성자(Constructor)는 액세스 수식자로 호출할 수 있는 범위를 제한할 수 있다. 이것에 의해 인스턴스를 만들어도 괜찮은 클래스의 범위를 클래스를 프로그래머가 지정할 수 있다는 것이다.

 생성자(Constructor)로의 액세스 수식자는 메소드와 동일하게 public, protected, package private(지정하지 않은 경우)/ private 이렇게 네 가지로 지정이 가능하다. 각각의 의미도 메소드의 경우와 동일한다.

  • public : 어떤 패키지, 어떤 클래스에서도 호출할 수 있다.
  • protected :  동일 패키지, 혹은 상속처 클래스에서만 호출할 수 있다.
  • package private : 동일 패키지내의 클래스에서만 호출할 수 있다.
  • private : 자기 자신의 클래스에서만 호출할 수 있다.

 그리고 덧붙여서 생성자(Constructor)를 공개하지 않도록 하는 것으로 실현할 수 있는 기술도 있다. 그 부분에 대한 설명은 나중에 기회가 되면 하도록 하겠다.

 

2-5. 생성자(Constructor)는 클래스를 만든 프로그래머의 의견을 표시한다.

 생성자(Constructor)는 프로그래마 이 클래스의 인스턴스는 이렇게 만들었으면 좋겠다는 의견 표시가 되기도 한다.

 프로그래머가 클래스의 생성자(Constructor)를 어떻게 만드는지, 클래스의 인스턴스를 어떻게 만들어가고 싶은지를 클래스를 사용하는 사람에게 강제할 수 있기 때문이다. 예를 들면 다음과 같다.

  • 인수가 있는 생성자(Constructor)가 하나만 있다면, 이것을 사용한 인스턴스 생성만 할 수 있다.
  • 인수가 없는 생성자(Constructor) 혹은 기본 생성자(Constructor)밖에 없다면, 인수 없는 new밖에 할 수 없다.
  • 생성자(Constructor)가 오버로드되어 있다면, 그것 중 하나는 반드시 사용해야 한다.
  • private 생성자(Constructor)라면, 그 클래스는 외부에서 인스턴스화 할 수 없다.

 이것은 클래스 설계를 할 때 제대로 해두지 않으면, 사용하기 어렵게 되거나, 의도가 불분명한 클래스가 되어버린다.
인수가 몇 십개 있는 생성자(Constructor)가 있다면, 각각의 인수에 제대로 값을 설정하는 것은 Javadoc가 있다고 해도 어렵다. 오버로드된 생성자(Constructor)가 몇 십개 있다면, 그것들의 컨스트럭터를 어떻게 사용하는 곳을 나누는가에대해서도 어렵다.

 그러므로, 생성자(Constructor)를 만들 때에 초기화에서 필요한 인수는 어떤 것인가,생성자(Constructor)에서 어디까지 초기화하면 좋을지에 대해 제대로 생각해둬야한다. 어떻게 하면 좋을지에 대한 방침은 나중에 설명하도록 하겠다.

 

 

3. 클래스의 상속과 생성자(Constructor)


3-1. 슈퍼 클래스의 생성자(Constructor)는 반드시 호출된다.

 클래스가 상속 관계에 있는 경우, 서브 클래스의 생성자(Constructor)에서는 “반드시” 슈퍼 클래스의 생성자(Constructor)가 호출된다. 슈퍼 클래스의 초기화는 서브 클래스의 초기화보다도 먼저 이루어지도록 되어 있다.

 슈퍼 클래스에 생성자(Constructor)가 있다면, 그 슈퍼 클래스의 작성자가 클래스의 초기화의 방식을 지정한다는 것이다. 그러므로 서브 클래스는 그 방식을 지키면서 슈퍼 클래스를 초기화해야한다.

 그러므로, 앞에서 설명했듯, 명시적으로 클래스에 생성자(Constructor)를 만들지 않아도, 클래스는 기본 생성자(Constructor)를 가지고 있다. 슈퍼 클래스라고 해도 보통의 클래스이므로 반드스 생성자(Constructor)가 있다는 것이다.

 따라서, Java에서는 반드시 슈퍼 클래스의 생성자(Constructor)가 있으므로, 그 생성자(Constructor)는 반드시 실행되는 것이 보증된다는 것이다. 이것은 슈퍼 클래스가 추상 클래스이어도 동일하다.

 

3-2. 슈퍼 클래스의 생성자(Constructor)는 super()로 호출한다.

 super()란 슈퍼 클래스의 생성자(Constructor)를 서브 클래스의 생성자(Constructor)에서 호출 했을 때 사용하는 것이다. this()와 동일하게, 컨스트럭터의 안에서만 호출된다.

 super()도 this()와 비슷한 사용법이지만, 호출하는 것이 슈퍼 클래스의 생성자(Constructor)라는 점이 다르다. 그리고, super()에서도 this()와 같은 룰이 있다. 이 룰을 지키지 않는다면 컴파일 에러가 발생한다.

  • 생성자(Constructor)의 “선두행“이여한다.
  • 하나의 생성자(Constructor)내에 “한 번”만 호출할 수 있다.
  • this()를 한후에 super()은 할 수 없다. super()한 후에 this()를 할 수 없다.

 한편, 슈퍼 클래스에 어떤 생성자(Constructor)가 있는지에 따라 서브 클래스에서 호출하는 방식이 조금씩 바뀐다. 아래에서 그 예에 대해서 몇 가지 설명하도록 하겠다.

3-2-1. 슈퍼 클래스의 생성자(Constructor)에 인수가 없는 경우

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자
	ConstructorSample6Parent() {
	}
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 서브 클래스의 생성자
	ConstructorSample6Child() {
		super(); // 슈퍼 클래스의 생성자를 호출
	}
}

슈퍼 클래스의 생성자(Constructor)가 인수를 가지지 않은 경우, 서브 클래스에서는 단순히 super()하면 된다.

3-2-2. 슈퍼 클래스의 생성자(Constructor)에 인수가 있는 경우

 슈퍼 클래스의 생성자(Constructor)가 인수를 가지고 있다면 서브 클래스에서는 super()에 인수를 기정하여 호출한다. 혹시 슈퍼 클래스의 생성자(Constructor)가 1개뿐이라면, 반드시 그 생성자(Constructor)를 호출해야한다.

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자
	ConstructorSample6Parent(String str) {
	}
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 서브 클래스의 생성자
	ConstructorSample6Child() {
		super("sample"); // 슈퍼 클래스의 인수가 어떠한 생성자를 호출한다.
	}
}

3-2-3. 슈퍼 클래스의 생성자(Constructor)가 오버로드되어 있는 경우

 슈퍼 클래스의 생성자(Constructor)가 몇 가지 있다면, 서브 클래스에서는 super()로 호출할 하나의 생성자(Constructor)를 선택한다. 호출하는 생성자(Constructor)의 구분은 메소드의 오버로드나 this()와 동일하므로, 인수의 데이터형이나 순서대로 작성한다.

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자①
	ConstructorSample6Parent() {
	}

	// 슈퍼 클래스의 생성자②
	ConstructorSample6Parent(String str) {
	}

	// 슈퍼 클래스의 생성자③
	ConstructorSample6Parent(String str, int i) {
	}
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 서브 클래스의 생성자
	ConstructorSample6Child() {
		super("sample"); // 슈퍼 클래스의 생성자②를 호출한다.
	}
}

3-2-4. 슈퍼 클래스에 명시적인 생성자(Constructor)가 없는 경우

 앞서 설명했듯, 명시적으로 생성자(Constructor)가 없어도 클래스에는 기본 생성자(Constructor)가 있으므로, 서브 클래스에서 super()할 수 있다.

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자가 없다 = 기본 생성자가 있다.
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 그러므로 super()를 통해 서브 클래스의 생성자에서 실행시킬 수 있다.
	ConstructorSample6Child() {
		super();
	}
}

 인수가 없는 super()에서는 슈퍼 클래스의 인수가 없는 생성자(Constructor) 혹은 기본 생성자(Constructor)가 호출되게 되는 것을 기억해두자.

 

3-3. 슈퍼 클래스의 생성자(Constructor)는 자동적으로 호출되는 경우가 있다.

 아래의 경우라면 서브 클래스는 슈퍼 클래스의 생성자(Constructor)를 호출하지 않는 것처럼 보인다. 그러나 컴파일도 실행도 가능하다.

 그러나 3장의 맨 앞에서 “서브 클래스에서는 슈퍼 클래스의 생성자(Constructor)가 반드시 호출된다”라고 설명했었다.

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자
	ConstructorSample6Parent() {
		System.out.println("ConstructorSample6Parent");
	}
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 서브 클래스의 생성자, super()를 하고 있지 않지만…
	ConstructorSample6Child() {
	}

	public static void main(String[] args) {
		new ConstructorSample6Child(); // → "ConstructorSample6Parent"가 표시된다.
	}
}

 서브 클래스의 생성자(Constructor)에서는 super()하지 않으면 슈퍼 클래스의 기본 생성자(Constructor) 혹은 인수가 없는 생성자(Constructor)를 자동적으로 호출한다. 이것을 “암묵적인 컨스트럭터 호출”이라고 부른다.

  중요한 것은 이러한 자동 호출은 슈퍼 클래스의 생성자(Constructor)가 기본 생성자(Constructor) 혹은 인수가 없는 경우 밖에 되지 않는다.

class ConstructorSample6Parent {
	// 슈퍼 클래스의 생성자에는 인수있는 것 밖에 없다.
	ConstructorSample6Parent(String str) {
	}
}

class ConstructorSample6Child extends ConstructorSample6Parent {
	// 컴파일 에러!!
	ConstructorSample6Child() {
	}
}

 이것으로 “서브 클래스에서 슈퍼 클래스의 생성자(Constructor)는 반드시 호출된다”라는 의미에 대해서 이해됐을 것이라고 생각한다.

 



4. [응용] 생성자(Constructor)에 대한 조금 더 상세한 이야기


4-1. 사용하기 쉬운 생성자(Constructor) 지침 방식

 생성자(Constructor)는 프로그래머가 자유롭게 만들 수 있다. 자유롭게 만들 수 있으므로, 프로그래머의 스킬이나 지식에 따라 알기 쉽거나 알기 어렵게 되어버린다.

 여기에서는 모두가 사용하기 쉽거나 사용하기 어려운 생성자(Constructor)는 어떤 것인가에 대해 몇 가지 사례로 소개하고자한다.

4-1-1. 인수가 많은 생성자(Constructor)는 사용하기 어렵다.

 생성자(Constructor)는 일반적으로 인수가 많으면 많을수록, 사용하는 것이 어려워진다. 내 개인적인 의견으로는 인수가 많아도 5~6개 정도가 적당하고, 그 이상은 많다는 느낌을 받는다.

4-1-2. 많이 오버로드된 생성자(Constructor)는 사용하기 어렵다.

 생성자(Constructor)는 오버로드 된다고 해서, 차이가 조금 밖에 없는 생성자(Constructor)를 계속해서 만드면 사용하는 쪽이 혼란스럽다.

 이것도 개인적인 의견이지만 5~6개 이상이 되면 굉장히 혼란스러운 느낌이 들기 시작한다.
오버로드되어 있는 어떤 생성자(Constructor)를 어떨 때 쓰면 좋을까. 클래스를 만든 프로그래머는 의도가 있다고 한다고 해도 사용하는 쪽에서는 쉽게 알기 어렵다.

 또한, Java의 오버로드 사양에서는 데이터형과 그 순서로만 구분할 수 있으므로 차이점을 알기 어렵다.

4-1-3. 생성자(Constructor)와 setter나 초기화 메소드를 나눠서 사용한다.

 위에서 봤던 사례들은 모두 초기화 처리를 생성자(Constructor)안에서만 하려고 해서 일어나는 문제들이다. 확실히 생성자(Constructor)는 초기화에 사용하는 것이지만, 생성자(Constructor)는 초기화에 만능인 툴은 아니다.
인스턴스의 초기화에는 대체적으로 아래와 같은 방법이 있다. 이 방법들은 클래스의 특성이나 초기화에 필수인 정보가 무언인지 생각한 후에 유연하게 맞춰서 사용하면 된다.

  • 생성자(Constructor)에 값을 지정한다.
  • 인스턴스 생성 후에 setter로 속성을 설정한다.
  • 인스턴스 생성 후에 전용 초기화 메소드를 호출하는 것을 규칙으로 한다.
  • 인스턴스 생성 후에 초기화를 담당하는 전용 클래스의 메소드에서 초기화를 맡긴다.
  • DI(Dependency Injection)컨테이너나 IoC(Inversion of Control) 컨테이너등의 인스턴스를 생성을 도와주는 프레임 워크, 미들 웨어에게 일을 맡긴다.

 그리고 생성자(Constructor)의 인수로 할만한 것을 결정하기 위해, 다음과 같은 것을 고려한다. 내 개인적으로 다음과 같은 룰을 따른다는 것이지, 절대적인 것은 아니다.

  • 인스턴스의 키 정보이며, 다른 인스턴와의 식별에도 사용할 수 있는 것
  • 인스턴스에 있어서 필수불가결인 것
  • 인스턴스에 있어서 일단 설정되면 변경이 불필요한 것 (final과 같은 필드 등)
  • 여러 개의 값에 상관관계가 있어, 정합성을 위한 설정이 필요한 경우
  • 초기화 처리에서만 사용하며, 인스턴스의 필드에서는 유지하지 않는 것

 중요한 것은 그 클래스나 인스턴스 주요 요소인지 그렇지 않은지를 제대로 구분짓는 것이다.예를 들어, RDBMS의 테이블에 있어서 한 행을 표현하는 클래스라면, 주키는 생성자(Constructor)에 지정하고 다른 것은 setter로 지정하는 방법을 생각해볼 수 있다.

 

4-2. 생성자(Constructor)와 final 인스턴스 필드

 Java에서의 final은 필드에 사용하면 그것을 지정하는 값이나, 지정한 처의 인스터스를 변경할 수 없는 것이다. 이 final은 생성자(Constructor)에 따라 인스턴스의 초기화와 굉장히 상성이 좋다.
이유는 생성자(Constructor)에서 final필드를 설정하면, 인스턴스가 살아있는 동안 절대로 바뀌지 않는 하나의 정수와 같은 것으로 다루어지기 때문이다.

 

4-3. 인스턴스 필드의 초기값 설정/초기화와 생성자(Constructor)의 실행 타이밍

 인스턴스 필드의 초기값의 설정과 초기화, 생성자(Constructor)의 실행 타이밍을 이해하지 않으면, 생각지도 못한 동작이 되어 원인을 파악하기 위한 시간을 쓰게 되는 원인이 된다.

 Java에서는 인스턴스가 생성됐을 때에는 아래의 순서대로 처리가 이뤄진다.

  1. 인스턴스 필드를 초기값을 설정한다 (숫자는 0, boolean은 false, 참조형은 null)
  2. 슈퍼 클래스의 생성자(Constructor)를 실행한다.
  3. 인스턴스 필드의 명시적인 초기화와 인스턴스 이니셜라이저를 실행한다.
  4. 생성자(Constructor)를 실행한다.

 2에서 슈퍼 클래스의 생성자(Constructor)가 실행되면 슈퍼 클래스쪽에서는 위와 동일한 처리가 실행된다. 따라서 예를 들어, 클래스이 상속 관계가 슈퍼클래스에서 순서대로 “A>B>C”이면, 전체적으로 본다면 다음과 같이 된다.

클래스 C 1 > 2
  클래스 B 1 > 2
    클래스 A 1 > (2>) 3 > 4
  클래스 B 3 > 4
클래스 C 3 > 4

 한편, 인스턴스 생성의 순서는 여기서 설명에서도 알 수 있듯, 상속 관계에서 가장 아래인 클래스부터 시작된다. 그러므로 슈퍼 클래스의 초기화시에는 인스턴스 메소드가 오버라이드된 상태에서 초기화가 진행된다.

 그리고 1과 3의 차이점, 이미 초기값과 초기화의 차이점을 알고 있을지 모르겠지만, 필드의 선언와 초기값을 동시에 한 경우, 예를 들어 int i = 123; 하여도 처리가 3까지 오기 전까지 i의 값은 0이 된다.

 

 

5. 정리


 이 포스트에서는 Java의 생성자(Constructor)에 대해서 설명했다. 생성자(Constructor)는 클래스에서 만들 수 있는 인스턴스로의 초기화 처리를 하고 싶을 때 사용한다. 생성자(Constructor)는 메소드와 비슷하게 인수를 바꾸면 오버로드도 할 수 있다.

 this()나 super()를 사용하면 다른 생성자(Constructor)나 슈퍼 클래스의 생성자(Constructor)를 호출할 수 있다.
생성자(Constructor)는 제대로 생각두지 않으면, 사용하기 힘든 클래스가 되어버린다. 그러므로 그에 대한 방침 중 하나로 인스턴스에 필요한 정보나 정보의 주종 관계를 파악하여 적절히 사용하는 것을 추천한다.

 생성자(Constructor)는 인스턴스를 생성할 때에는 절대적으로 사용하며, Java에서는 필수불가결한 것이다. 생성자(Constructor)를 자유자재로 다룰 줄 알게 되어 인스턴스의 초기화는 맡겨주세요라고 말할 수 있도록 되자!


참고자료

https://www.bold.ne.jp/engineer-club/java-constructor

728x90