JAVA - 생성자 + 연습 문제
1. 생성자 - 필요한 이유
- 객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Constructor)를 이용하면 된다.
생성자를 알아보기 전 왜 생성자가 필요한 지 코드로 알아보자.
public class MemberInit {
String name;
int age;
int grade;
}
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 member : members) {
System.out.println("이름 : " + member.name
+ ", 나이 " + member.age + ", 점수 : " + member.grade );
}
}
}
회원 객체를 생성 후 name, age, grade 같은 변수에 초기값을 설정한다. 아마도 회원 객체를 제대로 사용하기 위해서는 객체를 생성하자 마자 이런 초기값을 설정해야 할 것이다. 이 코드에서 회원의 초기값을 설정하는 부분이 계속 반복된다. 메서드를 통해 반복을 제거해보자.
public class MemberInitMain2 {
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;
}
}
반복이 조금 줄어들었지만, 앞에서 배운 것 처럼 속성과 기능은 한 곳에 두는 게 나은 방법이기에 MemberInit이 자기 자신의 데이터를 변경하는 기능을 제공하는 것이 좋다.
2. 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 MemberInitMain3 {
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 );
}
}
}
initMember(...)는 Member에 초기값 설정 기능을 제공하는 메서드이다.
다음과 같이 메서드를 호출하면 객체의 멤버 변수에 인자로 넘어온 값을 채운다.
member1.initMember("user1", 15, 90)
this
Member의 코드를 다시 보자.
initMember(String name, ...)의 코드를 보면 메서드의 매개변수에 정의한 String name과 Member의 멤버 변수의 이름이 String name으로 둘 다 똑같다. 나머지 변수 이름도 서로서로 같다.
멤버 변수와 메서드의 매개변수가 일치하면 둘을 어떻게 구분할까??
- 이 경우 멤버 변수보다 매개변수가 코드 블럭 안쪽에 있기 때문에 매개변수가 우선순위를 가진다. 따라서 initMember(String name, ...)메서드 안에서 name이라고 적으면 매개변수에 접근하게 된다.
- 멤버 변수에 접근하려면 앞에 this. 라고 해주면 된다. 여기서 this 는 인스턴스 자신의 참조값을 가리킨다.
this의 생략
this는 생략할 수 있다. 이 경우 변수를 찾을 때 가까운 지역변수(매개변수)를 먼저 찾고 없다면 그 다음으로 멤버 변수를 찾는다. 멤버 변수도 없으면 오류가 발생한다.
public class MemberThis {
String nameField;
void initMember(String nameParameter) {
nameField = nameParameter;
}
}
위 코드는 필드 이름과 매개변수의 이름이 다르다.
3. 생성자 - 도입
public class MemberConstruct {
String name;
int age;
int grade;
//생성자
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name : " + name + ", 나이 : "
+ age + ", 점수 : " + 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 member : members) {
System.out.println("이름 : " + member.name + ", 나이 : " + member.age + ", 점수 : " + member.grade );
}
}
}
생성자 호출
생성자는 인스턴스를 생성하고 나서 즉시 호출된다. 생성자를 호출하는 방법은 다음 ㅗㅋ드와 같이 new 명령어 다음에 생성자 이름과 매개변수를 맞추어 인수를 전달하면 된다.
new 생성자이름(생성자에 맞는 인수 목록)
new 클래스이름(생성자에 맞는 인수 목록)
MemberConstruct member1 = new MemberConstruct("user1", 15, 90);
이렇게 하면 인스턴스 생성하고 즉시 해당 생성자를 호출한다.
참고로 new 키워드를 사용해서 객체를 생성할 때 마지막에 괄호()도 포함되어 있는 이유가 바로 생성자 때문이다. 객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함한다.
생성자 장점
중복 호출 제거
//생성자 등장 전
MemberInit member = new MemberInit();
member.initMember("user1", 15, 90)
//생성자 등장 후
MemberConstruct member2 = new MemberConstruct("user2", 16, 80);
제약 - 생성자 호출 필요
방금 코드에서 생성자 등장 전 코드를 보자. 이 경우 'initMember(...)' 를 실수로 호출하지 않으면 어떻게 될까? 이 메서드를 실수로 호출하지 않아도 프로그램은 작동한다. 하지만 회원의 이름과 나이, 성적 데이터가 없는 상태로 프로그램이 동작하게 된다. 만약에 이 값들을 필수로 반드시 입력해야 한다면, 시스템에 큰 문제가 발생할 수 있다. 결국 아무 정보가 없는 유령 회원이 시스템 내부에 등장하게 된다.
하지만 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다는 점이다.
직접 정의한 생성자를 호출하지 않으면 컴파일 오류가 발생한다.
참고로 좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.
4. 기본 생성자
우리는 알고보면 이때까지 생성자를 호출한 적이 있다.
public class MemberInit {
String name;
int age;
int grade;
}
public class MemberInitMain1 {
public static void main(String[] args) {
//기본 생성자가 생기는 부분
MemberInit member1 = new MemberInit();
...
}
}
어? 그럼 우리가 생성자를 만들어 주지 않았지만 어떻게 생길까??
public class MemberConstruct {
String name;
int age;
int grade;
MemberInit(){//없으면 자바가 알아서 만들어 준다.
}
}
- 매개변수가 없는 생성자를 기본 생성자라 한다.
- 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어준다.
- 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않는다.
5. 생성자 - 오버로딩과 this()
생성자도 메서드 오버로딩처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
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 + ", 점수 : " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
새로 추가된 생성자는 grade를 받지 않는다. 대신에 grade는 50이 된다.
this()
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 + ", 점수 : " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
두 코드를 보면
- this.name
- this.age
가 중복이 되는 것을 볼 수 있다.
이때 this()라는 기능을 사용하면 생성자 내부에서 자신의 생정자를 호출할 수 있다. 참고로 this는 자신의 인스턴스의 참조값을 가리킨다. 그래서 자신의 생성자를 호출한다고 생각하면 된다.
이 것을 이용하여 위 코드를 수정해보자.
MemberConstruct(String name, int age) {
this(name, age, 50);
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name : " + name + ", 나이 : " + age + ", 점수 : " + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
this() 규칙
- this()는 생성자 코드의 첫 줄에만 작성할 수 있다.
생성자는 객체 생성 직후 객체를 초기화 하기 위한 특별한 메서드로 생각하면 될 거 같다.
연습문제1. Book과 생성자
BookMain 코드가 작동하도록 Book 클래스 완성하기.
특히 Book 코드의 생성자가 중복이 없도록 만들기.
public class Book {
String title;
String author;
int page;
Book() {
this("", "", 0);
}
Book(String title, String author) {
this(title, author, 0);
}
Book(String title, String author, int page) {
this.title = title;
this.author = author;
this.page = page;
}
void displayInfo() {
System.out.println("제목 : " + title
+ ", 저자 : " + author + ", 페이지 : " + page);
}
}
생성자가 있는 상태에서 기본 생성자를 만들 때 중복이 없이 어떻게 만들까 고민했었는데, 기본 생성자는 받는 매개변수가 없으므로
("", "", 0) 과 같이 작성해주면 되었다.
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();
}
}