1. 생성자
1-1. 생성자가 필요한 이유
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Constructor)를 이용하면 됩니다. 밑에서 간단한 코드로 설명드리겠습니다.
전체 코드
package construct;
public class MemberInit {
String name;
int age;
int 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 s : members) {
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" +
s.grade);
}
}
}
//출력 결과
이름:user1 나이:15 성적:90
이름:user2 나이:16 성적:80
회원 객체를 생성하고 나면 name, age, grade 같은 변수에 초기값을 설정합니다. 이 코드에서는 회원의 초기값을 설정하는 부분이 계속 반복됩니다. 따라서 메서드를 사용해서 반복을 제거하겠습니다.
전체 코드
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 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이 자신의 데이터를 변경하는 기능(메서드)을 제공하는 것이 더 좋습니다.
1-2. 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;
}
}
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 s : members){
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
}
}
}
//출력 결과
이름:user1 나이:15 성적:90
이름:user2 나이:16 성적:80
이렇게 this를 통해 인스턴스 자신의 참조값을 가리키게 됩니다.
정리
- 매개변수의 이름과 멤버 변수의 이름이 같은 경우 this를 사용해서 둘을 명확하게 구분해야 합니다.
- this는 인스턴스 자신을 가리킵니다.
1-3. this의 생략
매개변수(파라미터)의 이름과 멤버 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수입니다)를 먼저 찾고 없으면 그다음으로 멤버 변수를 찾습니다. 멤버 변수도 없으면 오류가 발생합니다.
전체 코드
package construct;
public class MemberThis {
String nameField;
void initMember(String nameParameter){
nameField = nameParameter;
}
}
이렇게 nameField 앞에 this가 없어도 멤버 변수에 접근합니다. nameField는 먼저 지역변수(매개변수)에서 같은 이름이 있는지 찾습니다. 이 경우 없으므로 멤버 변수에서 찾습니다.
정리
this.nameField로 써도 되고, 생략할 수 있습니다. 과거에는 this를 강제로 사용해서, 지역 변수(매개 변수)와 멤버 변수를 눈에 보이도록 구분지어서 사용하였습니다. 하지만 최근에 IDE가 발전하면서 멤버 변수와 지역 변수를 색상으로 구분해 줍니다.
이런 점 때문에 this는 앞서 설명한 것처럼 이름이 중복되는 부분 또는 꼭 필요한 경우에만 사용해도 충분하다고 생각합니다.
1-4. 생성자 도입
위에서는 메서드를 통해 기능을 분리하였습니다. 하지만 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공합니다. 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행할 수 있습니다. 앞서 살펴본 initMember(...)와 같이 메서드와 유사하지만 몇 가지 다른 특징이 있습니다.
밑의 코드를 통해 살펴보겠습니다.
전체코드
package construct;
public class MemberConstructor {
String name;
int age;
int grade;
//생성자
MemberConstructor(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 s : members) {
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
}
}
}
//출력 결과
생성자 호출 name=user1,age=15,grade=90
생성자 호출 name=user2,age=16,grade=80
이름:user1 나이:15 성적:90
이름:user2 나이:16 성적:80
생성자는 인스턴스를 생성하고 나서 즉시 호출됩니다. 생성자는 호출하는 방법은 다음 코드와 같이 new 명령어 다음에 생성자 이름과 매개변수에 맞추어 인수를 전달하면 됩니다.
참고로 new 키워드를 사용해서 객체를 생성할 때 마지막에 괄호 () 도 포함해야 하는 이유가 바로 생성자 때문입니다. 객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함합니다.
1-4-1. 생성자 장점
1. 중복 호출 제거
생성자가 없던 시절에는 생성 직후 작업을 수행하기 위해 메서드를 직접 한 번 더 호출했습니다. 생성자 덕분에 객체를 생성하면서 동시에 생성 직후 필요한 작업을 한 번에 처리할 수 있게 되었습니다.
//생성자 등장 전
MemberInit member = new MemberInit();
member.initMember("user1", 15, 90);
//생성자 등장 후
MemberConstruct member = new MemberConstruct("user1", 15, 90);
2. 생성자 호출 필수
생성자의 진짜 장점은 객체를 생성할 때 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다는 점입니다. 참고로 생성자를 메서드 오버로딩처럼 여러 개 정의할 수 있는데, 이 경우에는 하나만 호출하면 됩니다.
*오버로딩은 밑에서 설명드리겠습니다.
MemberConstuct 클래스의 경우 다음 생성자를 직접 정의했기 때문에 직접 정의한 생성자를 반드시 호출해야 합니다.
MemberConstruct(String name, int age, int grade) { ... }
다음과 같이 직접 정의한 생성자를 호출하지 않으면 컴파일 오류가 생깁니다.
MemberConstruct memeber3 = new MemberConstruct();
member3.name = "user1";
컴파일 오류는 IDE에서 즉시 확인할 수 있는 오류입니다. 이 경우 개발자는 객체를 생성할 때, 직접 정의한 생성자를 필수로 호출해야 한다는 것을 바로 알 수 있습니다. 그래서 필요한 생성자를 찾아서 다음과 같이 호출할 것입니다.
MemberConstruct member = new MemberConstruct("user1", 15, 90);
생성자 덕분에 회원의 이름, 나이, 성적은 항상 필수로 입력하게 됩니다. 따라서 아무 정보가 없는 유령 회원이 시스템 내부에 등장할 가능성을 원천 차단합니다.!
1-5. 기본 생성자
생각해 보면 생성자를 만들지 않았는데, 생성자를 호출한 적이 있습니다. 다음 코드들을 다시 확인해 보겠습니다.
전체 코드
package construct;
public class MemberInit {
String name;
int age;
int grade;
}
public class MethodInitMain1 {
public static void main(String[] args) {
MemberInit member1 = new MemberInit();
...
}
}
여기서 new MemberInit() 이 부분은 분명히 매개변수가 없는 다음과 같은 생성자가 필요할 것입니다.
전체 코드
package construct;
public class MemberInit {
String name;
int age;
int grade;
MemberInit(){ //생성자 필요
}
}
1-5-1. 기본 생성자 내용
- 매개변수가 없는 생성자를 기본 생성자라 합니다.
- 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어 줍니다.
- 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않습니다.
코드를 통해 상세히 알아보겠습니다.
전체 코드
package construct;
public class MemberDefault {
String name;
}
package construct;
public class MemberDefaultMain {
public static void main(String[] args) {
MemberDefault memberDefault = new MemberDefault();
}
}
MemberDefault 클래스에는 생성자가 하나도 없으므로 자바는 자동으로 다음과 같은 기본 생성자를 만들어줍니다.
package construct;
public class MemberDefault {
String name;
//기본 생성자
public MemberDefault(){
}
}
물론 다음과 같이 기본 생성자를 직접 정의해도 됩니다.
package construct;
public class MemberDefault {
String name;
public MemberDefault(){
System.out.println("생성자 호출");
}
}
1-5-2. 기본 생성자가 자동으로 만들어지는 이유
자바에서 기본생성자를 만들어주지 않는다면 생성자 기능이 필요하지 않은 경우에도 모든 클래스에 개발자가 직접 기본생성자를 정의해야 합니다. 생성자 기능을 사용하지 않는 경우도 많기 때문에 이런 편의 기능을 제공합니다.
정리
- 생성자는 반드시 호출되어야 한다.
- 생성자가 없으면 기본 생성자가 제공된다.
- 생성자가 하나라도 있으면 기본 생성자가 제공되지 않는다. 이 경우 개발자가 정의한 생성자를 직접 호출해야 한다.
1-6. 오버로딩과 this()
생성자도 메서드 오버로딩처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있습니다.
1-6-1. 오버로딩
전체 코드
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;
}
}
기존 MemberConstruct에 생성자를 하나 추가해서 생성자가 2개가 되었습니다.
MemberConstruct(String name, int age)
MemberConstruct(String name, int age, int grade)
즉 오버로딩(같은 이름의 메서드, 생성자를 파라미터만 다르게 해서 여러 개 정의하는 말)을 한 덕분에 성적 입력이 꼭 필요한 경우에는 grade 가 있는 생성자를 호출하면 되고, 그렇지 않은 경우에는 grade가 없는 생성자를 호출하면 됩니다.
1-6-2. this()
this()는 밑의 코드를 통해 설명드리겠습니다.
전체코드
public MemberConstruct(String name, int age) {
this.name = name;
this.age = age;
this.grade = 50;
}
public MemberConstruct(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
두 생성자를 비교해 보면 코드가 중복되는 부분이 있습니다.
this.name = name;
this.age = age;
바로 이 부분입니다.
이때 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) {
this(name, age, 50); //변경
}
이 코드는 첫 번째 생성자 내부에서 두 번째 생성자를 호출합니다. this()를 사용하면 생성자 내부에서 다른 생성자를 호출할 수 있습니다. 이 부분을 잘 활용하면 지금과 같이 중복을 제거할 수 있습니다. 물론 실행 결과는 기존과 같습니다.
1-6-3. this() 규칙
this()는 생성자 코드의 첫 줄에만 사용할 수 있습니다.
public MemberConstruct(String name, int age) {
System.out.println("go");
this(name, age, 50);
}
이렇게 this()가 생성자 코드의 첫줄에 사용되지 않는다면 컴파일 오류가 발생하게 됩니다.
'Language > Java' 카테고리의 다른 글
[Java] 접근 제어자 (0) | 2023.12.26 |
---|---|
[Java] 패키지 (2) | 2023.12.18 |
[Java] 절차지향, 객체 지향 프로그래밍 (0) | 2023.12.17 |
[Java] 변수와 초기화 (0) | 2023.12.17 |
[Java] 인자, 매개변수 (1) | 2023.12.14 |