생성자

2024. 2. 1. 14:36Java

◎ 생성자

-> 객체를 생성하는 시점에 어떤 작업을 수행하기 위해 사용한다.

public class MemberInit {
    String name;
    int age;
    int grade;
}
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 member : members) {
            System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 성적 : " + member.grade);
        }
    }
}

-> 결과

-> 위 코드는 Member 객체를 생성하고 name, age, grade 변수에 값을 초기화한다. 

-> 만약 객체를 추가로 생성한다면 name, age, grade 변수를 초기화하는 코드가 반복될 것이다.

객체 추가에 따라 반복되는 코드

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 member : members) {
            System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 성적 : " + member.grade);
        }
    }

    static void initMember(MemberInit member, String name, int age, int grade) {
        member.name = name;
        member.age = age;
        member.grade = grade;
    }
}

-> 이번에는 MethodInitMain2 클래스에 initMember()메서드를 추가하여 해당 메서드에서 name,age, grade의 값을 설정하도록 했다.

-> initMember()는 name, age, grade라는 MemberInit 객체의 멤버 변수를 사용한다. 이 경우에는 클래스에 속성과 기능을 하나로 묶어 멤버 변수의 값을 설정하는 기능을 Memberinit 클래스에서 수행하도록 변경하는 것이 더 객체 지향적인 코드가 될 것 같다.

-> 그래서 이번에는 initMember()메서드를 MemberInit 클래스에 추가를 해본다.

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;
    }
}
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 member : members) {
            System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 성적 : " + member.grade);
        }
    }
}

-> main()메서드에서 MemberInit 인스턴스를 생성하고 initMember()메서드를 호출하여 매개변수로 name, age, grade를 넘겨 MemberInit클래스 내부에 있는 initMember()메서드가 작업을 수행했다.

 

◎ this

-> MemberInit 클래스의 initMember()메서드 내부를 보면 this.name = name처럼 this.멤버변수 = 변수 형태의 코드가 작성되어 있다.

-> 그리고 initMember()의 매개변수와 MemberInit클래스의 멤버변수는 모두 name, age, grade로 같다.

-> 이런 경우 this를 통해 해당 변수가 멤버 변수인지 매개 변수인지 구분을 해준다.

-> 참고로 해당 코드에서는 매개변수가 멤버 변수보다 코드 블럭의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가진다.

-> 'this.변수'는 멤버 변수에 접근하게 한다. this.name은 MemberInit 인스턴스의 멤버 변수인 name이 되는 것이다.

-> this는 인스턴스 자신의 참조값을 가리킨다.

-> this.name은 아래와 같은 과정을 거치며 MemberInit인스턴스의 멤버 변수에 "user1"값을 대입한다.

this.name = name; 
this.name = "user1";
x001.name = "user1";

 

-> 만약 initMember() 메서드 내부에 this를 제거하면 어떤 결과가 나올까?

-> 인스턴스를 참조하던 this가 없어졌기 때문에 더 이상 멤버 변수를 참조하지 않는다. 따라서 양 변에 있는 변수들은 모두 매개변수 name, age, grade가 된다.

-> 이대로 코드를 실행해본다.

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 member : members) {
            System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 성적 : " + member.grade);
        }
    }
}

-> 결과

-> member1.initMember()는 MemberInit()의 참조값.initMember()가 되고, initMember()를 통해 매개변수를 넘겼다.

-> 하지만 initMember()내부에는 클래스의 멤버 변수를 참조하지 않기 때문에 멤버 변수는 모두 자동 초기화되어 각각 nill, 0, 0이 출력이 된다.

 

◎ this 생략

-> this는 생략 가능하다.

-> 변수를 찾을 때 가장 가까운 지역변수(매개변수)를 먼저 찾고 없다면 멤버 변수를 찾는다. 만약 멤버 변수도 없으면 오류가 발생한다.

public class MemberThis {
    String nameField;

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

-> 위 코드는 매개변수와 멤버변수의 이름이 다르다. 

-> nameField는 가까운 지역변수를 먼저 찾는다. 지금 코드에서는 없기 때문에 다음으로 멤버변수를 찾는다. MemberThis클래스의 멤버변수로 nameField가 선언되어 있기 때문에 initMember()메서드 내부의 nameField는 MemberThis의 멤버 변수다.

-> nameParameter는 지역변수(매개변수)에서 먼저 찾는다. 지금 코드에서는 매개변수에 nameParameter가 있기 때문에 nameParameter는 initMember()메서드의 매개변수가 된다.

 

◎ 생성자 활용

-> 지금까지 인스턴스를 생성하고 반환된 참조 값을 변수에 저장하여 해당 변수로 인스턴스에 접근하여 초기값을 할당하였다.

-> 그래서 지금까지 작성한 코드들을 보면 인스턴스를 생성하고 매번 초기값을 할당하는 메서드를 만들어야 했다.

-> 생성자를 활용하면 인스턴스 생성과 동시에 초기값을 세팅할 수 있다.

package construct;

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

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

-> 아래 코드가 생성자다.

생성자

-> 생성자는 클래스의 이름과 같아야 하며, 반환 타입이 없다. 

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

        MemberConstructor[] members = {member1, member2};

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

-> 결과

-> new MemberConstructor("user1", 15, 90);은 인스턴스를 생성하고 즉시 생성자를 호출한다. 

-> 생성자를 통해 인스턴스 생성과 동시에 초기값 설정과 같은 필요한 작업을 바로 처리할 수 있어 이전처럼 인스턴스 생성하고, 메서드를 호출하는 코드를 작성할 필요가 없어졌다.

MemberInit member = new MemberInit();
member.initMember("user1", 15, 90);

// 생성자로 한 번에 처리
MemberConstructor member = new MemberConstructor("user1", 15, 90);

 

-> 위 코드에서 만약 member.initMember()메서드를 실수로 호출하지 않았다면 해당 위치의 MemberInit인스턴스에 값이 초기화 되지 않은 채 그대로 코드가 실행될 것이다.

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 member : members) {
            System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 성적 : " + member.grade);
        }
    }
}

-> 만약 해당 인스턴스에 반드시 값이 들어갔어야 했는데 이대로 실행된다면 큰 문제가 발생할 것이다. 

-> 기존 메서드 호출 방식은 코드를 실행하기 전 까지 지나친 부분을 발견하지 못할 위험성이 있다.

-> 반면 생성자는 사용자가 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다.

-> MemberConstructor 클래스의 아래 생성자를 정의했다면 반드시 해당 생성자를 호출해야 한다.

-> MemberConstructor() 생성자의 형식을 지키지 않은 경우 위 코드처럼 빨간 밑줄이 생긴다.

-> 그대로 실행하면 다음과 같은 오류가 발생한다.

-> 필수값이 들어가지 않은 경우 즉시 오류를 확인할 수 있기 때문에 필수값 입력을 보장할 수 있다.

 

◎ 기본 생성자

-> 사실 이전에도 인스턴스를 생성하면서 생성자를 사용했다.

-> 위 코드에 MemberInit()이 있는데 이것 역시 생성자다.

-> 하지만 MemberInit 클래스를 보면 생성자를 따로 만들지 않았다.

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

-> 자바 컴파일러는 클래스에 생성자가 하나도 없는 경우 매개변수가 없는 생성자를 자동으로 만들어준다. 이렇게 만들어지는 매개변수가 없는 생성자를 기본 생성자라고 한다.

-> 만약 클래스 내에 생성자가 하나라도 있다면 자바는 기본 생성자를 만들지 않는다.

-> 위 코드에서 Memberinit 인스턴스를 생성하기 위해 new MemberInit(); 코드를 작성했는데, MemberInit 클래스 내부에는 생성자가 하나도 없었기 때문에 매개변수가 없는 기본 생성자를 만들어 별다른 작업 없이 참조값만 반환했다.

-> 기본 생성자는 직접 정의할 수도 있다.

public class MemberDefault {
    String name;

    MemberDefault(){
        System.out.println("생성자 호출");
    }
}
public class MemberDefaultMain {
    public static void main(String[] args) {
        MemberDefault memberDefault = new MemberDefault();
    }
}

-> 결과

 

※ 기본 생성자는 왜 자동으로 생성될까?

- 자바에서 기본 생성자를 자동으로 만들지 않는다면 생성자의 별다른 기능 수행이 필요 없는 경우에도 모든 클래스에 직접 기본 생성자를 정의해야 할 것이다. 이런 불편함을 해결하기 위해 자동으로 생성한다.

 

◎ 생성자 - 오버로딩

-> 생성자도 메서드 처럼 오버로딩을 통해 매개변수만 다르게 하여 여러 생성자를 정의할 수 있다.

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

    // 생성자
    MemberConstructor(String name, int age) {
        this.name = name;
        this.age = age;
        this.grade = 50;
    }

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

-> 참고로 위 코드에서 사용자 정의 생성자가 있고, 따로 기본 생성자를 생성하지 않았기 때문에 기본 생성자는 사용할 수 없다.

-> 생성자를 두 개 만들었는데 하나는 name,age만 매개변수로 가지는 생성자, 하나는 name, age, grade를 매개변수로 가지는 생성자다.

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

        MemberConstructor[] members = {member1, member2};

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

-> 결과

-> user2는 name과 age만 매개변수로 받는 생성자를 사용했다. 해당 생성자는 name과 age만 매개변수로 받아 해당 멤버 변수를 초기화하고, grade는 50으로 설정했기 때문에 user2의 성적은 50이 출력된다.

-> user1은 name, age, grade 모두 매개변수로 받아 멤버 변수 값을 설정하여 출력되었다.

 

생성자 - this()

-> 위에서 작성한 생성자를 보면 코드의 중복을 확인할 수 있다.

-> this()를 사용하면 생성자 내부에서 자신의 생성자를 호출할 수 있다. 

※ this() vs this.

this() : 생성자 내부에서 자신의 생성자 호출

this. : 인스턴스 자신의 참조값을 가리킴

-> 이제 MemberConstructor 생성자에 this()를 사용하도록 수정해본다.

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

    // 생성자
    MemberConstructor(String name, int age) {
        this(name, age, 50);
    }

    // 생성자
    MemberConstructor(String name, int age, int grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
}

-> this(name, age, 50)으로 자신의 생성자를 호출하는데 매개변수가 name, age, grade를 가지는 생성자가 호출이 된다.

-> this()를 통해 생성자 간 코드 중복을 제거할 수 있다.

-> this()는 반드시 생성자 코드의 첫 줄에만 작성 가능하다.

-> 만약 this()가 생성자 코드의 첫 줄에 위치하지 않으면 컴파일 에러가 발생한다.

 

 

 

 

★ 참고

김영한의 실전 자바 - 기본편

'Java' 카테고리의 다른 글

자바 메모리 구조 & static  (0) 2024.02.06
접근 제어자  (0) 2024.02.05
객체 지향 프로그래밍  (0) 2024.01.31
기본형 & 참조형  (0) 2024.01.30
클래스  (0) 2024.01.24