본문 바로가기

Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어]/4. 객체지향 프로그래밍

4. Java 자바 - 클래스의 구성 멤버 [ 생성자 ]

생성자

 

new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다.

 

객체 초기화 : 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 하는 것

 

생성자를 실행시키지 않고는 클래스로 부터 객체를 만들 수 없다.

 

new 연산자에 의해 생성자가 성공적으로 실행되면, 힙 영역에 객체가 생성되고, 객체의 주소가 반환된다.

 

반환된 객체 주소는 클래스 타입 변수에 저장되어, 객체에 접근할 때 이용된다.

만약 생성자가 실행되지 않고 예외(에러)가 발생했다면, 객체는 생성되지 않는다.

 

- 기본 생성자

 

모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있다.

 

클래스 내부에 생성자 선언을 생략했다면, 컴파일러는 아래와 같이

중괄호 { } 블록 내용이 비어있는 기본 생성자(Default Constructor)를 바이트 코드에 자동 추가 시킨다.

 

바이트코드 내부

[public] 클래스() { }

 

클래스가 public class 로 선언되면 기본 생성자에도 public 이 붙지만,

 

클래스가 public 없이 class 로만 선언되면, 기본 생성자에도 public 이 붙지 않는다.

(나중에 접근 제한자에서 자세히!)

 

ex) Car 클래스를 설계할 때 생성자를 생략하면 아래와 같이 기본 생성자가 생성된다.

 

기본 생성자는 자동으로 생성된다.

 

따라서 클래스에 생성자를 선언하지 않아도, 아래와 같이 new 연산자 뒤에 기본 생성자를 호출해서

객체를 생성시킬 수 있다.

 

Car myCar = new Car();  // Car() : 기본 생성자

 

클래스에 명시적으로 선언한 생성자가 한 개라도 존재하면, 컴파일러는 기본 생성자를 추가하지 않는다.

 

명시적으로 생성자를 선언하는 이유는 객체를 다양하게 초기화하기 위해서이다.

 


 

1. 생성자 선언 (생성자 명시)

 

기본 생성자 대신 생성자를 명시적으로 선언하려면 아래와 같은 형태로 작성한다.

 

클래스 ( 매개변수선언, . . . ) {     // 생성자 블록
    // 객체의 초기화 코드
}

 

메소드와 비슷한 모양을 가지지만, 반환 타입이 없고 클래스 이름과 동일하다.

 

생성자 블록 내부에는 객체 초기화 코드가 작성되는데,

일반적으로 필드에 초기값을 저장하거나 메소드를 호출하여 객체 사용 전에 필요한 준비를 한다.

 

매개 변수 선언은 생략할 수 있고, 여러 개를 선언할 수도 있다.

 

매개 변수는 new 연산자로 생성자 호출 시 외부의 값을 생성자 블록 내부로 전달하는 역할을 한다.

 

ex) Car 생성자를 호출 시 세 개의 값을 제공할 때

Car myCar = new Car("그랜저", "검정", 300);

 

두 개의 매개값은 String, 마지막 하나는 int 타입이다.

세 개의 매개 값을 생성자가 받기 위해 아래와 같이 생성자를 선언해야 한다.

 

public class Car {
    // 생성자
    Car(String model, String color, int maxSpeed) {
    . . . 
    }
}

 

클래스 생성자가 명시적으로 선언되었을 경우, 반드시 선언된 생성자를 호출해서 객체를 생성해야만 한다.

 

Car 클래스에 생성자 선언이 있다면, 기본 생성자 ( Car( ) )를 호출해서 객체를 생성할 수 없고,

Car(String color, int cc) 를 호출해서 객체를 생성해야 한다. ( 매개 변수 값이 전달되어야 한다.)

 

Car.java : 생성자 선언

public class Car {
    // 생성자
    Car(String color, int cc) {
    }
}

 

CarExample.java : 생성자를 호출해서 객체 생성

public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car("검정", 3000);
        // Car myCar = new Car();  기본 생성자는 호출 불가
    }
}

 


 

2. 필드 초기화

 

클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정된다.

 

만약 다른 값으로 초기화하고 싶다면 두 가지 방법이 존재한다.

 

- 필드를 선언할 때 초기값을 주는 방법

 

- 생성자에서 초기값을 주는 방법 (매개 변수)

 

필드를 선언할 때 초기값을 주게 되면, 동일한 클래스로부터 생성되는 객체들은

모두 같은 데이터를 갖게 된다.

 

객체 생성 후 변경할 수 있지만, 객체 생성 시점에는 필드 값이 모두 같다.

 

ex) Korean 클래스에 nation 필드를 선언하면서 "대한민국"으로 초기값을 준 경우

Korean 클래스로부터 k1, k2 객체를 생성하면, k1, k2 객체의 nation 필드는 "대한민국"이 저장되어 있다.

 

public class Korean {
    String nation = "대한민국";
    String name;
    String ssn;
}

 

Korean k1 = new Korean();    // nation 필드에 "대한민국" 저장됨
Korean k2 = new Korean();

 

객체 생성 시점에서 외부에서 제공되는 다양한 값들로 초기화 되어야 한다면 

생성자에서 초기화를 해야 한다.

 

위의 코드에서 name(이름), ssn(주민번호) 필드 값은 클래스를 작성할 때 초기값을 줄 수 없고

객체 생성 시점에 다양한 값을 가져야 한다.

 

따라서 생성자의 매개값으로 이 값들을 받아 초기화 해야 한다.

 

public class Korean {
    //필드
    String nation = "대한민국";
    String name;
    String ssn;
    
    // 생성자
    public Korean(String n, String s) {
        name = n;
        ssn = s;
    }
}

 

name, ssn 필드는 매개변수 n, s 를 통해 값을 받는다.

 

public class KoreanExample {
    public static void main(String[] args) {
        Kroean k1 = new Korean("Kephi", "12345-12345");
        
        System.out.println("k1.name : "+ k1.name);
        System.out.println("k1.ssn : "+ k1.ssn);
        
        Korean k2 = new Korean("Batzzi", "1234-1234");
        
        System.out.println("k2.name : "+ k2.name);
        System.out.println("k2.ssn : "+ k2.ssn);
    }
}

 

관례적으로 필드와 동일한 이름을 갖는 매개 변수를 사용한다.

 

하지만 이 경우, 필드와 매개 변수의 이름이 동일하기 때문에, 생성자 내부에서 해당 필드에 접근할 수 없다.

 

동일한 이름의 매개 변수가 사용 우선순위가 높기 때문이다.

 

해결 방법 : 필드 앞에 "this."를 붙이면 된다.

 

this 는 객체 자신의 참조이고, 자신을 "나"라고 하듯 객체가 객체 자신을 this 라고 칭한다.

 

this를 사용해서 Korean 생성자를 수정하면 아래와 같다.

 

public class Korean {
    //필드
    String nation = "대한민국";
    String name;
    String ssn;

public Korean(String name, String ssn) {  //생성자의 매개변수가 필드와 동일한 경우 매개변수 우선
    this.name = name;   // this.name (필드), name (매개변수)
    this.ssn = ssn;     // this.ssn (필드), ssn (매개변수)
}

 

객체의 필드는 하나가 아니라 여러 개가 있고, 이 필드들을 모두 생성자에서 초기화한다면

 

생성자의 매개 변수의 수는 필드 수만큼 선언되어야 한다.

 

실제로는 중요한 몇 개의 필드만 매개 변수를 통해 초기화되고, 나머지 필드들은 필드 선언시 초기화하거나,

생성자 내부에서 임의의 값 또는 계산된 값으로 초기화한다.

또는, 객체 생성 후에 필드값을 별도로 저장하기도 한다.

 

 


 

3. 생성자 오버로딩 (Overloading)

 

외부에서 제공되는 다양한 데이터들을 이용해서 객체를 초기화 하려면,

 

생성자도 다양화될 필요가 있다.

 

Car 객체를 생성 시 외부에서 제공되는 데이터가 없다면, 기본 생성자로 Car 객체를 생성해야 하고,

외부에서 model, color 데이터가 제공될 경우, Car 객체를 생성할 수 있어야 한다.

(두 가지 경우일 때 모두 객체를 생성할 수 있어야한다.)

 

따라서 생성자가 하나뿐이라면, 두 가지 경우에 따라 객체를 모두 생성할 수 없다.

 

자바는 다양한 방법으로 객체를 생성할 수 있도록 생성자 오버로딩을 제공한다.

 

생성자 오버로딩 : 매개 변수를 달리하는 생성자를 여러 개 선언이 가능하도록 하는 것.

 

pubilc class 클래스 {
    클래스 ( [타입 매개변수1, 타입 매개변수2, . . . ] ) {   // 생성자1
        . . .
    }
    
    클래스 ( [타입 매개변수1, 타입 매개변수2, . . . ] ) {   // 생성자2
        . . . 
    }
}

 

ex) Car 클래스에서의 생성자 오버로딩

public class Car {
    Car() { . . . }
    Car(String model) { . . . }
    Car(String model, String color) { . . . }
    Car(String model, String color, int maxSpeed) { . . . }
}

 

생성자 오버로딩시 주의할 점!

 

매개 변수의 타입과 개수, 선언된 순서가 똑같을 경우 

매개 변수 이름만 바꾸는 것은 생성자 오버로딩으로 볼 수 없다. (구분 불가)

 

Car(String model, String color) { . . . }
Car(String color, String model) { . . . } // 오버로딩이 아니다!!

 

생성자가 오버로딩 되어 있는 경우, new 연산자로 생성자를 호출할 때,

제공되는 매개값의 타입과 개수에 의해 호출될 생성자가 결정된다.

 

ex) 다양한 방법으로 Car 객체가 생성된다.

 

Car car1 = new Car();                       // 기본 생성자로 객체 생성
Car car2 = new Car("그랜저");               // Car(String model) 생성자 호출
Car car3 = new Car("그랜저", "흰색");       // Car(String model, String color) 생성자
Car car4 = new Car("그랜저", "흰색", 300);  // Car(String model, String color, int maxSpeed) 생성자

 

ex) Car.java 생성자 오버로딩

public class Car {
    //필드
    String company = "현대";
    String model;
    String color;
    int maxSpeed;
    
    //생성자 오버로딩
    Car(){
    }
    
    Car(String model) {
        this.model = model;   // 필드 model 에 매개변수 값 저장
    }
    
    Car(String model, String color) {
        this.model = model;
        this.color = color;
    }
    
    Car(String model, String color, int maxSpeed) {
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}

 

ex) CarExample.java 객체 생성 시 생성자 선택

public class CarExample {
    public static void main(String[] args) {
        Car car1 = new Car();   // 생성자 선택1 : 기본생성자
        System.out.println("car1.company : "+ car1.company);  // 현대
        System.out.println();
        
        Car car2 = new Car("자가용");    // 생성자 선택2
        System.out.println("car2.company : "+ car2.company);  // 현대
        System.out.println("car2.model : "+ car2.model);      // 자가용
        System.out.println();
        
        Car car3 = new Car("자가용", "빨강");   // 생성자 선택3
        System.out.println("car3.company : "+ car2.company);  // 현대
        System.out.println("car3.model : "+ car2.model);      // 자가용
        System.out.println("car3.color : "+ car2.color);      // 빨강
        System.out.println();
        
        Car car4 = new Car("택시", "검정", 200);   // 생성자 선택4
        System.out.println("car4.company : "+ car4.company);    // 현대
        System.out.println("car4.model : "+ car4.model);         // 택시
        System.out.println("car4.color : "+ car4.color);         // 빨강
        System.out.println("car4.maxSpeed : "+ car4.maxSpeed);   // 200
    }
}

 


 

4. 다른 생성자 호출 this( )

 

생성자 오버로딩이 많아질 경우 생성자간 중복 코드가 많아질 수 있다.

매개 변수의 수만 다르게 하고 필드 초기화 내용이 비슷한 생성자에서 이런 현상이 많아진다.

 

이런 경우, 필드 초기화 내용은 한 생성자에서만 집중적으로 작성하고,

나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.

 

- 초기화 내용 가진 생성자

- 그 초기화 내용을 가진 생성자를 호출하는 생성자

 

생성자에서 다른 생성자를 호출할 때는 this( ) 코드를 사용한다.

 

클래스( [매개변수선언, . . .] ) {         // 생성자 내부
    this(매개변수값1, 매개변수값2, . . . );    // 클래스의 다른 생성자 호출
    실행문;
}

 

this( ) 는 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫 줄에서만 허용된다.

 

this( ) 의 매개값은 호출되는 생성자의 매개 변수 타입에 맞게 제공해야 한다.

 

this( ) 다음에는 추가적인 실행문들이 올 수 있다.

호출되는 생성자의 실행이 끝나면, 원래 생성자로 돌아와서 다음 실행문을 진행한다.

 

아래 코드의 중복을 제거하려면..

Car(String model) {
    this.model = model;    // 중복되는 코드
    this.color = "은색";   // 중복되는 코드
    this.maxSpeed = 250;   // 중복되는 코드
}

Car(String model, String color) {
    this.model = model;  // 중복되는 코드
    this.color = color;  // 중복되는 코드
    this.maxSpeed = 250; // 중복되는 코드
}

Car(String model, String color, int maxSpeed) {
    this.model = model;        // 중복되는 코드
    this.color = color;        // 중복되는 코드
    this.maxSpeed = maxSpeed;  // 중복되는 코드
}

 

this( ) 를 사용해서 앞의 두 개의 생성자에서

마지막 생성자인 Car(String model, String color, int maxSpeed)를 호출하도록 수정한다.

 

Car.java

public class Car {
    //필드
    String company = "현대";
    String model;
    String color;
    int maxSpeed;
    
    //기본 생성자
    Car() {
    }
    
    Car(String model) {             // 생성자1
        this(model, "은색", 250);   // 생성자3 호출함
    }
    
    Car(String model, String color) {    //생성자2
        this(model, color, 250);         // 생성자3 호출
    }
    
    Car(String model, String color, int maxSpeed) {  //생성자3
        this.model = model;          // 공통 실행 코드
        this.color = color;          // 공통 실행 코드
        this.maxSpeed = maxSpeed;    // 공통 실행 코드
    }
}

 

 

CarExample.java 객체 생성 시 생성자 선택

public class CarExample {
    public static void main(String[] args) {
        Car car1 = new Car();   // 기본 생성자
        System.out.println("car1.company : "+ car1.company);
        System.out.println();
        
        Car car2 = new Car("자가용");
        System.out.println("car2.company : "+ car2.company);
        System.out.println("car2.model : "+ car2.model);
        System.out.println();
        
        Car car3 = new Car("자가용", "빨강");
        System.out.println("car3.company : "+ car3.company);
        System.out.println("car3.model : "+ car3.model);
        System.out.println("car3.color : "+ car3.color);
        System.out.println();
        
        Car car4 = new Car("택시", "검정", 200);
        System.out.println("car4.company : "+ car4.company);
        System.out.println("car4.model : "+ car4.model);
        System.out.println("car4.color : "+ car4.color);
        System.out.println("car4.maxSpeed : "+ car4.maxSpeed);
    }
}