회원 객체를 생성하고 나면 name, age, grade 같은 변수에 초기값을 설정한다. 회원 객체를 제대로 사용하기 위해서는 객체를 생성하자마자 이런 초기값을 설정해야 할 것이다. 이 코드에는 회원의 초기값을 설정하는 부분이 계속 반복된다. 메서드를 사용해서 반복을 제거해본다.
initMember(...) 메서드를 사용해서 반복을 제거했다. 그런데 이 메서드는 대부분 MemberInit 객체의 멤버 변수를 사용한다. 앞서 객체 지향에 대해서 학습했다. 이런 경우 속성과 기능을 한 곳에 두는 것이 더 나은 방법이다. MemberInit이 자기 자신의 데이터를 변경하는 기능(메서드)을 제공하는 것이 좋다.
2. this
MemberInit - initMember() 추가
package construct;
public classMemberInit {
String name;
int age;
int grade;
voidinitMember(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
initMember(...)는 Member에 초기값 설정 기능을 제공하는 메서드이다. 다음과 같이 메서드를 호출하면 객체의 멤버 변수에 인자로 넘어온 값을 채운다.
member1.initMember("user1", 15, 90)
this
Member의 코드를 다시 보면, initMember(String name...)의 코드를 보면 메서드의 매개변수에 정의한 String name과 Member의 멤버 변수의 이름이 String name으로 둘다 똑같다. 나머지 변수 이름도 name, age, grade로 모두 같다.
멤버 변수와 메서드의 매개변수의 이름이 같은 경우 둘을 어떻게 구분해야 할까?
이 경우 멤버 변수보다 매개변수가 코드 블록의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가진다. 따라서 initMember(String name,...) 메서드 안에서 name이라고 적으면 매개변수에 접근하게 된다.
멤버 변수에 접근하려면 앞에 this.이라고 해주면 된다. 여기서 this는 인스턴스 자신의 참조값을 가리킨다.
진행 과정
Member 인스턴스
this.name = name; // 1. 오른쪽의 name은 매개변수에 접근this.name = "user"; // 2. name 매개변수의 값 사용
x001.name = "user"; // 3. this.은 인스턴스 자신의 참조값을 뜻함, 따라서 인스턴스의 멤버 변수에 접근
this 제거
만약 이 예제에서 this를 제거하면 어떻게 될까?
name = name
다음과 같이 수정하면 name은 둘 다 매개변수를 뜻하게 된다. 따라서 멤버변수의 값이 변경되지 않는다.
정리
매개변수의 이름과 멤버 변수의 이름이 같은 경우 this를 사용해서 둘을 명확하게 구분해야 한다.
this는 인스턴스 자신을 가리킨다.
this의 생략
this는 생략할 수 있다. 이 경우 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수다)를 먼저 찾고 없으면 그 다음으로 멤버 변수를 찾는다. 멤버 변수도 없으면 오류가 발생한다.
this.nameField를 보면 this를 생략할 수 있지만, 생략하지 않고 사용해도 된다. 이렇게 this를 사용하면 이 코드가 멤버 변수를 사용한다는 것을 눈으로 쉽게 확인할 수 있다. 하지만 최근에는 IDE가 발전하면서 IDE가 멤버 변수와 지역 변수를 색상으로 구분해준다. 이런 점 때문에 this는 이름이 중복되는 경우처럼 꼭 필요한 경우에만 사용해도 충분하다.
3. 생성자 - 도입
프로그래밍을 하다보면 객체를 생성하고 이후에 바로 초기값을 할당해야 하는 경우가 많다. 따라서 앞서 initMember(...)와 같은 메서드를 매번 만들어야 한다. 그래서 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공한다. 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행할 수 있다.
생성자는 앞서 살펴본 initMember(...)와 같이 메서드와 유사하지만 몇가지 다른 특징이 있다. 기존 코드를 유지하기 위해 MemberConstruct라는 새로운 클래스를 작성하겠다.
MemberConstruct
package construct;
public classMemberConstruct {
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;
}
생성자는 메서드와 비슷하지만 다음과 같은 차이가 있다.
생성자의 이름은 클래스 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다.
생성자는 반환 타입이 없다. 비워두어야 한다.
나머지는 메서드와 같다.
ConstructMain1
package construct;
public classConstructMain1 {
public static voidmain(String[] args) {
MemberConstruct member1 = newMemberConstruct("user1", 15, 90);
MemberConstruct member2 = newMemberConstruct("user2", 16, 80);
MemberConstruct[] members = {member1, member2};
for (MemberConstruct s : members) {
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
}
}
}
생성자는 인스턴스를 생성하고 나서 즉시 호출된다. 생성자를 호출하는 방법은 다음 코드와 같이 new 명령어 다음에 생성자 이름과 매개변수에 맞추어 인수를 전달하면 된다.
new 생성자이름(생성자에 맞는 인수 목록)
new 클래스이름(생성자에 맞는 인수 목록)
참고로 생성자이름이 클래스 이름이기 때문에 둘 다 맞는 표현이다.
newMemberConstruct("user1", 15, 90)
이렇게 하면 인스턴스를 생성하고 즉시 해당 생성자를 호출한다. 여기서는 Member 인스턴스를 생성하고 바로 MemberConstruct(String name, int age, int grade) 생성자를 호출한다.
참고로 new 키워드를 사용해서 객체를 생성할 때 마지막에 괄호 ()도 포함해야 하는 이유가 바로 생성자 때문이다. 객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함한다.
생성자 장점 - 중복 호출 제거
생성자가 없던 시절에는 생성 직후에 어떤 작업을 수행하기 위해 메서드를 직접 한번 더 호출해야 했다. 생성자 덕분에 객체를 생성하면서 동시에 생성 직후에 필요한 작업을 한번에 처리할 수 있게 되었다.
// 생성자 등장 전MemberInit member = newMemberInit();
member.initMember("user1", 15, 90);
// 생성자 등장 후MemberConstruct member = newMemberConstruct("user1", 15, 90);
제약 - 생성자 호출 필수
방금 코드에서 생성자 등장 전 코드를 보면, initMember(...)를 실수로 호출하지 않으면 어떻게 될까? 이 메서드를 실수로 호출하지 않아도 프로그램은 작동한다. 하지만 회원의 이름과 나이, 성적 데이터가 없는 상태로 프로그램이 동작하게 된다. 만약에 이 값들을 필수로 반드시 입력해야 한다면, 시스템에 큰 문제가 발생할 수 있다. 결국 아무 정보가 없는 유령 회원이 시스템 내부에 등장하게 된다.
생성자의 진짜 장점은 객체를 생성할 때 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다는 점이다. 참고로 생성자를 메서드 오버로딩처럼 여러개 정의할 수 있는데, 이 경우에는 하나만 호출하면 된다.
MemberConstruct 클래스의 경우 다음 생성자를 직접 정의했기 때문에 직접 정의한 생성자를 반드시 호출해야 한다.
MemberConstruct(String name, int age, int grade) {...}