생성자 - 필요한 이유

객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Constructor)를 이용하면 된다.

생성자를 알아보기 전에 먼저 생성자가 왜 필요한지 코드로 알아보자.

 

[MemberInit 클래스 생성]

package construct;

public class MemberInit {
    String name;
    int age;
    int grade;
}

 

[MethodInitMain1]

package construct;

public class MethodInitMain1 {
    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
        member1.name = "user1";
        member1.age = 15;
        member1.grade = 90;

        MemberInit member2 = new MemberInit();
        member2.name = "user2";
        member2.age = 16;
        member2.grade = 80;

        MemberInit[] members = {member1, member2};

        for (MemberInit s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
        }
    }
}

실행 결과

회원 객체를 생성하고 나면 name, age, grade 같은 변수에 초기값을 설정한다. 초기값을 설정하는 부분이 계속 반복되고 있는데, 메서드를 사용해서 반복을 제거해보자.

 

[MethodInitMain2]

package construct;

public class MethodInitMain2 {
    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
        initMember(member1, "user1", 15, 90);

        MemberInit member2 = new MemberInit();
        initMember(member2, "user2", 16, 80);

        MemberInit[] members = {member1, member2};

        for (MemberInit s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
        }
    }
    static void initMember(MemberInit member, String name, int age, int grade){
        member.name = name;
        member.age = age;
        member.grade = grade;
    }
}

initMember(...) 메서드를 사용해서 반복을 제거했다. 그런데 이 메서드는 대부분 MemberInit 객체의 멤버 변수를 사용한다. 이런 경우 속성과 기능을 한 곳에 두는 게 낫다. MemberInit이 자기 자신의 데이터를 변경하는 기능(메서드)을 제공하는 것이 좋다.

 


this

 

[MemberInit & MethodInitMain3]

package construct;

public class MemberInit {
    String name;
    int age;
    int grade;

    //추가
    void initMember(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
}
package construct;

public class MethodInitMain3 {
    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
        member1.initMember("user1", 15, 90);

        MemberInit member2 = new MemberInit();
        member2.initMember("user2", 16, 80);

        MemberInit[] members = {member1, member2};

        for (MemberInit s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
        }
    }
}

 

MemberInit의 코드를 보면 메서드의 매개변수에 정의한 String name과 MemberInit 내의 멤버 변수 이름이 String name으로 둘다 똑같다. (String name의 중복)

age, grade도 마찬가지이다.

 

멤버 변수와 메서드의 매개변수의 이름이 같으면 둘을 어떻게 구분해야 할까?

  • 이 경우 멤버 변수보다 매개변수가 코드 블럭과 더 가까이 있기 때문에 매개변수가 우선순위를 가진다. 따라서 initMember(...) 메서드 내에서 name이라고 적으면 메서드의 매개변수에 접근하게 된다. 
  • 멤버 변수에 접근하려면 앞에 this.이라고 해주면 된다.
    this: 인스턴스 자신의 참조값

[진행 과정]

this.name = name; //1. 오른쪽의 name은 매개변수에 접근
this.name = "user"; //2. name 매개변수의 값 사용
x001.name = "user"; //3. this.은 인스턴스 자신의 참조값을 뜻함, 따라서 인스턴스의 멤버 변수에 접근

 

[정리]

  • 매개변수의 이름과 멤버 변수의 이름이 같은 경우 this를 사용해서 둘을 명확하게 구분해야 한다.
  • this는 인스턴스 자신을 가리킨다.

 

this의 생략

this는 생략할 수 있다. this를 생략하면 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수의 일종)를 먼저 찾고, 없으면 그 다음으로 멤버 변수를 찾는다. 멤버 변수도 없으면 오류가 발생한다.

package construct;

public class MemberThis {
    String nameField;

    void initMember(String nameParameter){
        nameField = nameParameter;
    }
}

this.nameField에서 this가 생략된 것이다.

 

  • nameField: 가까운 지역변수인 매개변수에 nameField가 없기 때문에 그 다음으로 멤버 변수를 찾는다.
  • nameParameter: 가까운 지역변수인 매개변수에 nameParameter가 있기 때문에 매개변수를 사용한다.

this는 이름이 중복되는 것처럼 꼭 필요한 경우에만 사용해도 충분하다!

 


생성자 - 도입

프로그래밍을 하다보면 객체를 생성하고 바로 초기값을 할당해야 하는 경우가 많다. 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 편리하게 수행할 수 있도록 생성자라는 기능을 제공한다. 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행할 수 있다.

생성자는 메서드와 유사하지만 몇 가지 다른 특징이 있다.

 

[클래스: MemberConstruct]

public class MemberConstruct {
    String name;
    int age;
    int grade;

    MemberConstruct(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
        System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
    }
}

 

이 부분이 바로 생성자이다.

    MemberConstruct(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
        System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
    }

생성자는 메서드와 비슷하지만 다음과 같은 차이가 있다.

  1. 생성자의 이름은 클래스의 이름과 같아야 한다. 따라서 첫 글자도 클래스와 같이 대문자로 시작한다.
  2. 생성자는 반환 타입이 없다. (void와 같은 반환 타입을 앞에 쓰지 않음)
package construct;

public class ConstructMain1 {
    public static void main(String[] args) {
        MemberConstruct member1 = new MemberConstruct("user1", 15, 90);
        MemberConstruct member2 = new MemberConstruct("user2", 16, 80);
        // 객체를 생성함과 동시에 호출

        MemberConstruct[] members = {member1, member2};

        for (MemberConstruct s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade );
        }
    }
}

 

실행 결과

 

생성자 호출

생성자는 인스턴스를 생성하고 나서 즉시 호출된다. 생성자를 호출하는 방법은 new 명령어 다음에 생성자 이름과 매개변수에 맞춰 인수를 전달하면 된다.

new 생성자이름 (생성자에 맞는 인수 목록)
new 클래스이름 (생성자에 맞는 인수 목록)

생성자이름 = 클래스이름이기 때문에 둘 다 맞는 표현이다.

 

new MemberConstruct("user1", 15, 90);

이렇게 하면 인스턴스를 생성하고 즉시 해당 생성자를 호출한다.

여기서는 member1 인스턴스를 생성하고 바로 MemberConstruct(String name, int age, int grade) 생성자를 호출한다.

 

생성자 장점

중복 호출 제거

생성자가 없던 시절에는 생성 직후에 어떤 작업을 수행하기 위해 다음과 같이 메서드를 직접 한번 더 호출해야 했다.

생성자 덕분에 객체를 생성하면서 동시에 생성 직후에 필요한 작업을 한번에 처리할 수 있게 되었다.

//생성자 등장 전
MemberInit member1 = new MemberInit();
member1.initMember("user1", 15, 90);

//생성자 등장 후
MemberConstruct member1 = new MemberConstruct("user1", 15, 90);

 

제약 - 생성자 호출 필수

생성자 등장 전의 코드에서 initMember( ) 메서드를 실수로 호출하지 않아도 프로그램은 작동한다. 하지만 회원의 이름, 나이, 성적 데이터가 없는 상태로 프로그램이 동작하게 된다. 만약 이 값들을 필수로 반드시 입력해야 한다면 시스템에 큰 문제가 발생할 수 있다. 결국 아무 정보가 없는 유령 회원이 시스템 내부에 등장하게 된다.

 

생성자를 직접 정의한 경우, 직접 정의한 생성자를 반드시 호출해야 한다.

MemberConstruct 클래스의 경우 다음 생성자를 직접 정의했기 때문에 직접 정의한 생성자를 반드시 호출해야 한다.

MemberConstruct(String name, int age, int grade){...}

 

다음과 같이 직접 정의한 생성자를 호출하지 않으면 컴파일 오류가 발생한다.

package construct;

public class ConstructMain1 {
    public static void main(String[] args) {
        MemberConstruct member1 = new MemberConstruct(); //컴파일 오류 발생
        MemberConstruct member2 = new MemberConstruct("user2", 16, 80);

        MemberConstruct[] members = {member1, member2};

        for (MemberConstruct s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade );
        }
    }
}

 

생성자 덕분에 회원의 이름, 나이, 성적을 필수로 입력하게 된다. 따라서 아무 정보가 없는 유령 회원이 시스템 내부에 등장할 가능성을 원천 차단한다.

 


기본 생성자

이전 기억을 떠올려보면 생성자를 만들지 않았는데도 우리는 생성자를 매번 호출하였다.

public class MemberInit {
    String name;
    int age;
    int grade;
}
package construct;

public class MethodInitMain1 {
    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
    	...
    }
}

여기서 new MemberInit( ); 부분은 분명히 매개변수가 없는 다음과 같은 생성자가 필요할 것이다.

package construct;

public class MemberInit {
    String name;
    int age;
    int grade;

    MemberInit(){ //생성자

    }
}

 

기본 생성자

  • 매개 변수가 없고, 작동하는 코드가 없는 생성자
  • 클래스에 생성자가 하나도 없으면 자바는 기본 생성자를 자동으로 만들어준다. (우리 눈에 보이지는 않는다.)
  • 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않는다.

MemberInit 클래스의 경우 생성자를 만들지 않았으므로 자바가 자동으로 기본 생성자를 만들어준 것이다.

 

물론 다음과 같이 기본 생성자를 직접 정의해도 된다.

package construct;

public class MemberInit {
    String name;
    int age;
    int grade;

    MemberInit(){ //기본 생성자 직접 정의
        System.out.println("생성자 호출");
    }
    
    void initMember(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
}
package construct;

public class MethodInitMain1 {
    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
        member1.name = "user1";
        member1.age = 15;
        member1.grade = 90;

        MemberInit member2 = new MemberInit();
        member2.name = "user2";
        member2.age = 16;
        member2.grade = 80;

        MemberInit[] members = {member1, member2};

        for (MemberInit s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
        }
    }
}

실행 결과

 

기본 생성자를 왜 자동으로 만들어줄까?

만약 자바에서 기본 생성자를 만들어주지 않으면 생성자 기능이 필요하지 않은 경우에도 모든 클래스에 개발자가 직접 기본 생성자를 정의해야 한다. 생성자 기능을 사용하지 않는 경우도 많기 때문에 이런 편의 기능을 제공한다.

 

정리

  • 생성자는 반드시 호출되어야 한다.
  • 생성자가 없으면 기본 생성자가 제공된다.
  • 생성자가 하나라도 있으면 기본 생성자가 제공되지 않는다. 이 경우 개발자가 정의한 생성자를 직접 호출해야 한다.

 


생성자 - 오버로딩과 this( )

메서드 오버로딩처럼, 생성자도 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.

 

[MemberConstruct - 생성자 추가]

package construct;

public class MemberConstruct {
    String name;
    int age;
    int grade;

    MemberConstruct(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
        System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
    }

    //추가
    MemberConstruct(String name, int age){
        this.name = name;
        this.age = age;
        this.grade = 50;
    }
}

 

기존 MemberConstruct에 생성자를 하나 추가해서, 생성자가 2개가 되었다.

MemberConstruct(String name, int age, int grade)
MemberConstruct(String name, int age)

 

새로 추가한 생성자는 grade를 받지 않는다. 대신에 grade는 50점이 된다.

 

package construct;

public class ConstructMain2 {
    public static void main(String[] args) {
        MemberConstruct member1 = new MemberConstruct("user1", 15, 90);
        MemberConstruct member2 = new MemberConstruct("user2", 16);

        MemberConstruct[] members = {member1, member2};

        for (MemberConstruct s : members) {
            System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade );
        }
    }
}

실행 결과

생성자를 오버로딩한 덕분에 성적 입력이 꼭 필요한 경우에는 grade가 있는 생성자를 호출하면 되고, 그렇지 않은 경우에는 grade가 없는 생성자를 호출하면 된다. grade가 없는 생성자를 호출하면 성적은 50점이 된다.

 


this( )

두 생성자를 비교해 보면 코드가 중복되는 부분이 있다.

MemberConstruct(String name, int age, int grade){
    this.name = name;
    this.age = age;
    this.grade = grade;
    System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
}

//추가
MemberConstruct(String name, int age){
    this.name = name;
    this.age = age;
    this.grade = 50;
}

 

바로 다음 부분이다.

this.name = name;
this.age = age;

 

이때 this( )라는 기능을 사용하면 생성자 내부에서 자신의 다른 생성자를 호출할 수 있다.

코드를 다음과 같이 수정해보자.

package construct;

public class MemberConstruct {
    String name;
    int age;
    int grade;

    MemberConstruct(String name, int age, int grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
        System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
    }

    //추가
    MemberConstruct(String name, int age){
        this(name, age, 50); //변경한 부분
    }
}

 

이 코드는 두 번째 생성자 내부에서 첫 번째 생성자를 호출한다.

MemberConstruct(String name, int age) -> MemberConstruct(String name, int age, int grade)

 

this( )를 사용하면 생성자 내부에서 다른 생성자를 호출할 수 있다. 이 부분을 잘 활용하면 지금처럼 중복을 제거할 수 있다.

 

this( ) 규칙

this( )는 생성자 코드의 첫 줄에만 작성할 수 있다.

 

다음은 규칙 위반이다. 이 경우 컴파일 오류가 발생한다.

MemberConstruct(String name, int age){
    System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade);
    this(name, age, 50); //변경한 부분
}

this( )가 생성자 코드의 첫줄에 사용되지 않아 오류가 발생한 화면

+ Recent posts