본문 바로가기

프로그래밍/JAVA

[JAVA] 생성자(Constructor)

JAVA의 정석(2nd Editionm) (남궁 성 著) 191p~199p 를 참조해 코드를 작성했으며

개인적인 공부 내용을 적은 것이므로 오류가 있을 수 있습니다.


0. 들어가기에 앞서


개인적으로 생성자를 공부할 때 가장 어려웠다.

왜 있어야 하는지도 모르겠고, 언제 사용해야 하는지도 잘 모르겠다.


내가 시행착오를 겪어가며 이해한 내용을 정리하고자 한다.


클래스(class)는 인스턴스를 만들기 위한 기본 뼈대이고

인스턴스(instance)는 구체적인 "상태(variable, 변수)" 와 "행위(method, 기능)"의 집합이라고 생각하자.


위 카드를 생각해본다면 비자카드 클래스(class)에 대표적으로 들어가야 할 것들이 있을 것이다.


  1. 고객의 이름 (변수)

  2. 카드 유효기간 (변수)

  3. 카드 번호 (변수)

  4. 정보 인식 기능 (메서드)

  5. 정보 보안 기능 (메서드)


그런데 고객 A, 고객 B, 고객 C 마다 가지고 있어야 할 정보들이 각기 다를 것이다.

즉, 카드는 각기 다른 "상태와 행위"를 가져야 한다. 그래서 인스턴스를 생성한다.


VisaCard 고객 A = new VisaCard ( ) ; // 고객 이름 : 김래원 , 유효기간 06월/20년

VisaCard 고객 B = new VisaCard ( ) ; // 고객 이름 : 김우빈  , 유효기간 05월/13년

VisaCard 고객 C = new VisaCard ( ) ; // 고객 이름 : 이종석  , 유효기간 12월/17년


인스턴스의 존재이유를 "서로 다른 상태를 가지게 하기 위해서!" 라고 생각하자!

공부하면서 그렇게 생각하는 것이 제일 간단하다고 느꼈다.


1. 생성자(Constructor)


우리는 인스턴스에 대해서 배웠다.

인스턴스는 실제 메모리의 빈 공간에 할당된다.

이런 메모리에 실제 인스턴스가 생성된다고 간단하게 생각하자.

(인스턴스에는 여러 멤버 변수와 메서드들이 존재한다)


오픈튜토티얼 저자의 말을 빌리자면

생성자(Constructor)는 업무를 하기 전에 책상정리와도 비슷하다고 말한다. 이를 '초기화'라고 한다.


Card c = new Card();

연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.

여기 자세히 보면 Card()가 생성자이다.


new 연산자로 객체를 생성하는 과정에서 최초로 수행해야 할 일이 "필드를 초기화 하는 것"이다.

 (※ 인스턴스 "변수"를 초기화하는 것이 아니라 인스턴스(객체) 를 초기화 하는 일이다!)


위에서 예로 들었던 것을 다시 활용해보자.

우리는 연예인 VIP 카드를 만들어 줄 것이다.


VisaCard 고객 A = new VisaCard ( ) ; // 고객 이름 : 김래원 , 유효기간 06월/20년

  ☞ VisaCard 고객 A = new VisaCard ( "김래원", "06/20" )


VisaCard 고객 B = new VisaCard ( ) ; // 고객 이름 : 김우빈  , 유효기간 05월/13년

  ☞ VisaCard 고객 B = new VisaCard ( "김우빈", "05/13" )


VisaCard 고객 C = new VisaCard ( ) ; // 고객 이름 : 이종석  , 유효기간 12월/17년

  ☞ VisaCard 고객 A = new VisaCard ( "이종석", "12/17" )


  위와 같이 인스턴스 생성과 동시에 생성자를 통해 사용자 입맛에 맞게 초기화할 수 있다.



사용자가 매개변수를 던져주지 않는다면 위 생성자에 의해 초기화 되고

사용자가 매개변수를 던져준다면 아래 생성자에 의해 초기화 될 것이다


즉, 매개변수가 있다는 것은 사용자의 입맛대로 초기화 하겠다는 뜻이다.



개념 : 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'

생성자 역시 메서드처럼 클래스 내에 선언된다. 구조도 메서드와 비슷하지만 리턴값이 없다.


목적 :  인스턴스 변수의 초기화 작업에 주로 사용 (인스턴스 생성 시에 실행되야 할 작업을 정의)


조건 : ① 생성자의 이름은 클래스의 이름과 같아야 한다.

        ② 생성자는 리턴 값이 없다. ( public 클래스명(매개변수 목록) )

③ 생성자를 만들지 않으면 매개변수 없는 생성자가 컴파일 할 때 자동으로 만들어진다.



Card c = new Card () ;


인스턴스를 new연산자로 생성하고 Card() 라는 생성자를 통해 인스턴스 변수들을 초기화한다.




2. 기본 생성자(default constructor)


  컴파일러가 제공하는 기본 생성자.

클래스이름 () { }
Card() { }


기본 생성자는 매개변수도 없고 로직의 내용도 없다.



Data 1의 인스턴스 : 에러발생 (X)

Data 2의 인스턴스 : 에러발생 (O)



Data 1 클래스 내에 생성자가 없으므로 컴파일러가 "기본 생성자"를 추가해준다.

(기본생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때)


Data2 클래스 내에 생성자 Data2 (int x) { Value = x ; } 가 정의되어 있다.

Date2 생성자를 살펴보면 매개변수로 int x가 전달되고 있다. (즉, 매개변수가 있는 생성자)

(그런데,  컴파일러는 매개변수가 있는 생성자가 있을 때는 자동으로 기본생성자를 만들어주지 않음)


따라서 위 에러를 고치는 두 가지 방법이 있다.

1. Data 2 클래스 내에 기본 생성자(default constructor)를 만들어주거나

2. 인스턴스 생성할 때 int 타입의 정수를 던져주거나



class Data2 내에 Data2() {} ; 를 추가해주거나

Data2 인스턴스를 생성할 때 생성자 Data2(임의의 정수값)을 사용하면 된다.



3. 매개 변수가 있는 생성자(default constructor)


  

인스턴스 생성시, 사용자가 원하는 값으로 초기화 하고 싶은 경우가 있다.


  *매개변수(Parameter) : 전달된 인자를 받아들이는 변수를 의미

  *인자(Argument) : 어떤 함수를 호출시에 전달되는 값을 의미


만약 나만의 커스터마이징 된 자동차를 만들고 싶다면?

자동차 색깔은 "하얀색"으로, 기어타입은 수동보다는 "자동"으로, 문 개수는 "4개"를 원한다면

인스턴스 생성시에 값을 던져주면 된다.


Car c = new Car ();   // Car 인스턴스 생성

c.color = "white";

c.gearType = "auto";

c.door=4;


위 예제는 Car 인스턴스 생성 후 → 원하는 값으로 초기화 하고 있다.

그러나 "매개변수가 있는 생성자"로 만들면 코드가 간결하고 직관적이다


Car c = new Car("white", "auto", 4);  // 이때, "white", "auto", 4가 매개변수이다.

이렇게 매개변수가 있는 생성자를 사용하면 동시에 원하는 값으로 초기화를 할 수 있다.


위 예제와 비교해보았을 때, 무려 3줄을 절약할 수 있다.


[전체 코드]


<복습> 매개변수는 지역변수 (메서드가 종료되면 소멸되는 변수) 이고, 이 값을 인스턴스 변수에 저장한다.


4. 생성자에서 다른 생성자 호출하기 - this(), this


처음에 이해하기 너무~~~ 어려운 개념이다. 

변수에 대한 이해가 적절하게 필요하다.

인스턴스변수와 지역변수에 대한 차이를 모른다면 적절한 학습을 하고 오자.

(링크 : http://whatisthenext.tistory.com/37)


바로 위에서 쓴 예제는 개선할 여지가 있다.

매개변수를 위해서 이름이 낭비되고 있다.

String c, String g, int d 


순서대로 color, gearType, door을 의미하지만, 매개변수 c, g, d만 보고 사용자는 무슨 의미인지 모른다.

이러한 불편함을 개선해보자



매개변수의 이름이 좀 더 직관적으로 변경되었다.

String color, String gearType, int door


우리는 생성자의 목적이 인스턴스 변수를 초기화하는 데 있다고 했다.

그런데 인스턴스변수명과 매개변수 명(=지역변수)이 같으니 문제가 발생한다.

따라서 this를 붙인다. 


다시 한번 정리하자

 매개변수(=지역변수) : color, gearType, door

 인스턴스변수 : this.color, this.gearType, this.door


클래스 멤버 간에 서로 호출이 가능한 것처럼, 생성자 간에도 서로 호출이 가능하다. 

그러나 두 가지 조건을 만족해야 한다.


1. 생성자의 이름으로 클래스이름 대신 this를 사용한다.

2. 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

반드시 첫 줄에서만 호출이 가능한 이유는  다른 생성자에 의해 에 수행한 초기화 작업이 덮어쓸 가능성

이 있어 초기화 작업이 의미가 없어질 수 있기 때문이다.



참조변수 c1, c2를 통해 "인스턴스 멤버(변수+메서드)"에 접근할 수 있고

               this를 통해 "인스턴스 변수"에 접근할 수 있다.


(단, this가 사용할 수 있는 것은 "인스턴스 변수" 뿐이다.

클래스 메서드(static 메서드)에서는 인스턴스 멤버를 사용할 수 없는 것처럼 (생성 안했을 수도 있으니까)

 

this - 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다.

this(), this(매개변수) - 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용된다.



클래스를 만들고 이를 이용해서 객체(인스턴스)를 만들려면 new 연산자를 이용해야한다.

new 연산자 뒤에는 반드시 생성자가 나와야 한다.


5. 생성자를 이용한 인스턴스의 복사




여기서 출제자의 의도가 무엇일까.


1. 먼저 Car타입의 참조변수 c2를 생성할 때 생성자에 매개변수 c1을 전달해 "복사"했다.


Car c1 = new Car() 

Car c2 = new Car(c1)


를 선언하면 메모리에 객체가 생긴다.

c1과 c2는 이 메모리의 주소값을 가르키게 된다.

이후

c1.door = 100을 선언했다.


인스턴스 c2는 c1을 복사했지만, c1.door=100을 했다고 해서 c2의 값에 영향을 받는 것은 아니다.

별도의 메모리 공간에 존재하기 때문이다.



class Car {
String color;
String gearType;
int door;
Car(){this("white", "auto", 4);}
Car(Car c) {
color = c.color;
gearType = c.gearType;
door = c.door;}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class Lee{
public static void main(String[] args){
Car c1 = new Car();
Car c2 = new Car(c1);
System.out.println("c1의 color=" + c1.color + ", gearType= " + c1.gearType+ ", door="+c1.door);
System.out.println("c2의 color=" + c2.color + ", gearType= " + c2.gearType+ ", door="+c2.door);
c1.door=100;
System.out.println("c1.door=100 수행 후");
System.out.println("c1의 color=" + c1.color + ", gearType= " + c1.gearType+ ", door="+c1.door);
System.out.println("c2의 color=" + c2.color + ", gearType= " + c2.gearType+ ", door="+c2.door);
}
}




5. 나의 시행착오


1. 클래스 내에 여러 개의 생성자를 정의할 수 있다. (즉, 오버로딩이 가능하다.)

이게 뭘까하면서 계속 생각했다. ㅠㅠ

오버로딩은 개성을 존중하는 것이다. 

사용자가 값을 던져주지 않는다면 num을 1로, isKwang을 true로 초기화하고

사용자가 값을 던져준다면 사용자가 던져주는 num과 isKwang으로 초기화한다는 뜻이다.



2. 멤버(member)라는 뜻은 변수+메서드를 합친 말을 일컫는 말이다.


3. static 메서드가 호출된 시점에 인스턴스가 존재하지 않을 수 있다.


즉, static 메서드를 붙이면 인스턴스 생성 없이 호출이 가능한데,

인스턴스가 존재하지 않는다는 것은 Card c1 = new Card(); 이런 식으로 인스턴스를 생성하지 않았을 수도 있다는 것이다.