[Java] 객체지향 프로그래밍(oop) - 생성자(constructor)

SolChan Kim·2023년 11월 28일

📖생성자란?

  • 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드

    • 인스턴스 초기화 : 인스턴스 변수에 적절한 값을 저장하는 것
  • 인스턴스 변수의 초기화 또는 인스턴스 생성 시 수행할 작업에 사용한다.

  • 몇가지 조건을 제외하고는 메서드와 동일하다.

  • 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.


🤔지금까지 생성자를 만들지 않았는데?

  • 컴파일러가 자동적으로 생성자를 만들어 주었기 때문이다.

  • 클래스에 생성자가 하나도 없을 때 컴파일러는 자동적으로
    기본생성자를 추가해준다.


생성자 예시

Car car = new Car();

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

2. 생성자 Car()(기본 생성자)가 호출되어 수행된다.

3. 연산자 new의 결과로, 생성된 Car인스턴스의 주소가 반환되어 참조변수 car에 저장된다.


📖생성자의 조건

  • 생성자의 이름은 클래스명과 동일해야 한다.

  • 생성자는 리턴값이 없다.

    • 리턴값이 없다고 해서 void를 사용하지 않는다.
클래스 이름(타입 변수명, 타입 변수명...){
	// 인스턴스 생성시 수행될 코드
    // 주로 인스턴스 변수의 초기화 코드를 적는다.
}

public class Car{
	String name, color, price;
    
    // 매개변수가 없는 기본 생성자(public 생략 가능)
    public Car(){
    	// 인스턴스 초기화 작업
    }
    
    // 매개변수가 있는 생성자
    // - 하나의 클래스의 다수의 생성자를 정의해야 하는 경우 오버로딩!
    public Car(String name, String color, String price){
    	// 인스턴스 초기화 작업
    }
}

📖기본 생성자(default constructor)

  • 매개변수가 없는 생성자

  • 클래스에 생성자가 하나도 없으면 컴파일러가 기본 생성자 자동 추가

  • 생성자가 하나라도 존재할 경우 컴파일러는 기본 생성자 생성X

public class Car{
	String name, color, price;
}

위와 같이 클래스에 생성자가 하나도 존재 하지 않을 경우,
코드상으로는 보이지 않아도 컴파일러가 자동으로
기본 생성자(public Car(){})를 추가해준다.

public class Data{
	int value;
}

public class Data2{
	int value;
    
    // 매개변수가 있는 생성자(기본 생성자X)
    public Data2{int x}{
    	value = x;
    }
}

public class constructorTest{
	public static void main(String[] args){
    	// 컴파일러가 자동으로 기본 생성자를 추가하므로 ok!
    	Data d1 = new Data();
        // compile error : 생성자가 하나라도 있으면 컴파일러는 기본 생성자 추가X
        // Data d2 = new Data();
        
    }
}

Data클래스는 생성자가 하나도 없지만 컴파일 하면 기본생성자가
자동적으로 추가됩니다.

그래서 인스턴스를 생성할 때 new Data()와 같이 해도
생성자 Data()이 있으니까 에러가 발생하지 않는다.

Data2클래스는 생성자가 있기 때문에 Data클래스와 달리
컴파일러에 의해서 기본생성자가 추가되지 않는다.

그래서 new Data2()와 같이 인스턴스를 생성하면
생성자 Data()가 존재하지 않으므로 에러가 발생하는 것이다.

에러가 발생하지 않게 하려면
Data2클래스에 기본생성자(Data2() {})를 추가로 정의하던가
인스턴스를 생성할 때 매개변수가 있는 생성자만을 사용해야 한다.
new Data2(); -> new Data(10);


📖매개변수가 있는 생성자

public class Car{
	// 색상, 변속기 종류(auto : 자동 manual : 수동)
	String color, gearType;
    // 가격
    int price;
    
    // 기본 생성자
    public Car(){}
    // 매개변수가 있는 생성자
    public Car(String c, String g, int p){
    	// 인스턴스 초기화
    	color = c;
        gearType = g;
        price = p;
    }
}

publc class CarTest{
	public static void main(String[] args){
    	// 기본생성자를 호출하여 인스턴스 초기화 
    	Car car1 = new Car();
        car1.color = "blue";
        car1.gearType = "auto";
        car1.price = 10000000;
        
        // 매개변수가 있는 생성자를 호출하여 인스턴스 초기화
        Car car2 = new Car("red", "manual", 20000000);
    }
}

Car클래스를 정의했을 때
기본생성자를 이용해서 인스턴스를 생성한 다음,
인스턴스 변수를 따로 일일이 초기화 해주는 것보다
매개변수가 있는 생성자를 이용해서 인스턴스를 생성할 때
직관적으로 알기쉽게 초기화하는 것이 좋다.

여러 개의 Car인스턴스를 만든다고 할 때 기본 생성자를 사용한다면
매개변수가 있는 생성자를 사용했을 때보다 훨씬 더 복잡해 질 것이다.

그래서 클래스를 만들 때
매개변수를 통해 인스턴스를 초기화 할 수 있는
생성자를 적절히 제공함으로써 클래스를 사용하는 쪽에서 간결한 코드를
작성할 수 있도록 지원하는 것이 바람직하다.


📖this()

  • 같은 클래스의 생성자에서 다른 생성자를 호출할때 사용한다.
    • 다른 생성자 호출은 생성자의 첫 문장에서만 가능하다.

Pizza클래스의 기본 생성자 Pizza()에서
각 인스턴스변수의 값을 "Bulgogi Pizza", "Regular", 10000으로 초기화 한다.

피자를 주문할 때 아무런 옵션도 주지 않으면
기본적으로 위와 같은 피자가 만들어 지는 것이다.

코드 자체에는 문제가 없지만
이렇게 하는 것보다 매개변수가 있는 생성자를 호출하도록 하는 것이
재사용성을 높이고 코드의 중복을 제거하는 좋은 코드다.

같은 클래스의 한 생성자에서 다른 생성자를 호출할 때는
클래스이름이 아닌 this()를 사용해야 한다.
그리고 반드시 생성자의 첫 줄에 한번만 호출할 수 있다.


📖참조변수 this

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

  • 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.

    • 별뜻없이 그냥 사용할 수 있다는 의미

괄호가 붙은 this()는 생성자고,
괄호가 없는 this는 인스턴스 자기 자신을 가리키는 참조변수다.
-> 인스턴스의 주소가 저장되어 있다.

참조변수 this는 모든 인스턴스 메서드에서 사용할 수 있지만,
main메서드와 같은 static메서드에서는 사용할 수 없다.

인스턴스변수와 지역변수가 이름이 같을 때는
인스턴스변수 앞에 this.를 붙여야 서로 구분이 된다.
만약에 여기서 this를 붙이지 않는다면 이름이 같기 때문에
둘 다 지역변수로 간주된다.

인스턴스변수와 지역변수가 이름이 다르면 this를 사용하지 않고도
서로 구별이 되긴 하지만 가능하면 매개변수의 이름을 인스턴스변수의
이름과 같게 정의하고 this를 사용해서 구별하는 것이
코드의 의미가 더 명확해진다.


📖생성자를 이용한 인스턴스 복사

  • 인스턴스간의 차이는 인스턴스변수의 값 뿐 나머지는 동일하다.

  • 생성자에서 참조변수를 매개변수로 받아서 인스턴스변수들의 값을 복사한다.

  • 똑같은 속성값을 갖는 독립적인 인스턴스가 하나 더 만들어진다.

public class Pizza{
	String name, size;
    int price;
    
    // 기본 생성자
    public Pizza(){
    	// 생성자에서 다른 생성자 호출
    	this("basicPizza", "Reqular", 10000)
    }
    
    // 인스턴스 자신을 가리키는 this를 이용한 생성자
    public Pizza(String name, String size, int price){
    	this.name = name;
    	this.size = size;
    	this.price = price;
    }
    
    // 인스턴스의 복사를 위한 생성자
    // 매개변수로 Pizaa클래스의 참조변수p를 받는다.
    public Pizza(Pizza p){
    	// 인스턴스 변수 = p가 참조할 인스턴스의 전역변수들
    	name = p.name;
        size = p.size;
        price = p.price;
    }
}

public class PizzaTest{
	public static void main(String[] args){
    	// new를 통해 Pizaa클래스의 인스턴스를 생성한다.
        // Pizza()를 통해 Pizaa클래스의 기본생성자를 호출한다.
        // 기본생성자에서는 this()생성자를 통해 매개변수를 가지고 있는
    	// 생성자를 호출하고, 인스턴스 초기화가 이루어진다.
    	Pizza pizza01 = new Pizza();
        
        // new를 통해 Pizza클래스의 인스턴스를 생성한다.
        // 참조변수 pizza01를 매개변수로 가지는 Pizaa클래스의 생성자를 호출한다.
        // 참조변수 pizza01이 참조하고 있는 대상(인스턴스)을
        // 호출된 생성자의 매개변수 p도 참조하게 된다.
        // Pizza pizza01 = new Pizza();에서 생성된 인스턴스의
        // 인스턴스 변수들의 값이 p가 가리키고 있는 인스턴스 메모리
        // 주소에 있는 인스턴스 변수들에 각각 인스턴스 초기화가 이루어진다. 
        Pizza pizza02 = new Pizza(pizza01);
    }
}

인스턴스를 생성하고, 참조변수 pizza01이 생성된인스턴스의 메모리주소를
참조하고 있다.

참조변수 pizza02역시 인스턴스를 생성하고, 생성자를 호출했을 때
호출된 생성자의 매개변수로 pizza01이 참조하고 있는 인스턴스의 메모리 주소를 전달했다.

전달된 인스턴스의 메모리 주소는 호출된 생성자에 매개변수 p가 받는다.
-> 참조변수 pizza01이 바라보고 있는 인스턴스 메모리 주소를 p도 바라보게 된다.

p가 바라보고 있는 인스턴스의 인스턴스변수들의 값을 Pizza클래스의 인스턴스 변수들의 값으로 각각 초기화한다.

결국, pizza01이 바라보고 있는 인스턴스 메모리주소와 pizza02가 바라보고 있는 인스턴스 메모리 주소는 동일하다.(복사가 이루어짐)

pizza02가 처음 참조했던 인스턴스는 아무도 참조하지 않게 되므로
가비지컬렉터(GC)에 의해 제거된다.

0개의 댓글