처음부터 차근차근

[Java] 생성자, this 본문

Language/Java

[Java] 생성자, this

HangJu_95 2023. 12. 26. 00:10
728x90

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

생성자란?

생성자(Constructor)는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이다.
따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 하는 작업을 위해서도 사용된다.

 

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

생성자가 필요한 이유

간단한 MemberInit Class를 만들고, MemberInit 객체를 사용하는 코드를 작성해보겠습니다.

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

        // shift + F6 해당하는 동일한 문자 변환
        MemberInit member2 = new MemberInit();
        member2.name = "user";
        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 같은 변수에 초기값을 각각 설정해주었습니다.

그러나 여기서 보면 코드가 반복되는 것이 보입니다.

        member1.name = "user";
        member1.age = 15;
        member1.grade = 90;

회원 객체를 제대로 사용하기 위해서는 객체를 생성하자 마자 이런 초기값을 설정해줘야 합니다. 메서드를 통해 반복되는 작업을 제거해보겠습니다.

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 객체의 멤버 변수를 사용합니다.

앞서 우리는 객체 지향에 대해서 학습하였으며, 이런 경우 속성과 기능을 한곳에 두는 것이 더 나은 방법입니다.

한번 수정해보겠습니다.

this를 사용하여 객체 멤버 변수 초기화

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

initMember는 Member에 초기값 설정 기능을 제공하는 메서드입니다.

다음과 같이 호출하면 객체의 멤버 변수에 인자로 넘어온 값을 채웁니다.

this

Member의 코드를 다시 보겠습니다.

InitMember(String name...)의 코드를 보면 메서드의 매개변수에 정의한 String nameMember의 멤버 변수의 이름이 String name으로 둘 다 동일합니다. 나머지 변수 이름도 동일한 것이 확인됩니다.

 

이 경우에는 둘을 어떻게 구분해야 할까요??

  • 멤버 변수보다 매개변수가 코드 블럭의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가집니다. 따라서 InitMember(String name,...) 메서드 안에서 name이라고 적으면 매개변수에 접근하게 됩니다.
  • 멤버 변수에 접근하려면 앞에 this.이라고 해주면 됩니다. 여기서 this는 인스턴스 자신의 참조값을 가리킵니다.

this의 참조 과정

만약 this를 제거한다면??

name = name;

이렇게 되는 경우 name은 둘 다 매개변수를 뜻하게 되므로, 멤버 변수의 값이 변경되지 않습니다.

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

this의 생략

this는 생략할 수 있습니다.

이 경우 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수입니다.)를 먼저 찾고 없으면 그 다음으로 멤버 변수를 찾습니다. 멤버 변수도 없다면 오류가 발생합니다.

public class MemberThis {
    String nameField;

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

이 경우는 멤버 변수 이름과 매게변수 이름이 서로 다릅니다.

  • 예를 들어서 nameField는 앞에 this가 없어도 멤버 변수에 접근한다.
  • nameField는 먼저 지역변수(매개변수)에서 같은 이름이 있는지 찾는다. 이 경우 없으므로 맴버 변수에서 찾는다.
  • nameParameter는 먼저 지역변수(매개변수)에서 같은 이름이 있는지 찾는다. 이 경우 매개변수가 있으므로 매개 변수를 사용한다.

최근에는 IDE가 발전하면서 IDE가 멤버 변수와 지역 변수를 색상으로 구분해줍니다.

따라서 멤버 변수와 매개변수가 다르다면 this를 사용하지 않아도 되지만, 자신이 속한 그룹의 코딩 스타일에 따라 맞추면 될 것 같습니다.

생성자를 도입해보자

대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공합니다.

따라서 앞서 만들었던 initMember(...) 메서드를 만들 필요가 없습니다.

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

    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;
    }
}
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);
        }
    }
}

다음 부분이 생성자(Constructor)입니다

Javascript에서나 Typescript에서는 Constructor로 정의합니다.

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;
}

생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'입니다.
따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 하는 작업을 위해서도 사용됩니다.

 

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

  • 생성자의 이름은 클래스 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다.
  • 생성자는 반환 타입이 없다. 비워두어야 한다.
  • 나머지는 메서드와 같다.

생성자 호출

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

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

참고로 생성자이름이 클래스이름이기 때문에 둘 다 맞는 표현입니다.

new MemberConstruct("user1", 15, 90)

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

참고로 new 키워드를 사용해서 객체를 생성할 때 마지막에 () 도 포함해야 하는 이유가 바로 생성자 때문입니다.

객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함합니다.

생성자의 장점

1. 중복 호출 제거

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

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

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

 

2. 생성자 호출 필수

생성자 등장 전 코드를 보면, initMember()를 실수로 호출하지 않으면 회원의 이름과 나이, 성적 데이터가 없는 상태로 프로그램이 동작합니다.

만약 이 값들을 필수로 반드시 입력해야 한다면, 시스템에 큰 문제가 발생할 수 있습니다.

생성자의 진짜 장점은 객체를 생성할 때 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다는 점입니다.

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

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

MemberConstruct member3 = new MemberConstruct(); //컴파일 오류 발생
member3.name = "user1";

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

생성자를 사용하면 필수값 입력을 보장할 수 있다.

기본 생성자

  • 매개변수가 없는 생성자를 기본 생성자라 합니다. (컴파일러가 제공하는)
  • 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어줍니다.
  • 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않습니다.
package construct;

public class MemberDefault {
    String name;
}

이것은 제가 직접 생성한 MemberDefault 클래스입니다. 

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

MemberDefalut 클래스를 사용하는 코드를 만들어 실행해보면

out 컴파일러 파일에서 확인해보기

자바가 자동으로 기본 생성자를 만들어주는 것을 확인할 수 있습니다.

참고로 자바가 자동으로 생성해주는 기본 생성자는 클래스와 같은 public 접근 제어자를 가집니다.

 

자동으로 만들어지는 이유

모든 클래스에 개발자가 직접 기본 생성자를 정의해야되기 때문에 귀찮다.

생성자 - Overloading과 this()

생성자도 메서드 Overloading과 마찬가지로 매개변수만 다르게 해서 여러 생성자를 제공할 수 있습니다.

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

    MemberConstruct(String name, int age) {
        this.name = name;
        this.age = age;
        this.grade = 50;
    }
    
    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;
    }
}

새로 추가한 생성자는 grade를 받지 않는 대신 50점으로 고정됩니다.

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가 없는 생성자를 호출하면 됩니다.

여기서도 리펙토링이 가능한데, 두개의 생성자를 비교해보면 코드가 중복되는 부분이 있습니다.

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

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

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

    MemberConstruct(String name, int age) {
        this(name, age, 50); // 자기 자신의 생성자를 호출
    }
    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;
    }
}

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

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

this()를 사용하면 생성자 내부에서 다른 생성자를 호출할 수 있습니다.

this() 규칙

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

다음과 같은 경우는 컴파일 오류가 발생합니다.

public MemberConstruct(String name, int age) {
    System.out.println("go");
    this(name, age, 50);
}

this 정리

  • this : 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다. 모든 인스턴스메서드에 지역변수를 숨겨진 채로 존재한다.
  • this(), this(매개변수) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

참조

 

김영한의 실전 자바 - 기본편 강의 - 인프런

실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다

www.inflearn.com

자바의 정석_기초편

'Language > Java' 카테고리의 다른 글

[Java] 제어자, 접근 제어자  (0) 2023.12.26
[Java] Package  (1) 2023.12.26
[Java] 객체 지향 프로그래밍  (1) 2023.12.24
[Java] 변수와 초기화, null, null  (0) 2023.12.21
[Java] 기본형과 참조형  (1) 2023.12.21