관리 메뉴

나구리의 개발공부기록

생성자, 생성자 - 필요한 이유, this, 생성자 - 도입, 기본 생성자, 생성자 - 오버로딩과 this(), 문제와 풀이 본문

인프런 - 실전 자바 로드맵/실전 자바 - 기본편

생성자, 생성자 - 필요한 이유, this, 생성자 - 도입, 기본 생성자, 생성자 - 오버로딩과 this(), 문제와 풀이

소소한나구리 2024. 12. 28. 11:44

출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 생성자 - 필요한 이유

1) 생성자

(1) 필요한 이유

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

(2) MemberInit

package construct;

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

 

(3) MethodInitMain1

  • 회원 객체를 사용하기 제대로 사용하기 위해서는 회원 객체를 생성하고 나면 name, age, 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 member : members) {
            System.out.println("이름:" + member.name + " 나이:" + member.age + " 성적:" + member.grade);
        }
    }
}

/* 출력 결과
이름:user1 나이:15 성적:90
이름:user2 나이:16 성적:80
*/

 

(3) MethodInitMain2

  • initMember(...) 메서드를 사용하여 반복을 제거했지만 이 메서드는 대부분 MemberInit 객체의 멤버 변수를 사용하고 있음
  • 이런 경우에는 속성과 기능을 한 곳에 두는것이 객체 지향 관점에서 더 나은 방법임
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 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;
    }
}

2. this

1) this

(1) MemberInit - initMember() 추가

  • void initMember() 메서드를 추가
  • this로 내 멤버변수로 접근할 수 있음
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;
    }
}

 

(2) MethodInitMain3

  • initMember()는 Member에 초기값 설정 기능을 제공하는 메서드로 member1.initMember("user1", 15, 90)과 같이 호출하면 객체의 멤버 변수에 인자로 넘어온 값을 채움
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 member : members) {
            System.out.println("이름:" + member.name + " 나이:" + member.age + " 성적:" + member.grade);
        }
    }
}
/* 출력결과
이름:user1 나이:15 성적:90
이름:user2 나이:16 성적:80
*/

 

(3-1) this

  • initMember(String name, ...)의 코드를 보면 메서드의 매개변수에 정의한 String name과 Member의 멤버 변수의 이름이 String name 으로 똑같고 나머지 변수도 모두 똑같음
  • 멤버 변수와 메서드의 매개변수의 이름이 같으면 멤버 변수보다 매개변수가 코드 블럭의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가지게 됨
  • 따라서 initMember(String name, ...) 메서드 안에서 name이라고 적으면 매개변수에 접근하게 되는데, 멤버 변수에 접근하려면 앞에 this. 이라고 해주면 되며 여기서 this는 인스턴스 자신의 참조값을 가리킴

(3-2) 진행과정

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

(3-3) this제거

  • 만약 이 예제에서 this를 제거하게 되면 name = name 모두 매개변수를 뜻하게 되어 멤버변수의 값이 변경되지 않음

(3-4) 정리

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

(4) this의 생략 - MemberThis

  • this는 생략할 수 있는데 이 경우에는 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수임)를 먼저 찾고 없으면 그 다음으로 멤버 변수를 찾고 멤버 변수도 없으면 오류가 발생함
  • 아래의 예제는 필드 이름과 매개변수의 이름이 서로 다른 상황인데 nameField는 앞에 this가 없어도 멤버 변수에 접근함
  • nameField는 먼저 지역변수에서 같은 이름이 있는지 찾는데 지역변수에는 nameField가 없으므로 멤버 변수에서 찾음
  • nameParameter도 먼저 지역변수에서 같은 이름이 있는지 찾는데 지역변수에 nameParameter가 있으므로 매개변수를 사용함
package construct;

public class MemberThis {
    String nameField;

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

 

(5) this와 코딩 스타일

public class MemberThis {
    String nameField;

    void initMember(String nameParameter) {
        this.nameField = nameParameter;
    }
}
  • 지금 처럼 멤버 변수에 접근하는 경우 항상 this를 사용하는 코딩 스타일도 있음
  • this.nameField를 보면 this를 생략할 수 있지만 생략하지 않고 사용해도되며 이렇게 this를 사용하면 이 코드가 멤버 변수를 사용한다는 것을 눈으로 쉽게 확인할 수 있음
  • 과거에는 이런 스타일을 많이 사용하여 지역 변수와 멤버 변수를 눈에 잘 보이도록 코드를 작성하였는데 최근에는 IDE가 발전하면서 멤버 변수와 지역 변수를 색상으로 구분을 해줌
  • 그렇기 때문에 최근에는 이런 경우에는 this를 사용하지 않는 코딩 스타일이 더욱 많이 쓰이기 때문에 지역변수와 멤버변수의 이름이 같을때에만 구분을 위하여 this를 사용하는 것을 권장함

** 참고

  • 관습을 따를 때에는 왜? 라는 질문을 하고 따르는 것을 권장하는데 관습을 따르는 이유를 생각했을 때 굳이 쓸 이유가 없다면 그 관습은 굳이 따르지 않아도 된다고 생각한다고 함
  • this의 경우도 멤버변수와 지역변수를 명확히 구분하기위해 this를 항상 붙이는 코딩 스타일을 당연히 써야했지만 지금은 IDE가 색깔로 구분을 해주기 때문에 굳이 명확히 구분되는 상황에서는 사용할 필요가 없다고 생각한다고 함

3. 생성자 - 도입

1) 생성자 도입

(1) 초기값 할당

  • 프로그래밍을 하다보면 객체를 생성하고 이후에 바로 초기값을 할당해야하는 경우가 많아 앞서 만든 initMember()와 같은 메서드를 매번 만들어야 할 수 있음
  • 그래서 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공하며 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행할 수 있음

(2) MemberConstruct

  • Public MemberConstruct(...) 부분이 바로 생성자인데 메서드와 비슷하지만 차이가 있음
  • 생성자의 이름은 클래스 이름과 같아야 하므로 첫 글자도 대문자로 시작함
  • 생성자는 반환 타입이 없어서 비워두어야 함
  • 나머지는 메서드와 같음
package construct;

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

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

 

(2) ConstructMain1

  • 객체를 생성할 때 new 키워드 다음에 입력했던 것이 바로 생성자였음
  • 실행 해보면 생성자에 입력한 출력문과 반복문으로 출력한 출력문이 함께 출력됨
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 member : members) {
            System.out.println("이름: " + member.name + " 나이: " + member.age + " 성적: " + member.grade);
        }
    }
}
/* 실행 결과
생성자 호출 name= user1, age= 15, grade= 90
생성자 호출 name= user2, age= 16, grade= 80
이름: user1 나이: 15 성적: 90
이름: user2 나이: 16 성적: 80
*/

 

(3) 생성자 호출

  • 생성자는 인스턴스를 생성하고 나서 즉시 호출되며 생성자를 호출하는 방법은 new 명령어 다음에 생성자 이름과 매개변수에 맞추어 인수를 전달하면됨
  • 생성자 이름이 클래스이름이기 때문에 new 생성자이름(), new 클래스 이름() 모두 맞는 표현임
  • new MemberConstruct("user1", 15, 90)으로 생성자를 호출하면 인스턴스를 생성하고 즉시 해당 생성자를 호출하는데 인스턴스를 생성할 때 메모리게 값을 할당하고 생성자를 호출할 때 값을 할당하는 순서로 동작하게 됨
  • new 키워드를 사용해서 객체를 생성할 때 마지막에 괄호()도 포함해야 하는 이유가 바로 생성자 때문이며 객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함함

2) 생성자 장점

(1) 중복 호출 제거

  • 생성자가 없던 시절에는 생성 직후에 어떤 작업을 수행하기 위해 메서드를 직접 한번 더 호출해야 했음
  • 그러나 생성자 덕분에 객체를 생성하면서 동시에 생성 직후에 필요한 작업을 한번에 처리할 수 있게 되었음
//생성자 등장 전
MemberInit member = new MemberInit();
member.initMember("user1", 15, 90);

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

 

(2) 제약 - 생성자 호출 필수

  • 위에서 작성한 생성자 등장 전 코드에서 initMember(...)를 실수로 호출하지 않게되면 프로그램은 작동하지만 회원의 이름과 나이 성적 데이터가 없는 상태로 프로그램이 동작하게됨
  • 만약 이 값들을 필수로 반드시 입력해야 한다면 시스템에 큰 문제가 발생할 수 있으며 아무 정보가 없는 유령 회원의 시스템 내부에 등장하게 됨
  • 생성자의 진짜 장점은 객체를 생성할 때 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야한다는 점임
  • 예제로 작성한 MemberConstruct 클래스의 경우 생성자를 직접 정의했기 때문에 직접 정의한 생성자를 반드시 호출해야 함
  • 참고로 생성자를 메서드 오버로딩 처럼 여러개 정의할 수 있는데 지금과 같은 경우에는 하나만 호출하면 됨
  • 만약 직접 정의한 생성자를 호출하지 않으면 컴파일 오류가 발생하며 개발자는 객체를 생성할 때 직접 정의한 생성자를 필수로 호출해야 한다는 것을 바로 알 수 있어 실수를 방지하고 빠르게 문제를 수정할 수 있게 됨
  • 즉 생성자 덕분에 회원의 이름, 나이, 성적은 항상 필수로 입력하게 되며 아무 정보가 없는 유령 회원이 시스템 내부에 등장할 가능성을 원천 차단할 수 있게 됨
  • 생성자를 사용하면 필수값 입력을 보장할 수 있음

** 참고

  • 좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약이 있는 프로그램임

4. 기본 생성자

1) 기본 생성자

(1) 설명

  • 과거 예제를 보면 생성자를 만들지 않았는데 생성자를 호출했었음
  • new MemberInit() 이 부분은 분명히 매개변수가 없는 생성자가 필요할텐데 그냥 호출하여 객체를 생성할 수 있었음
MemberInit member1 = new MemberInit();

 

  • 매개변수가 없는 생성자를 기본 생성자라고 하는데 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고 작동하는 코드가 없는 기본 생성자를 자동으로 만들어줌
  • 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않음
  • 즉 MemberInit 클래스의 경우 생성자를 직접 만들지 않았으므로 자바가 자동으로 기본 생성자를 만들어준 것임

(2) MemberDefault

  • 생성자를 직접 정의하지않은 MemberDefault 클래스
package construct;

public class MemberDefault {
    String name;
}

 

(3) MemberDefaultMain

  • MemberDefault 클래스에는 생성자가 하나도 없기 때문에 자바는 기본 생성자를 만들어주며 우리눈에는 보이지 않음
  • 그래서 new MemberDefault()를 호출하여 객체를 생성할 수 있음
package construct;

public class MemberDefaultMain {
    public static void main(String[] args) {
        MemberDefault memberDefault = new MemberDefault();
    }
}

 

(4) MemberDefault - 기본 생성자

  • 예제와 같은 코드처럼 자바가 기본 생성자를 만들어 준다고 보면됨
  • 참고로 자바가 자동으로 생성해주는 기본 생성자는 클래스와 같은 접근 제어자를 가지는데 접근제어자에 대한 내용은 뒤에서 자세히 설명함
package construct;

public class MemberDefault {
    String name;
    
    public MemberDefault() {
    }
}

 

(5) MemberDefault - 기본 생성자 직접 정의

  • 이렇게 직접 기본 생성자를 정의할 수 있으며 MemberDefaultMain의 코드를 실행해보면 해당 출력문이 출력되는 것을 확인할 수 있음
package construct;

public class MemberDefault {
    String name;

    public MemberDefault() {
        System.out.println("생성자 호출");
    }
}

 

(6) 기본 생성자를 자동으로 만들어지는 이유

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

(7) 정리

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

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

1) 생성자 오버로딩

(1) MemberConstruct - 생성자 추가

  • 기존 Memberconstruct에 매개변수 개수가 다른 생성자를 하나 추가하여 생성자가 2개가 되었으며 새로 추가한 생성자는 grade는 받지 않지만 grade가 50점으로 고정됨
package construct;

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

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

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

 

(2) ConstructMain2

  • 생성자를 오버로딩한 덕분에 성정 입력이 꼭 필요한 경우에는 grade가 있는 생성자를 호출하면 되고 그렇지 않은 경우에는 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 member : members) {
            System.out.println("이름: " + member.name + " 나이: " + member.age + " 성적: " + member.grade);
        }
    }
}
/* 실행 결과
생성자 호출 name= user1, age= 15, grade= 90
이름: user1 나이: 15 성적: 90
이름: user2 나이: 16 성적: 50
*/

2) this()

(1) MemberConstruct - this() 사용

  • 위에서 생성한 두 생성자를 비교해보면 코드가 중복되는 부분이 있는데 이때 this()라는 기능을 사용하면 생성자 내부에서 자신의 생성자를 호출할 수 있음
  • this는 인스턴스 자신의 참조값을 가리키므로 자신의 생성자를 호출한다고 생각하면 됨
  • 첫번째 생성자 코드를 보면 this()를 활용하여 생성자 내부에서 두번째 생성자를 호출하도록 작성할 수 있으며 이 부분을 잘 활용하면 코드 중복을 제거할 수 있게됨
  • 코드의 동작은 위에서 작성한 코드와 똑같이 동작함
package construct;

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

    public MemberConstruct(String name, int age) {
        this(name, age, 50);
    }

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

 

(2) this() 규칙

  • this()는 생성자 코드의 첫줄에만 작성할 수 있으며 아래처럼 작성하게 되면 규칙 위반이 되어 컴파일 오류가 발생함
public MemberConstruct(String name, int age) {
    System.out.println("go");
    this(name, age, 50);
}

6. 문제와 풀이

1) Book과 생성자

(1) 요구사항

  • BookMain 코드가 작동하도록 Book 클래스를완성
  • 특히Book 클래스의 생성자 코드에 중복이 없도록 주의

(2) 문제

더보기
package construct.ex;

public class Book {
    String title;
    String author;
    int page;

    // TODO 코드를 완성하세요.
}
package construct.ex;

public class BookMain {
    public static void main(String[] args) {
        // 기본 생성자 사용
        Book book1 = new Book();
        book1.displayInfo();
		
        // title과 author만을 매개변수로 받는 생성자
        Book book2 = new Book("Hello Java", "Seo");
        book2.displayInfo();

        // 모든 필드를 매개변수로 받는 생성자
        Book book3 = new Book("JPA 프로그래밍", "kim", 700);
        book3.displayInfo();
    }
}

 

제목: , 저자: , 페이지: 0

제목: Hello Java, 저자: Seo, 페이지: 0

제목: JPA 프로그래밍, 저자: kim, 페이지: 700

(3) 정답

더보기
package construct.ex;

public class Book {
    String title;
    String author;
    int page;

    public void displayInfo() {
        System.out.println("제목: " + title + " , 저자: " + author + " , 페이지: " + page);
    }

    public Book() {
        this("", "", 0);
    }


    public Book(String title, String author) {
        this(title, author, 0);
    }

    public Book(String title, String author, int page) {
        this.title = title;
        this.author = author;
        this.page = page;
    }
}
package construct.ex;

public class BookMain {
    public static void main(String[] args) {
        // 기본 생성자 사용
        Book book1 = new Book();
        book1.displayInfo();

        Book book2 = new Book("Hello Java", "Seo");
        book2.displayInfo();

        Book book3 = new Book("JPA 프로그래밍", "kim", 700);
        book3.displayInfo();
    }
}

 

제목:  , 저자:  , 페이지: 0
제목: Hello Java , 저자: Seo , 페이지: 0
제목: JPA 프로그래밍 , 저자: kim , 페이지: 700