Java/정석

생성자

hs_developer 2022. 5. 26. 22:42

p291-307

 

생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메소드'이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업에도 사용된다.

 

생성자는 메소드처럼 클래스 내에 선언되며, 구조도 메소드와 유사하지만 리턴 값이 없는 것이 다르다. 그렇다고 해서 생성자 앞에 void는 붙이지 않는다. 그냥 아무 것도 적지 않는다.

 

생성자 조건
1. 생성자 이름은 클래스 이름과 같다.
2. 리턴 값이 없다. (void 안 붙인다)

 

생성자도 오버 로딩이 가능하므로 하나의 클래스에 여러가지 생성자가 존재할 수 있다.

 

클래스 이름(타입 변수명, 타입 변수명, ...){}
    // 인스턴스 생성 시 수행 될 코드
    // 주로 인스턴스 변수의 초기화 코드를 적는다
class Card
{
    Card() { // 매개변수가 없는 생성자
    	...
    }

    Card(String k, int num) { // 매개변수가 있는 생성자
    	...
    }
	...
}

 

 

1. 기본 생성자

 

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. 그 동안 우리는 인스턴스를 생성할 때 컴파일러가 제공한 기본 생성자를 사용해왔다. 

 

특별히 인스턴스 초기화 작업이 요구되지  않는다면 생성자를 정의하지 않고 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다.

 

 

class Data1
{
	int value;
}

class Data2
{
	int value;
	
	Data2(int x) // 매개변수가 있는 생성자
	{
		value = x;
	}
}


public class test {

	public static void main(String[] args) {

		Data1 d1 = new Data1();
		Data2 d2 = new Data2(); // compile error 발생
	}
}

 

Data2에 생성자 Data2()가 정의되어 있지 않아서 에러가 발생했다. 

 

Data1에는 정의되어 있는 생성자가 하나도 없으므로 컴파일러가 기본 생성자를 추가해주었지만, Data2에는 이미 생성자 Data2(int x)가 정의되어 있으므로 기본 생성자가 추가되지 않았기 때문이다.

 

 

Data1 d1 = new Data1();
Data2 d2 = new Data2(); // error

Data1 d1 = new Data1();
Data2 d2 = new Data2(10); // OK

 

에러를 없애려면 Data2 인스턴스를 생성할 때 생성자 Data2(int x)를 사용하던가, 클래스 Data2에 생성자 Data2()를 추가로 정의해준다.

 

2. 매개변수가 있는 생성자

 

인스턴스마다 각기 다른 값으로 초기화되어야 하는 경우가 많기 때문에 매개 변수를 사용한 초기화는 유용하다.

 

아래의 코드는 자동차를 클래스로 정의한 것인데, 단순히 color, gearType, door의 인스턴스 변수 3개와 생성자 2개만 있다.

 

class Car
{
	String color;
	String gearType; 
	int door;
	
	Car(){} // 생성자
	Car(String c, String g, int d) // 생성자
	{
		color = c;
		gearType = g;
		door = d;
	}
}

 

매개변수가 있는 생성자 Car(String color, String gearType, int door)를 사용한다면 인스턴스를 생성하는 동시에 원하는 값으로 초기화를 할 수 있다.

 

인스턴스를 생성한 다음에 인스턴스 변수 값을 변경하는 것보다 매개변수를 갖는 생성자를 사용하는 것이 코드를 보다 간결하고 직관적으로 만든다.

 

Car c = new Car();
c.color = "white";
c.gearType = "auto";
c.door = 4;

Car c = new Car("white", "auto", 4);

 

 

c1의 color: white, gearType: auto, door: 4
c2의 color: white, gearType: auto, door: 4
class Car
{
	String color; 
	String gearType; 
	int door;
	
	Car(){} // 생성자
	Car(String c, String g, int d) // 생성자
	{
		color = c;
		gearType = g;
		door = d;
	}
}

class test {
	
	public static void main(String[] args) {
		
		Car c1 = new Car();
		
		c1.color = "white";
		c1.gearType = "auto";
		c1.door = 4;
		
		// 훨씬 간결
		Car c2 = new Car("white", "auto", 4);
		
		System.out.println("c1의 color: " + c1.color + 
				", gearType: " + c1.gearType + ", door: " + c1.door);
		System.out.println("c2의 color: " + c2.color + 
				", gearType: " + c2.gearType + ", door: " + c2.door);

	}
}

 

 

3. 생성자에서 다른 생성자 호출하기 - this(), this

 

같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 단, 다음 조건을 만족시켜야 한다.

1. 생성자의 이름으로 클래스 이름 대신 this를 사용한다.
2. 한 생성자에서 다른 생성자를 호출할 때는 반드신 첫 줄에서만 호출 가능하다.
Car(String color)
{
    door = 5;
    Car(color, "auto", 4); // Error
}

// 에러1 : 생성자의 2번째 줄에서 다른 생성자 호출
// 에러2 : this(color, "auto", 4);로 해야 함

 

 

 

c1의 color: white, gearType: auto, door: 4
c2의 color: blue, gearType: auto, door: 4
class Car
{
	String color;
	String gearType; 
	int door;
	
	Car() 
	{
		this("white", "auto", 4);
	} 
	
	Car(String color) 
	{
		this(color, "auto", 4); // color를 따로 넣고 싶을 때
	}
	
	Car(String color, String gearType, int door) // 생성자
	{
		this.color = color;
		this.gearType = gearType;
		this.door = door;
	}
}

class test {
	
	public static void main(String[] args) {
		
		Car c1 = new Car();
		Car c2 = new Car("blue"); // color를 따로 넣어줌
		
		System.out.println("c1의 color: " + c1.color + 
				", gearType: " + c1.gearType + ", door: " + c1.door);
		System.out.println("c2의 color: " + c2.color + 
				", gearType: " + c2.gearType + ", door: " + c2.door);

	}
}

 

 

Car(String c, String g, int d)
{
    color = c;
    gearType = g;
    door = d;
}

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

 

this를 사용할 수 있는 것은 인스턴스 멤버 뿐이다. static 메소드에서는 인스턴스 멤버들은 사용할 수 없는 것처럼, this 역시 사용할 수 없다.

 

 

this
:: 인스턴스 자신을 가리키는 참조 변수, 인스턴스 주소가 저장되어 있다.
:: 모든 인스턴스 메소드에 지역변수로 숨겨진 채로 존재한다.

this(), this(매개변수)
:: 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다. 

 

this와 this()는 비슷하게 생겼을 뿐 완전히 다른 것이다. this는 참조 변수이고, this()는 생성자이다.

 

 

3. 변수의 초기화

 

변수를 선언하고 처음으로 값을 저장하는 것을 변수의 초기화라 한다. 가능하면 선언과 동시에 초기화 하는 것이 좋다.

 

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

    void method()
    {
        int i; // 지역변수
        int j = i; // Error, 지역변수를 초기화하지 않고 사용
    }
}

 

x, y는 인스턴스 변수이고, i, j는 지역변수이다. 그 중 x, i는 선언만 하고 초기화를 하지 않았다. 그리고 y를 초기화 하는데 x를 사용했고, j를 초기화 하는데 i를 사용했다.

 

인스턴스 변수 x는 초기화 해주지 않아도 자동적으로 int형의 기본 값은 0으로 초기화 되므로, 'int y = x'와 같이 할 수 있다. x 값이 0이므로 y 역시 0으로 저장된다.

 

하지만, method()의 지역변수 i는 자동적으로 초기화되지 않으므로, 초기화 되지 않은 상태에서 변수 j를 초기화 하는데 사용될 수 없다.

 

멤버 변수 (클래스 변수, 인스턴스 변수)와 배열의 초기화는 선택적이지만, 지역 변수의 초기화는 필수적이다.

 

 

멤버 변수의 초기화는 지역 변수와 달리 여러 가지 방법이 있다.

멤버 변수의 초기화 방법
1. 명시적 초기화
2. 생성자
3. 초기화 블럭
ㅡ 인스턴스 초기화 블럭 : 인스턴스 변수를 초기화 하는 데 사용
ㅡ 클래스 초기화 블럭 : 클래스 변수를 초기화 하는 데 사용

 

 

3-1 명시적 초기화 explicit initalization

 

변수를 선언과 동시에 초기화 하는 것이다. 가장 기본적이고 간단한 초기화 방법이다.

 

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

    //..
}

 

보다 복잡한 초기화 작업이 필요할 때는 초기화 블럭 또는 생성자를 이용해야 한다.

 

3-2 초기화 블럭 initialization block

 

초기화 블럭에는 클래스 초기화 블럭, 인스턴스 초기화 블럭이 있다.  

 

클래스 초기화 블럭은 클래스 변수의 초기화에 사용되고, 인스턴스 초기화 블럭은 인스턴스 변수의 초기화에 사용된다.

 

클래스 초기화 블럭    클래스 변수의 복잡한 초기화에 사용된다.
인스턴스 초기화 블럭    인스턴스 변수의 복잡한 초기화에 사용된다.

 

초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭 {} 만들고 그 안에 코드를 작성하기만 하면 된다. 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static을 붙이기만 하면 된다.

 

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

 

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;
}

 

 

 

static 초기화 블럭
ㅡ
메인1
instance 초기화 블럭
생성자
ㅡ
메인2
instance 초기화 블럭
생성자
class test
{
	static // 클래스 초기화 블럭
	{
		System.out.println("static 초기화 블럭");
	}
	
	{ // 인스턴스 초기화 블럭
		System.out.println("instance 초기화 블럭");
	}
	
	public test() {
		System.out.println("생성자");
	}
	
	public static void main(String[] args) {
		
		System.out.println("메인1");
		test bt = new test();
		
		System.out.println("메인2");
		test bt2 = new test();
	}
}

 

1. 클래스 초기화 블럭이 가장 먼저 수행되어 'static { }' 출력

2. main 메소드 수행되어 test 인스턴스 생성되어 인스턴스 초기화 블럭 수행, 출력

3. 생성자 수행, 출력

 

클래스 초기화 블럭은 처음 메모리에 로딩될 때 한 번만 수행되었지만, 인스턴스 초기화 블럭은 인스턴스가 생성될 때마다 수행된다.

 

 

arr[0]:9
arr[1]:9
arr[2]:4
arr[3]:10
arr[4]:4
arr[5]:6
arr[6]:9
arr[7]:3
arr[8]:3
arr[9]:8
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을 생성하고, 클래스 초기화 블럭을 이용해 배열의 각 요소들을 random()을 사용해 임의의 값을 채웠다.

 

이처럼 배열이나 예외처리가 필요한 초기화에서는 명시적 초기화만으로는 복잡한 초기화 작업을 할 수 없다. 이 경우에 추가적으로 클래스 초기화 블럭을 사용한다.

 

 

 

3.3 멤버 변수의 초기화 시기와 순서

 

초기화가 수행되는 시기와 순서

 

클래스 변수의 초기화 시점   클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스 변수의 초기화 시점    인스턴스가 생성 될 때마다 각 인스턴스 별로 초기화 된다.

클래스 변수의 초기화 순서   기본 값 → 명시적 초기화 → 클래스 초기화 블럭
인스턴스 변수의 초기화 순서   기본 값 → 명시적 초기화 → 인스턴스 초기화 블럭 → 생성자

 

public class test {

	static int cv = 1;
	int iv = 1;
	
	static 
	{
		cv = 2; // 클래스 초기화 블럭
	}
	
	{
		iv = 2; // 인스턴스 초기화 블럭
	}
	
	test() 
	{
		iv = 3; // 생성자
	}
}