3) 생성자

이번에는 클래스의 생성자(Constructor)에 대해서 알아보자.

 

다음은 지금까지 만들어 온 클래스들이다.

 

Animal.java


public class Animal {
    String name;

    public void setName(String name) {
        this.name = name;
    }
}

 

Dog.java


public class Dog extends Animal {
    public void sleep() {
        System.out.println(this.name+" zzz");
    }
}

 

HouseDog.java


public class HouseDog extends Dog {
    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();
    }
}

 

HouseDog 클래스로 만든 인스턴스에 setName 메소드로 이름을 세팅하지 않고 다음과 같이 호출 해 보자.

 


HouseDog dog = new HouseDog();
System.out.println(dog.name);

 

name 인스턴스 변수에 아무런 값이 설정되지 않았기 때문에 null이 출력될 것이다.

 

이렇듯 HouseDog 클래스는 코딩하기에 따라 인스턴스 변수 name에 값을 세팅할 수도 있고 세팅 안할 수도 있다. 그렇다면 name 이라는 인스턴스 변수에 값을 무조건 세팅해야만 컴파일이 되도록 강제할 수 있는 방법은 없을까?

 

생성자(Constructor)를 이용하면 된다.

 

다음과 같이 HouseDog 클래스를 변경해 보자.

 


public class HouseDog extends Dog {
    public HouseDog(String name) {
        this.setName(name);
    }  

    public void sleep() {
        System.out.println(this.name + " zzz in house");
    }

    public static void main(String[] args) {
        HouseDog dog = new HouseDog("happy");
        System.out.println(dog.name);
    }
}

 

위에 작성한 public HouseDog(String name) { ... 과 같이 메소드명이 클래스명과 동일하고 리턴 자료형이 없는 경우 이 메소드는 생성자라고 말한다.

 

생성자의 규칙

  1. 클래스명과 메소드명이 동일하다.
  2. 리턴타입을 정의하지 않는다.

 

위와 같이 HouseDog 클래스에 생성자를 정의한 후 다음처럼 작성하면 컴파일 오류가 발생한다.

 


HouseDog dog = new HouseDog();

 

오류가 발생하는 이유는 생성자가 선언되어 있기 때문이다. 생성자가 선언된 경우 생성자의 규칙대로만 객체를 생성할 수 있게 된다. 컴파일 오류를 해결하기 위해서는 아래와 같이 생성자에 문자열을 입력으로 넘겨주어야만 한다.

 


HouseDog dog = new HouseDog("happy");

 

위와 같이 변경하면 생성자 메소드가 자동으로 호출되고 생성자의 입력변수로 "happy"라는 문자열이 전달된다. 생성자로 전달된 "happy" 는 setName메소드에 의해서 인스턴스 변수 name에 세팅될 것이다.

 

이렇듯 생성자를 사용했을 때 얻게 되는 이득은 setName("happy")와 같은 필수적인 행동을 인스턴스 생성시에 제어할 수 있게 된다는 점이다.

 

HouseDog 클래스의 main 메소드 실행 시 역시 동일한 다음의 결과를 출력하는 것을 확인 할 수 있다.

 


happy

 

default 생성자

 

이번에는 default 생성자에 대해서 알아보자.

 

다음의 코드를 보자.

 


public class Dog extends Animal {
    public void sleep() {
        System.out.println(this.name + " zzz");
    }
}

그리고 다음 코드를 보자.

 


public class Dog extends Animal {
    public Dog() {
    }

    public void sleep() {
        System.out.println(this.name + " zzz");
    }
}

 

첫번 째 코드와 두번 째 코드의 차이점은 무엇일까? 두번 째 코드에는 생성자가 구현되어 있다. 입력 항목이 없는 위와 같은 생성자를 default 생성자라고 부른다.

 

위와 같이 디폴트 생성자를 구현하면 new Dog() 로 Dog 객체가 만들어 질 때 위 디폴트 생성자가 실행된다.

 

만약 클래스에 생성자가 하나도 없다면 컴파일러는 자동으로 위와같은 디폴트 생성자를 추가한다. 하지만 사용자가 작성한 생성자가 하나라도 구현되어 있다면 컴파일러는 디폴트 생성자를 추가하지 않는다.

 

※ 이러한 이유로 위에서 살펴본 HouseDog 클래스에 name을 입력으로 받는 생성자를 만든 후에 new HouseDog() 는 사용할 수 없게 되는 것이다.

 

생성자 오버로딩

 

하나의 클래스에 여러개의 입력항목이 다른 생성자를 만들 수 있다.

 

즉, 다음과 같은 것이 가능하다.

 


public class HouseDog extends Dog {
    public HouseDog(String name) {
        this.setName(name);
    }

    public HouseDog(int type) {
        if (type == 1) {
            this.setName("yorkshire");
        } else if (type == 2) {
            this.setName("bulldog");
        }
    }

    public void sleep() {
        System.out.println(this.name + " zzz in house");
    }

    public static void main(String[] args) {
        HouseDog happy = new HouseDog("happy");
        HouseDog yorkshire = new HouseDog(1);
        System.out.println(happy.name);
        System.out.println(yorkshire.name);
    }
}

 

위 HouseDog 클래스는 두 개의 생성자를 갖는다. 하나는 String 자료형을 입력으로 받는 생성자이고 다른 하나는 int 자료형을 입력으로 받는 생성자이다. 두 생성자의 차이는 입력 항목이다. 이렇게 입력 항목이 다른 생성자를 여러 개 만들 수 있는데 이런 것을 생성자 오버로딩(Overloading) 이라고 말한다.

 

이제 HouseDog 객체는 다음과 같이 두 가지 방법으로 생성이 가능하다.

 


HouseDog happy = new HouseDog("happy");
HouseDog yorkshire = new HouseDog(1);

 

※ 메소드 오버로딩도 마찬가지 개념이다. 단 메소드 오버로딩은 생성자 오버로딩과 마찬가지로 입력항목은 다를 수 있지만 리턴타입(자료형)은 반드시 일치해야 한다.