클래스의 멤버 변수들을 초기화해주는 블럭(혹은 메서드)을 생성자라고 한다. 생성자의 이름은 클래스 이름과 같고, 리턴값은 없다.
한편, 생성자를 특별히 구현해주지 않아도 new 클래스이름()을 통해 객체를 생성할 수 있고, 이 생성된 객체의 멤버 변수 값을 찍어보면 모두 초기화가 된 것을 살펴볼 수 있는데, 그것은 디폴트 생성자가 있기 때문이다. 디폴트 생성자의 형태는 다음과 같다.
class Stduent {
public Stduent() { }
}
한편, 커스텀 생성자를 만들 경우에는 디폴트 생성자는 명시해주지 않으면 존재하지 않는다. 그러나 이후에 발생할 수 있는 에러를 막기 위해 디폴트 생성자는 반드시 명시적으로 적어두는 편이 좋다.
코드를 짜면서 디폴트 생성자가 있어서 편하다고 생각했던 경험이 있는데, 예를 들어 BookStore 클래스가 Book 객체 배열을 멤버 변수로 갖는 다음과 같은 경우,
public class BookStore {
Book[] books;
}
멤버 변수를 선언하며 초기화해줄 수 있는데, 이때 Book 객체의 값을 특정해줄 수 없기 때문에 디폴트 생성자를 통해 다음과 같이 초기화 해줄 수 있었다.
public class BookStore {
Book[] books = {new Book(), new Book(), new Book()};
}
물론 BookStore의 생성자에서 객체 배열을 생성해주고, 전달받은 객체 배열의 길이만큼만 초기화를 해주는 방법도 있겠지만, 이 경우에는 배열 뒤쪽이 초기화 되지 않아 null pointer exception이 발생하기 쉽다.
this.books = new Book[5];
for(int i = 0; i < books.length; i++) {
this.books[i] = books[i];
}
(물론 이게 다 고정적인 메모리 공간을 가지는 배열의 문제니 다른 자료형을 쓰면 되긴 하다..^-^;)
디폴트 생성자를 명시적으로 적어주는 것은 상속 관계를 구현할 때에 더욱 중요하다. 자식 클래스를 만들고 생성자를 따로 만들지 않으면 자식 클래스의 디폴트 생성자가 호출이 되는데, 이때 부모 클래스에서 매개변수가 없는 디폴트 생성자를 명시에 두지 않았다면 오류가 발생할 수 있다.
public class SpecialBookStore {
private int specialThing;
public SpecialBookStore(int specialThing) {
super(); // 부모 클래스의 디폴트 생성자 호출
this.specialThing = specialThing;
}
}