생성자란 인스턴스 생성 시 호출되는 인스턴스 초기화 메서드이며 주로 인스턴스 초기화 작업에 주로 사용된다.
생성자는 다음 조건을 갖추어야 한다.
모든 클래스는 생성자를 가지며 생성자를 별도로 정의하지 않은 경우 JVM은 기본 생성자를 자동으로 추가한다.
class ConstructorClass{
int a;
void method1(){}
ConstructorClass(){} //기본 생성자
}
여기서 ConstructorClass{} 생성자를 따로 만들지 않으면 기본 생성자로서 컴파일러가 자동으로 추가한다.
생성자도 메서드와 마찬가지로 오버로딩이 가능하며 매개변수의 차이점이 그 조건이 된다.
class ConstructorOverload{
int a;
void method1(){}
ConstructorOverload(){} //매개변수가 없는 생성자(=기본 생성자)
ConstructorOverload(int a){} //매개변수가 있는 생성자. 오버로딩 가능
생성자에 대해 착각하기 쉬운 점이 있는데 생성자가 인스턴스를 생성하는 것이 아니다. new 연산자에 의해 인스턴스가 생성되며 생성자는 인스턴스 생성 시 호출되는 메서드인 것이다.
인스턴스가 생성되는 과정을 보면 이해가 쉽다. watch 클래스를 예로 들어 설명하겠다.
watch wt = new watch();
1) new 연산자에 의해 watch 클래스의 인스턴스가 Heap 영역에 생성된다
2) 이때 생성자가 호출되어 수행된다(new 연산자 뒤에 watch()가 생성자 호출을 의미)
3) 참조변수 wt에 생성된 인스턴스의 주소값이 할당되어 저장된다
2번 과정에서 생성자가 호출되므로 인스턴스를 생성하는 것은 생성자가 아니다.
앞에서 설명했듯이 클래스에 정의된 생성자가 없을 경우에만 컴파일러가 자동으로 기본 생성자를 추가한다.
기본 생성자는 매개변수도 내용도 없는 아주 기본적인 생성자다.
그렇다면 기본 생성자가 아닌 생성자가 정의된 경우에는 기본 생성자는 없는 것일까?
class Data{
int a;
Data(int x){
a = x;
}
}
class test{
public static void main(String[] args){
Data d = new Data(); //컴파일 에러 발생
}
}
여기서 Data 클래스에 이미 매개변수가 있는 생성자가 정의되어 있기 때문에 기본 생성자는 없다. 따라서, Data()를 호출해도 기본 생성자가 없기 때문에 컴파일 에러가 발생한다.
호출하기 위해서는 Data d = new Data(10); 로 코드를 작성해야 한다.
*클래스의 접근 지정자가 public인 경우 기본 생상자로 public 클래스이름(){} 가 추가된다
앞에서 Data(int x){}가 매개변수가 있는 생성자라 할 수 있다. 말 그래도 생성자에 매개변수가 들어가는 것을 의미한다. 굳이 매개변수가 있는 생성자를 정리하는 이유는 생성자를 사용하는 이유가 여기서 납득되기 때문이다.
예전에 알고리즘 공부를 하면서 레퍼런스를 보았는데 클래스 맴버 초기화를 생성자를 이용하였길래 왜 굳이 이런식으로 사용했는지 의문이었다. 이유는 코드를 간결,직관적으로 만들고 인스턴스마다 각기 다른 값으로 초기화되어야 하는 경우가 많기 때문이다.
한 개의 클래스에서 여러 개의 인스턴스를 생성할 때 인스턴스들은 서로 독립성을 가진다고 이전에 공부했었다. 즉, 인스턴스 변수들은 서로 다른 값을 가질 수 있고 그런 용도로 사용하기 위해 인스턴스를 만드는 것이다.
class car{
int door;
String name;
int speed;
}
자동차 클래스를 생각해보자. 어떤 차는 문이 2개, 이름은 슈퍼카, 속도는 100km/h이고 또 다른 차는 문이 4개, 이름은 울트라카... 이런식으로 각각의 특성을 가지게 만들어야 하는 경우를 가정해보자.
이때 방법은 2가지가 있다.
1) 인스턴스를 생성하고 변수를 호출하여 값을 대입한다
2) 매개변수가 있는 생성자를 사용하여 인스턴스 생성 시 바로 초기화 시킨다
1번째 방법을 코드로 구성해보겠다.
class car{
int door;
String name;
int speed;
}
class make{
car c1 = new car();
car c2 = new car();
car c3 = new car();
car c4 = new car();
c1.door = 4;
c1.name = "슈퍼카";
c1.speed = 100;
c2.door = 2;
c2.name = "울트라카";
c2.speed = 120;
}
이런 식으로 만들면 코드가 너무 길어진다. 간결하고 직관적으로 만들기 위해 생성자를 사용하는 2번째 방법을 코드로 구성해보겠다.
class car{
int door;
String name;
int speed;
car(int door, String name, int speed){
this.door = door;
this.name = name;
this.speed = speed;
}
}
class make{
car c1 = new car(4,"슈퍼카",100);
car c2 = new car(2,"울트라카",120);
}
코드가 훨씬 간결해지고 직관적으로 볼 수 있게 되는 것을 알 수 있다.
한 클래스 내부에서 생성자가 다른 생성자를 호출하고자 할 경우에는 어떻게 해야할까?
생성자 간 서로 호출이 가능하려면 다음 두 조건을 만족해야 한다.
1) 생성자의 첫 줄에서만 가능하다
2) 생성자의 이름으로 this를 사용한다
다음 예제를 보자.
class Car{
int door;
String name;
int speed;
Car(int door, String name){
this.door = door;
this.name = name;
} // 문의 개수와 이름만 초기화하는 생성자
Car(int door, String name, int speed){
this.door = door;
this.name = name;
this.speed = speed;
} // 전부 초기화하는 생성자
}
class make{
public static void main(String args[]){
Car c1 = new car();
Car c2 = new car();
}
}
두 번째 생성자의 코드를 좀 더 간결하게 하기 위해 첫 번째 생성자의 내용을 이용하고 싶다. 이때 this()를 사용하면 된다.
class Car{
int door;
String name;
int speed;
Car(int door, String name){
this.door = door;
this.name = name;
} // 문의 개수와 이름만 초기화하는 생성자
Car(int door, String name, int speed){
this(door,name);
this.speed = speed;
} // 전부 초기화하는 생성자
}
class make{
public static void main(String args[]){
car c1 = new car();
car c2 = new car();
}
}
door, name 을 초기화 하는 내용을 첫 번째 생성자로 대처하였고 이때 this()를 사용하였는데 이는 생성자를 가리키는 것이다. this(door,name)처럼 매개변수의 개수를 같게 하여 첫 번째 생성자를 호출한 것이다.
Car(int door, String name, int speed){
this.speed = speed;
this(door,name);
} // 전부 초기화하는 생성자 //컴파일 에러
만약 코드를 이렇게 구성했다면 컴파일 에러가 발생할 것이다. 1번째 조건인 생성자의 첫 줄에서만 호출한다는 것이 위배되었기 때문이다.
this()를 응용하여 자동차 클래스로 인스턴스를 생성할 때 문의 개수, 이름, 속도 중 아무 옵션도 선택하지 않으면 기본값으로 설정되고 이름만 선택하면 나머지는 기본값 그리고 옵션을 전부 선택할 경우 선택한대로 출력되도록 코드를 구성해보자.
class Car{
int door;
String name;
int speed;
Car(){
this.door = 4;
this.name = "Basic Model";
this.speed = 140;
}
Car(String name){
this();
this.name = name;
} // 이름만 선택
Car(int door, String name, int speed){
this.door = door;
this.name = name;
this.speed = speed;
} // 옵션 전부 선택
}
class make{
public static void main(String args[]){
Car c1 = new Car(); // 기본 옵션
Car c2 = new Car("스포티지"); // 이름만 선택
Car c3 = new Car(2,"스포츠카",200); // 풀옵션
System.out.println(c1.door + "/" + c1.name + "/" +c1.speed);
System.out.println(c2.door + "/" + c2.name + "/" +c2.speed);
System.out.println(c3.door + "/" + c3.name + "/" +c3.speed);
}
}
이처럼 생성자들끼리 유기적으로 연결해주면 더 좋은 코드 구성이 가능해진다.
this()와 this는 같은 것일까?
비슷해보이지만 엄연히 다르다.
this() 또는 this(매개변수)는 생성자로 같은 클래스의 다른 생성자 호출 시 사용한다.
this는 인스턴스 자신을 가리키는 참조변수로 인스턴스의 주소값을 가진다.
앞에서 this.name = name 이런식으로 코드를 구성했는데 여기서 this.name은 인스턴스 변수를 말하는 것이고 this가 인스턴스 자신을 가리키는 것이다.
그렇다면 this를 굳이 왜 사용할까?
Car(int d, String n, int s){
door = d;
name = n;
speed = s;
}
그냥 이렇게 구성하면 되지 않나?
여기서는 매개변수와 인스턴스 변수가 이름이 달라 서로 구별이 되므로 문제가 되지 않는다. 하지만, 생성자의 매개변수로 인스턴스 변수들의 초기값을 제공받는 경우가 많기 때문에 매개변수와 인스턴스 변수의 이름이 일치하는 경우가 많다.(아마 직관적으로 이해하기 쉬우려고 그런듯하다)
Car(int door, String name, int speed){
this.door = door;
this.name = name;
this.speed = speed;
}
이처럼 매개변수와 인스턴스 변수의 이름이 같을 경우 이를 구별해주기 위해서는 this 참조변수를 붙여야 한다. 그렇지 않으면 둘 다 지역변수로 간주된다.
정리하자면, 참조변수 this를 붙이면 인스턴스 자신을 가리키기 때문에 this.door을 인스턴스 변수를 의미하고 door은 생성자의 매개변수를 의미한다.
생성자 외에도 메서드에서도 this를 사용할 수 있다. 하지만 클래스 메서드(static 메서드)에서는 인스턴스 멤버들을 사용할 수 없는 것처럼 this도 사용이 불가하다.
클래스 메서드는 인스턴스를 생성하지 않고 호출될 수 있으므로 클래스 메서드가 호출된 시점에 인스턴스가 존재하지 않을 수 있기 때문이다.
Book 이라는 클래스에서 여러 개의 인스턴스를 만들었다. 4번째 인스턴스는 3번째 인스턴스를 복사하고자 한다.(여기서 복사란 인스턴스 변수 값을 그대로 가져오는 것을 말한다)
Object 클래스의 clone 메서드를 이용하면 간단히 인스턴스 복사가 가능하지만 생성자를 이용한 방법도 있다.
class Book{
int price;
String title;
long page;
Book(Book b){
price = b.price;
title = b.title;
page = b.page;
} // 인스턴스 복사를 위한 생성자
}
public class publish {
public static void main(String[] args) {
Book b1 = new Book(10000,"돈키호테",600);
Book b2 = new Book(b1);
System.out.println("b1.price = " + b1.price + " b1.title = " + b1.title + " b1.page = " + b1.page);
System.out.println("b2.price = " + b2.price + " b2.title = " + b2.title + " b2.page = " + b2.page);
}
}
//b1.price = 10000 b1.title = 돈키호테 b1.page = 600
//b2.price = 10000 b2.title = 돈키호테 b2.page = 600
이렇게 생성자 매개변수로 참조변수를 이용하면 복사가 가능하다.