도도한 개발자
[Java] #08. 참조 자료형 본문
자바의 타입은 기본 자료형과 참조 자료형이 있다. 기본 자료형(byte, short, int, long, char, float, double, boolean) 8개를 제외한 나머지 타입은 모두 참조 자료형이다. 쉽게 구분하면, 기본 자료형과 참조 자료형의 가장 큰 차이는 new를 사용해서 객체를 생성하는지 여부로 알 수 있다. 단, String의 경우 참조 자료형이지만 new 없이도 객체를 생성할 수 있다.
참조 자료형은 new를 사용하여 객체를 사용하는데, new 뒤에 나오는 것이 바로 생성자이다. 클래스를 만들면 보통 인스턴스 변수와 클래스 변수를 만들고, 생성자와 메소드를 만들어야 한다. 그럼 생성자와 메소드에 대해 알아보자.
* 기본 생성자
자바는 생성자를 만들지 않아도 자동으로 기본 생성자가 만들어진다.
public class ReferenceString {
public ReferenceString() {} // @1
public ReferenceString(String arg) {} // @2
public static void main(String[] args) {
ReferenceString reference = new ReferenceString();
}
}
main() 메소드를 보면 ReferenceString 클래스의 인스턴스인 reference를 만든 것을 볼 수 있다. 여기서 등호 우측에 new 옆에 있는 ReferenceString()이 바로 생성자이다. 기본 생성자는 다른 생성자가 없을 경우 컴파일할 때 자동으로 만들어진다. 그러나 @2와 같이 생성자를 만들었다면 @1처럼 기본 생성자를 명시적으로 적어줘야 한다. 생성자는 메소드와 비슷하게 생겼지만 리턴 타입이 없고 클래스 이름으로 되어 있다는 점에서 메소드와 다르다.
** 자바에서 생성자는 왜 필요할까?
자바의 생성자는 자바 클래스의 객체(또는 인스턴스)를 생성하기 위해 존재한다. 위의 reference가 객체이다. 생성자에 리턴 타입이 없는 이유는 생성자의 리턴 타입은 클래스의 객체이기 때문이며 클래스 이름과 동일한 이유는 그렇게 함으로써 컴파일러가 그것이 생성자라고 인식할 수 있기 때문이다.
** 생성자는 몇 개까지 만들 수 있을까?
자바는 클래스의 객체를 보다 간편하게 만들기 위해 여러 가지 매개 변수를 갖는 여러 생성자를 가질 수 있다. 자바 패턴 중 DTO라는 것이 있는데 어떤 속성을 갖는 클래스를 만들고, 그 속성들을 쉽게 전달하기 위해서 만든다. Data Transfer Object의 약자이며 비슷한 클래스로 VO라는 것도 있다. VO는 Value Object의 약자로 DTO와 형태는 동일하지만 VO는 데이터를 담아 두기 위한 목적으로 사용되며, DTO는 데이터를 다른 서버로 전달하기 위한 것이 주 목적이다.
한 사람의 개인정보를 담는 DTO 클래스가 있다고 생각해보자.
public class MemberDTO {
public String name;
public String phone;
public String email;
public MemberDTO() {
// 아무 정보 모를 때
}
public MemberDTO(String name) {
// 이름만 알 때
this.name = name;
}
public MemberDTO(String name, String phone) {
// 이름과 전화번호만 알 때
this.name = name;
this.phone = phone;
}
public MemberDTO(String name, String phone, String email) {
// 모든 정보를 알고 있을 때
this.name = name;
this.phone = phone;
this.email = email;
}
}
** DTO의 장점은 무엇일까?
자바의 메소드를 선언할 때 리턴 타입은 한가지만 선언할 수 있다. String변수가 여러개인 복합적인 데이터를 리턴하려면 String[]과 같은 배열을 리턴하면 되겠지만 int 타입이 포함된다면 애매해진다. 그래서 DTO를 만들면, 메소드의 리턴 차입에 MemberDTO로 선언하고 그 객체를 리턴해주면 된다. DTO 패턴은 생성자를 구현할 때 유용하다. 위의 예제에서 클래스의 객체를 생성할 때 어떤 이의 아무 정보도 모를 때도 있고, 이름만 알 때도 있고 혹은 이름과 전화번호만 알 때가 있는 등 각각의 상황에 따른 생성자를 DTO에 추가하여 구현할 수 있기 때문이다.
그런데 매개 변수가 없는 생성자를 제외하고 모두 this라는 예약어가 사용된 것을 볼 수 있다. 이는 객체의 변수와 매개 변수의 이름이 동일할 때, 인스턴스의 변수를 구분하기 위해 사용한다.
public class ReferenceConstructor {
public static void main(String[] args) {
ReferenceConstructor reference = new ReferenceConstructor();
reference.makeMemberObject();
}
public void makeMemberObject() {
MemberDTO dto1 = new MemberDTO();
MemberDTO dto2 = new MemberDTO("Kiara");
MemberDTO dto3 = new MemberDTO("Kiara", "01012345678");
MemberDTO dto4 = new MemberDTO("Kiara", "01012345678", "abc@naver.com");
}
}
위에서 만들 네 개의 생성자를 사용하여 MemberDTO의 객체를 생성해보았다. 모두 MemberDTO의 객체들이지만 한편으론 이 네 가지 생성자로 만들 객체들은 서로 다른 속성값을 갖고 있다.
* 메소드 Overloading
앞에서 클래스의 생성자는 매개 변수들을 서로 다르게 하여 선언할 수 있었다. 그렇다면 메소드는 어떨까?
public class ReferenceOverloading {
public static void main(String[] args) {
ReferenceOverloading reference = new ReferenceOverloading();
}
public void print(int data) {
}
public void print(String data) {
}
public void print(int intData, String stringData) {
}
public void print(String stringData, int intData) {
}
}
여기에 있는 메소드들은 모두 이름이 동일하다. 그러나 각 메소드의 매개 변수의 종류와 개수, 순서가 다르다. 이와 같이 개수가 같아도 타입의 순서가 다르면 다른 메소드처럼 인식된다. 즉, 메소드의 이름을 같도록 하고 매개 변수만을 다르게 하는 것을 바로 오버로딩(Overloading)이라고 한다.
* 메소드에서 값 넘겨주기 return
자바에서 메소드가 종료되는 조건은 다음과 같다.
- 메소드의 모든 문장이 실행되었을 때
- return 문장에 도달했을 때
- 예외가 발생(throw)했을 때
자바에선 모든 타입을 한 개만 리턴 타입으로 넘겨줄 수 있다. 모든 기본 자료형과 참조 자료형 중 하나를 리턴할 수 있는데 그러면 int만 리텅, int의 배열을 리턴, String을 리턴하는 예제를 보자.
public class ReferenceReturn {
public static void main(String[] args) {
ReferenceReturn reference = new ReferenceReturn();
System.out.println(reference.intReturn());
System.out.println(reference.intArrayReturn());
System.out.println(reference.stringReturn());
}
public int intReturn() {
int returnInt = 0;
return returnInt;
}
public int[] intArrayReturn() {
int returnArray[] = new int[10];
return returnArray;
}
public String stringReturn() {
String returnString = "Hello";
return returnString;
}
}
이렇게 메소드 이름 앞에 변수의 타입을 지정해주고, 메소드 내에서는 그 타입을 생성하고 가공한 후, return이라는 예약어를 사용하여 리턴해주면 요청한 메소드로 그 값이 전달된다.
* static 메소드와 일반 메소드
자신의 클래스나 다른 클래스에 있는 메소드를 호출하려면 반드시 객체를 생성해야 한다. 그러나 지금까지 익숙하게 써온 System.out.println() 메소드는 왜 객체를 생성하지 않았을까? 그 이유는 static이라는 예약어 때문에 생성하지 않아도 됐기 때문이다.
public class ReferenceStatic {
public static void main(String[] args) {
ReferenceStatic.staticMethod(); // 객체 생성 없이 클래스 변수가 직접 호출
}
public static void staticMethod() {
System.out.println("This is a staticMethod");
}
}
이 static 메소드는 클래스 변수만 사용할 수 있다. 만약 어떤 변수 a를 static으로 선언하게 되면 이 변수는 더이상 인스턴스 변수가 아닌 클래스 변수가 된다. 클래스 변수가 되면, 모든 객체에서 하나의 값을 바라보기 때문에 예상치도 못하는 상황이 발생하게 된다.
* Pass by value, Pass by reference
public class ReferencePass {
public static void main(String[] args) {
ReferencePass reference = new ReferencePass();
// reference.callPassByValue();
reference.callPassByReference();
}
public void callPassByValue() {
int a = 10;
String b = "b";
System.out.println("before passByValue");
System.out.println("a = " + a);
System.out.println("b = " + b);
passByValue(a, b); // @1
System.out.println("after passByValue");
System.out.println("a = " + a);
System.out.println("b = " + b);
}
public void passByValue(int a, String b) {
a = 20;
b = "z";
System.out.println("in passByValue");
System.out.println("a = " + a);
System.out.println("b = " + b);
}
public void callPassByReference() {
MemberDTO member = new MemberDTO("Dohee");
System.out.println("after passByReference");
System.out.println("member.name = " + member.name);
passByReference(member); // @2
System.out.println("before passByReference");
System.out.println("member.name = " + member.name);
}
public void passByReference(MemberDTO member) {
member.name = "kiara";
System.out.println("in passByReference");
System.out.println("member.name = " + member.name);
}
}
passByValue() 메소드를 보면, 매개 변수로 받은 값들을 다른 값으로 변경하고 출력한 것을 볼 수 있다. "Pass by value"라는 말은 "값을 전달한다"는 뜻으로 프로그래밍 언어에서의 Pass by value는 메소드의 매개 변수로 넘길 때에는 원래 값은 놔두고 전달되는 값이 진짜인 것처럼 보이게 한다. 따라서 매개 변수를 받은 메소드에서 그 값을 어떻게 변형하던 원래의 값은 변하지 않는다. 위 코드를 컴파일 하고 실행해보면 @2를 실행했을에도 int인 a나 String인 b의 기존 값은 변경되지 않았다.
한편, passByReference() 메소드를 보면 호출한 메소드의 데이터에도 영향이 있다. 만약 매개 변수로 받은 참조 자료형 안에 있는 객체를 변경하면 호출한 참조 자료형 안에 있는 객체는 호출된 메소드에서 변경한 대로 데이터가 바뀐다.
* 실습
Student라는 클래스는 만들고 name을 받는 Student 클래스의 생성자와 name, address, phone, email을 받는 Student 클래스의 생성자를 만들자. 학생의 정보를 String으로 리턴하는 toString() 메소드를 만들자. 학생관리하기 위해 main() 메소드가 있는 ManageStudent 클래스를 만들자. addStudent() 메소드를 호출하고 그 결과를 student 객체로 받지. student 객체의 내용을 출력할 메소드를 만들고 실행하라.
public class Student {
String name, address, phone, email;
public Student(String name) {
this.name = name;
}
public Student(String name, String address, String phone, String email) {
this.name = name;
this.address = address;
this.phone = phone;
this.email = email;
}
public String toString() {
return name + " " + address + " " + phone + " " + email;
}
}
public class ManageStudent {
public static void main(String[] args) {
Student[] student = null;
ManageStudent manage = new ManageStudent();
student = manage.addStudent();
manage.printStudents(student);
}
public Student[] addStudent() {
Student[] student = new Student[3];
student[0] = new Student("Kim");
student[1] = new Student("Kwon");
student[2] = new Student("Lee", "Seoul", "010xxxxxxxx", "abc@naver.com");
return student;
}
public void printStudents(Student[] students) {
for(Student data : students) {
System.out.println(data);
}
}
}
* 정리
생성자는 반드시 만들어야 하나요?
생성자는 반드시 만들 필요는 없으나, 만드는 습관을 가지는 것이 좋다.
만약 매개 변수가 있는 생성자를 만들고 매개 변수가 없는 기본 생성자를 호출하면 어떻게 될까요?
기본 생성자를 만들지 않고, 매개변수가 있는 생성자만 만들었을 때, 기본 생성자를 사용하여 객체를 생성할 수는 없다. 그러면 컴파일 에러가 발생한다.
생성자의 개수는 제한이 있나요?
생성자의 개수는 제한이 없다.
인스턴스의 변수와 매개 변수나 메소드 내에서 생성한 변수와 구분하기 위해서 사용하는 키워드는 무엇인가요?
this라는 예약어는 해당 객체를 의미한다. 따라서, 메소드 내에서 this를 사용하면 인스턴스 변수를 의미하게 된다.
메소드 선언시 리턴 타입으로 지정한 데이터를 넘겨줄 때 필요한 키워드는 무엇인가요?
return 예약어를 사용하여 메소드를 호출한 문장으로 결과를 넘겨준다.
메소드 선언시 아무 데이터도 리턴 타입으로 넘겨주지 않겠다는 것을 지정하는 키워드는 무엇인가요?
void 라는 예약어는 해당 메소드의 리턴 값이 없다는 것을 의미한다.
메소드 선언에 static이 있는 것과 없는 것의 차이는 무엇인가요?
static 메소드는 클래스의 객체를 생성하지 않고 클래스 이름만으로 참조할 수 있다.
많이 사용하는 System.out.println()의 경우는 System클래스에 out이라는 이름으로 선언된 클래스에 static으로 선언된 println()메소드를 호출하는 것이다.
필자가 엄청나게 중요하다고 한 것 중 메소드의 이름은 같으나 매개 변수를 다르게 하는 것의 명칭은 무엇인가요?
메소드의 이름을 동일하게하고, 매개변수만을 다르게 하는 것은 overloading이다.
기본 자료형을 매개 변수로 넘겨 줄 때 Pass by value인가요? 아니면 Pass by reference인가요?
모든 기본 자료형과 참조 자료형은 매개변수로 넘어갈 때 값이 넘어가는 Pass by Value이다.
참조 자료형을 매개 변수로 넘겨 줄 때 Pass by value인가요? 아니면 Pass by reference인가요?
참조 자료형 안에 있는 변수들은 매개변수로 넘어갈 때 참조가 넘어가는 Pass by Reference 이다.
매개 변수의 수가 가변적일 때 메소드 선언시 타입과 변수 이름 사이에 어떤 것을 적어줘야 하나요?
가변 매개변수를 지정할 때에는 "변수타입...변수명"으로 선언하면 된다. 이 선언을 할 때, 해당 변수는 매개변수 선언의 가장 마지막에 위치해야만 한다.
그럼 이만.
'Backend > Java' 카테고리의 다른 글
[Java] #10. 상속 (0) | 2022.03.14 |
---|---|
[Java] #09. 패키지와 접근 제어자 (0) | 2022.03.14 |
[Java] #07. 배열의 모든 것 (0) | 2022.03.13 |
[Java] #06. 자바의 제어문 (키워드 : 조건) (0) | 2022.03.10 |
[Java] #05. 자바의 연산자 (0) | 2022.03.10 |