생성자

계리·2024년 3월 2일
0

생성자가 필요한 이유

객체를 생성한 시점에 어떠한 작업을 하려면 생성자를 사용하면 된다.
예시를 든 코드로 알아보자.

package construct;

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


package construct;

public class MemberInitMain1 {

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

	}

}

실행 결과
이름: user1 나이: 15 성적: 90
이름: user2 나이: 16 성적: 80

객체를 생성 후 초기 값을 설정하는 것을 볼 수 있다. 그런데 객체를 여러 개 생성하면서 반복적으로 초기 값도 생성 해야 하는 것을 볼 수 있다.

메서드를 통해 중복되는 코드를 줄여보자.

package construct;

public class MemberInitMain2 {

	public static void main(String[] args) {
		MemberInit member1 = new MemberInit();
		memberInit(member1, "user1", 15, 90);
		
		MemberInit member2 = new MemberInit();
		memberInit(member2, "user2", 16, 80);
		
		MemberInit[] members = {member1, member2};
		
		for(MemberInit m : members) {
			System.out.println("이름: " + m.name + " 나이: " + m.age + " 성적: " + m.grade);
		}

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

}

memberInit() 메서드를 이용해서 중복된 코드를 줄였다. 그런데 memberInit() 메서드를 보면 MemberInit 객체의 멤버 변수를 사용하고 있다. 우리는 객체 지향에 대해서 학습 했었을 때 객체 안에서 속성(멤버 변수)과 기능(메서드)을 같이 정의 하는 것으로 배웠다. 배운 것을 토대로 코드를 다시 짜보자.


this

package construct;

public class MemberInit {
	String name;
	int age;
	int grade;
	
	//	추가
	void memberInit(String name, int age, int grade) {
		this.name = name;
		this.age = age;
		this.grade = grade;
	}
}


package construct;

public class MemberInitMain3 {

	public static void main(String[] args) {
		MemberInit member1 = new MemberInit();
		member1.memberInit("user1", 15, 90);
		
		MemberInit member2 = new MemberInit();
		member2.memberInit("user2", 16, 80);
		
		MemberInit[] members = {member1, member2};
		
		for(MemberInit m : members) {
			System.out.println("이름: " + m.name + " 나이: " + m.age + " 성적: " + m.grade);
		}

	}
}

객체의 멤버 변수와 메서드 내의 매개변수의 이름들을 보면 같은 것을 알 수가 있다. 그렇다면 어떤 것이 멤버 변수인지 지역 변수인지 구분을 어떻게 하는가??

  • 위와 같은 경우에 멤버 변수보다 매개변수가 메서드 코드 블록 안에 선언되어 있기 때문에 매개 변수가 우선순위를 가진다. 그래서 memberInit(String name ...) 메서드 내에서 name 변수를 선언하면 매개변수의 String name에 먼저 접근하게 된다.
  • 멤버 변수의 name에 접근하려면 this.을 사용하면 된다. 여기서 this는 인스턴스 자신의 참조 값을 가리킨다.

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

여기서 만약 this를 제거하게 되면 어떻게 될까?

name = name;

바로 위와 같이 변경하게 되면 매개 변수를 뜻하게 되므로 멤버 변수의 값이 변하지 않게 된다.

this 요약

  • 멤버 변수와 매개 변수 이름이 같은 경우 구분하기 위해 this를 사용해서 구분한다.
  • this는 인스턴스 자신의 참조 값을 가리킨다.

this 생략

  • this를 생략할 수도 있는데 이 경우에는 매개 변수를 먼저 우선순위로 찾고 난 다음에 멤버 변수를 찾아 접근한다.
  • 멤버 변수도 없게 되면 오류가 발생하게 된다.

this 생략 예시

package construct;

public class MemberThis {
	String nameField;
	
	void initMember(String nameParameter) {
		nameField = nameParameter;
	}
}

initMember() 메서드 안에서 nameField의 경우 this가 없어도 멤버 변수를 찾아 접근할 수 있다.

nameField 접근 순서

  • nameField는 먼저 initMember() 메서드 안에서 매개 변수에 있는지 찾아본다.
  • 매개 변수에 없으므로 멤버 변수에서 찾는다.

nameParameter 접근 순서

  • initMember() 메서드 안에서 매개 변수에 있으므로 매개 변수 있으므로 매개 변수를 사용 한다.

생성자 도입

자바 같은 객체 지향 프로그래밍을 하다보면 객체를 생성 후 바로 초기 값을 설정 해줘야 하는 경우가 많다. 그래서 앞서 memberInit()와 같은 메서드를 매번 만들어야 한다.

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

생성자를 이용해 객체를 생성하는 시점에 즉시 필요한 기능들을 수행할 수 있다.
기존 코드를 유지하기 위해 MemberConstruct 클래스를 새로 생성한다.

package construct;

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

바로 위 코드에서

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

이 부분이 생성자이다. 메서드와 크게 차이는 없어보이지만 다음과 같은 특징들이 있다.

  • 생성자의 이름은 클래스의 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다.
  • 생성자는 반환 타입이 없다. 바워둬야 한다.
  • 나머지는 메서드와 같다.
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 m : members) {
			System.out.println("이름: " + m.name + " 나이: " + m.age + " 성적: " + m.grade);
		}
	}

}
실행 결과
생성자 호출 name=user1,age=15,grade=90
생성자 호출 name=user2,age=16,grade=80
이름: user1 나이: 15 성적: 90
이름: user2 나이: 16 성적: 80

생성자 호출 방법

생성자는 인스턴스 생성 하고나서 즉시 호출 된다. 생성자 호출 방법은 다음과 같다.

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

new 명령어 다음에 생성자 이름과 매개 변수에 맞추어 매개 인수를 작성하면 된다. 참고로 생성자이름이 클래스이름과 같기 때문에 둘다 맞는 표현이다.

new MemberConstruct("user1", 15, 90)

이렇게 인스턴스 생성 후 즉시 생성자를 호출한다. 여기서는 member 인스턴스를 생성 후 MemberConstruct(String name, int age, int grade) 생성자를 호출한다.

참고로 new 키워드로 인스턴스 생성 후 () 괄호를 넣은 이유가 생성자 때문이다. 인스턴스를 생성하면서 동시에 생성자를 호출한다는 의미이다.


생성자 장점

중복 호출 제거

생성자를 사용하지 않았을 때는 객체 생성 후 어떠한 작업을 하기 위해서는 메서드를 매번 호출했었다. 그런데 생성자를 사용한 후 객체를 생성 하는 동시에 어떠한 작업을 한번에 수행할 수 있게 됐다.

// 생성자 사용 전
		MemberInit member1 = new MemberInit();
		member1.memberInit("user1", 15, 90);

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

제약으로 인해 생성자 호출 필수

생성자 사용 전에 메서드를 호출하지 않았다고 가정해보자. 그러면 어떻게 될까? 프로그램은 정상적으로 수행은 할 것이다. 그러나 이름 나이 성적에는 데이터가 없는 상태로 프로그램이 작동하게 될 것이다. 만약 필수로 해당 값들을 넣어야 하는 상황이면 시스템에서는 큰 문제가 발생하게 된다. 아무 정보가 없는 유령 회원이 시스템 내부에 등장하게 될 것이다.

생성자의 찐 장점은 해당 객체에 생성자를 정의 했다면 반드시 생성자를 호출해서 사용해야 한다는 점이다. 참고로 생성자도 오버로딩을 통해 메서드를 여러 개 정의할 수 있지만 위와 같은 경우는 하나만 정의해도 되는 상황이다.

만약 생성자를 호출하지 않는다면 컴파일 오류가 발생한다.

MemberConstruct member3 = new MemberConstruct();

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	The constructor MemberConstruct() is undefined

컴파일 오류는 IDE에서 즉시 확인할 수 있는 좋은 오류다. 이 경우 개발자는 객체 생성 시 생성자 호출을 해야하는 것을 확인할 수 있다.

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

생성자 덕분에 회원 정보를 필수로 입력하게 되고 그러면 아무 정보가 없는 유령 회원이 나타날 가능성을 원천 차단한다.

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

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


기본 생성자

가만 생각해 보면 생성자를 정의하지 않았는데 생성자를 호출한 적이 있다. 예시로 확인해보자.

package construct;

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


package construct;

public class MemberInitMain1 {

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

여기서 new MemberInit(); 객체 생성하는 것을 보면 매개 변수가 없는 생성자가 필요 할 것이다.

package construct;

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

// 생성자 필요
MemberInit() {
}

기본 생성자 특징

  • 매개 변수가 없는 생성자를 기본 생성자라고 한다.
  • 클래스에 기본 생성자가 하나도 없으면 자바 컴파일러는 매개 변수가 없고 작동하는 코드가 없는 기본 생성자를 자동으로 만들어준다.
  • 생성자가 하나라도 만들어져 있으면 자바는 기본 생성자를 만들지 않는다.

예시

package construct;

public class MemberDefault {
	String name;
	
    // 기본 생성자
	public MemberDefault() {
	}
}

참고로 자바가 생성해주는 기본 생성자는 클래스와 같은 접근 제어자를 가진다. 물론 아래와 같이 기본 생성자를 직접 정의해도 된다.

package construct;

public class MemberDefault {
	String name;
	
	MemberDefault() {
		System.out.println("기본 생성자 호출");
	}
}
실행 결과
기본 생성자 호출

자바에서는 기본 생성자를 왜 만들어줄까?

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


생성자 요약

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

생성자 오버로딩과 this()

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

package construct;

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



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

}
실행 결과
성자 호출 name=user1,age=15,grade=90
생성자 호출 name=user2,age=16,grade=50
이름: user1 나이: 15 성적: 90
이름: user2 나이: 16 성적: 50

기존 MemberConstruct 클래스에 생성자를 하나 추가했다.

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

새로 추가한 생성자에는 grade를 받지 않는다. 대신에 this.grade에는 50을 받도록 했다.

이렇게 생성자 오버로딩을 통해 grade를 필수로 받아야 하는 상황과 그렇지 않는 상황에는 grade 매개 변수가 없는 생성자를 사용하면 된다.


this()

그런데 두 생성자들을 보면 코드가 중복이 되는 부분이 있다.

this.name = name
this.age = age

이 부분이 중복되는데 이럴 경우 this()를 사용하면 된다. this() 생성자 내에서 자신의 생성자를 호출할 수 있다. 앞에서 언급했듯이 this는 자신의 인스턴스 참조 값을 가리킨다고 했다. 그래서 자신의 생성자를 호출한다고 생각하면 된다.

package construct;

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()는 생성자 내부에서 첫 줄에 작성되어야 한다.

예시

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

맨 첫줄에 작성하지 않을 경우 컴파일 오류가 발생한다.


참고

  • 김영한의 실전 자바 기본편
profile
gyery

0개의 댓글