생성자

박영준·2022년 11월 26일
0

Java

목록 보기
3/111

1. 정의

  • 객체 생성 시, 초기화 담당

  • new 연산자로 호출됨

  • 모든 클래스에 반드시 1개 이상 존재

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

    • 인스턴스 생성시 수행할 작업에 사용
    • 인스턴스 변수의 값을 초기화할 때 주로 사용
  • 메서드와 비교

    • 생성자도 메서드처럼 클래스 내에 선언
    • 구조는 메서드와 유사하지만, 리턴값이 없다는 점(void 조차 적지 않는다)
  • 오버로딩이 가능함

    • 따라서, 하나의 클래스에 여러개의 생성자가 존재할 수 있음
  • 단, 생성자가 인스턴스를 생성하는 것이 아니다.

    • 연산자 new가 인스턴스를 생성하는 것!

2. 생성 조건

  1. 생서자 이름 == 클래스 이름

  2. return 값 없다.

3. 생성 방법

클래스 이름(타입변수명, 타입변수명, ...) {
	인스턴스 생성 시 수행될 코드. 주로 인스턴스 변수의 초기화 코드
}

1) 기본 생성자

(1) 정의

  • 매개변수가 없는 생성자를 의미

    public Car() {
    }
  • 클래스 내부에 생성자 선언을 생략 or 하나도 선언하지 않았다면?
    : 컴파일러가 { } 중괄호 블록이 비어있는 기본 생성자를 바이트 코드에 자동 추가함

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

  • 상속의 경우
    부모 클래스에서 기본 생성자 없으면
    자식 클래스에서 생성자를 만들어주면 됨

    참고: 상속

(2) 예시

예시 1

pulic class Car {

	//car(): 기본 생성자
	public Car() { }		// 자동 추가
}    

예시 2

Data1클래스

class Data1 {
	int value;
}

Data2클래스

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

EX클래스

class EX {
	public static void main(String[] args) {
    	Data1 d1 = new Data1();
        // Data2 d1 = new Data2();		컴파일 에러 발생
        Data2 d1 = new Data2(10);		// OK
    }
}
  • Data2클래스에서 Data2()라는 생성자를 찾을 수 없기 때문에 에러가 발생
    • Data1클래스 : 아무 생성자도 없으므로, 자동으로 기본생성자가 추가된 상태라 에러가 없음
    • Data2클래스 : 이미 Data2(int x) 라는 매개변수가 있는 생성자가 추가된 상태라 기본생성자는 추가되지 않음
      • 해결법 1 : Data2클래스의 인스턴스 생성 시, Data2(int x) 를 사용
      • 해결법 2 : Data2클래스에 기본 생성자 Data2()를 추가로 정의해주기

예시 2

class Tv9_1 {
    // 속성 : 변수 선언
    boolean power; // 전원상태
    int channel;  // 채널
    String color; // 색깔
    long price; // 가격

    // 위 속성에서 필수로 초기값이 필요한 값들을 초기화 해주는 기본 생성자
    public Tv9_1() {
        power = false;
        channel = 1;
    }

    //  오버로딩 한 생성자 - 매장 진열 용 일 경우에는 가격과 색깔의 초기화가 필요합니다. this는 매개변수와 인스턴스변수를 구분하기 위해 사용
    public Tv9_1(String color, long price) {
        power = false; // this.power, power 둘다 지금 상황에서는 인스턴스 변수를 명확하게 판단 할 수 있기 때문에 어떤걸 사용해도 상관 없습니다.
        channel = 1;
        this.color = color;
        this.price = price;
    }

    // 기능 : 메서드 선언
    void power() {  // 전원 기능
        power = !power;
        if (power) {
            System.out.println("전원 ON");
        } else {
            System.out.println("전원 OFF");
        }
    }

    void channelUp() { // 채널 증가
        channel++;
        System.out.println("채널 증가");
    }

    void channelDown() { // 채널 감소
        channel--;
        System.out.println("채널 감소");
    }

}

class Tv9_1Main {
    public static void main(String[] args) {
        // 기본 초기화된 Tv9_1 생성
        Tv9_1 tv = new Tv9_1();
        System.out.print("기본 생성자 TV: ");
        tv.power();

        // 진열 용 Tv9_1 생성
        Tv9_1 exTv = new Tv9_1("보라색", 3456789);
        System.out.print("오버로딩 생성자 TV: ");
        exTv.power();
        System.out.println("exTv.color = " + exTv.color);
        System.out.println("exTv.price = " + exTv.price);

        // 근데 이때 주의할 점!
        // 기본 생성자는 없고 오버로딩한 생성자만 있을 경우!
        // 컴파일러는 기본 생성자를 만들어주지 않기 때문에 기본 생성자를 사용하려고 하면 Error 발생!
        // 위 기본 생성자를 주석 하세요!!!!
        Tv9_1 tv2 = new Tv9_1(); // Error 발생, 기본 생성자가 없기 때문에 매개 변수를 넣으라고 intellij 가 요구합니다.

    }
} 

예시 3

class Product {
	int price;
    int point;
    
    Product() {				// 기본 생성자
    }
    
    Product(int price) {		// 생성자
    	this.price = price;
    	point = (int) (price / 10.0);
    }
}

class ... {
	...
}
...
  • Product(int price)
    • 생성자가 이미 추가돼있으면 컴파일러는 자동으로 기본 생성자를 생성해주지 않음
    • 따라서, 직접 기본 생성자를 넣어줘야 한다

2) 매개변수가 있는 생성자

(1) 정의

기본 생성자 대신, '명시적으로 생성자를 선언'하는 경우

  1. 매개변수를 선언

    public class Car {
    
        // 생성자 (명시적 생성자 선언)
        Car(String model, String color, int maxSpeed) {
            ...
        }
    }
  2. 매개변수에 각각 매개값을 넣어준다.

    // String 타입 매개값 2개, int 타입 매개값 1개
    car myCar = new Car("그랜저", "검정", 300);
  • 매개 변수

    • 역할 : new 연산자로 생성자 호출 시, 외부 값을 생성자 블록 내부로 전달
    • 생략 가능
    • 여러개 선언 가능
  • 주의점

    • 클래스에 생성자가 명시적으로 선언되어 있는 경우(기본 생성자가 아닌 생성자 선언인 경우),
      반드시 선언된 생성자를 호출해서 객체를 생성해야 한다.

    • 명시적 생성자 선언을 한 경우, 기본 생성자는 호출 불가능
      Car myCar = new Car(); → (X)

(2) 예시

// 기존 코드
Car c = new Car();		// 인스턴스 생성
c.color = "white";		// 인스턴스의 값 초기화
c.gearType = "auto";
c.door = 4;

// 매개변수를 갖는 생성자를 사용한다면
Car c = new Car("white", "auto", 4)		// 인스턴스 생성 & 초기화

4. 필드 초기화

public class Korean {

    // 필드
    String nation = "대한민국";
    String name;    // 이름
    String ssn;     // 주민번호

    // 생성자
    public Korean(String n, String s) {
        name = n;
        ssn = s;
    }
}
public class KoreanExample {
    public static void main(String[] args) {
    
        // 객체 생성
        Korean1 K1 = new Korean("박자바", "011225-1234567");	// 이 값들이 name필드와 ssn필드의 초기값으로 사용됨
        
        System.out.println("K1.name : " + K1.name);
        System.out.println("K1.ssn : " + K1.ssn);
    }
}
  • 필드

    • String nation = "대한민국"처럼 따로 설정하지 않는 한, 기본 초기값으로 자동 설정된다
  • 생성자

    • 역할 : 필드값을 초기화
    • 객체 생성 시점에 외부에서 제공되는 값들로 초기화되어야 하는 경우, 생성자의 매개값으로 초기화하는 것이 좋음

5. 생성자 오버로딩

1) 정의

매개 변수를 달리하는 생성자를 여러 개 선언하는 것

참고: 오버로딩, 오버라이딩

2) 사용법

예시 1

생성자 오버로딩 (O)

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

생성자 오버로딩 (X)

public class Car {
    Car() {...}
    Car(String model,String color) {...}
    Car(String color, String model) {...}		// 오버로딩 (X) : 매개변수의 선언 순서가 다르다
}
  • 매개 변수 타입, 매개 변수 개수, 매개 변수 선언된 순서가 똑같을 경우

예시 2

public class Car {

    // 필드
    String company = "현대자동차";
    String model;
    String color;
    int maxSpeed;

    // 생성자 1
    Car() {
    }

    // 생성자 2
    Car(String model) {
        this.model = model;
    }

    // 생성자 3
    Car(String model, String color) {
        this.model = model;
        this.color = color;
    }

    // 생성자 4
    Car(String model, String color, int maxSpeed) {
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}
public class CarExample {
    public static void main(String[] args) {
    
        // 생성자 1 선택
        Car car1 = new car();
        System.out.println("car1.company :" + car1.company);
        System.out.println();

        // 생성자 2 선택
        Car car2 = new car("자가용");
        System.out.println("car2.company :" + car2.company);
        System.out.println("car2.model :" + car2.model);
        System.out.println();
        
        // 생성자 3 선택
        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();

        // 생성자 4 선택
        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);
        System.out.println();
    }
}

예시 3 : this()

public class Car {

    // 필드
    String company = "현대자동차";
    String model;
    String color;
    int maxSpeed;

    // 생성자 1
    Car() {
    }

    // 생성자 2 : 마지막 생성자인 생성자 4에서 Car(String model, String color, int maxSpeed)를 호출
    Car(String model) {
        this(model, "은색", 250);
    }

    // 생성자 3 : 마지막 생성자인 생성자 4에서 Car(String model, String color, int maxSpeed)를 호출
    Car(String model, String color) {
        this(model, color, 250);
    }

    // 생성자 4 : 공통 실행 코드들
    Car(String model, String color, int maxSpeed) {
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}

생성자 오버로딩이 多 경우, 생성자 간의 중복된 코드 발생 위험

해결책
나머지 생성자는 초기화 내용을 가지고 있고, 한 생성자에게만 this( ) 를 이용하여 집중적으로 작성

6. this(), this

this()

1) 정의

// 기존 코드
Car 2() {
	color = "white";
	gearType = "auto";
    door = 4;
}

// 간략히 하면
Car 2() {
	this("white", "auto", 4);
}
  • 생성자 에서 다른 생성자를 호출할 때 사용

    • 생성자 이름으로, 클래스이름 대신 this를 사용
  • this() 를 사용하지 않고 클래스명(); 이렇게 생성자를 호출하려고 하면, Error 가 발생

  • 다른 생성자 호출시 '첫 줄'에서만 사용 가능

2) 예시

Car (String color) {
	door = 5;
	Car(color, "auto", 4);		// 에러
}
  • 에러 발생 이유?
    • 생성자 호출이 첫 줄이 아니다.
    • 클래스 이름 car 이 아닌, this 로 호출해야 한다
// 올바르게 수정한다면
Car (String color) {
	tihs(color, "auto", 4);
    door = 5;
}

this

1) 정의

  • 인스턴스 자신을 가리키는 참조변수

  • this 는 인스턴스 멤버만 사용 가능

    • 인스턴스 메서드(생성자 포함) 에서 사용 가능
    • static 메서드의 경우, 인스턴스 생성없이도 호출 가능하므로 this를 사용 X
    • 따라서, 클래스 내에서는 인스턴스 멤버에서만 사용 가능
  • 지역 변수 와 인스턴스 변수를 구별할 때 사용

    • 만약, this.color = color 대신 color = color 로 사용하면 둘 다 지역 변수로 간주되어버린다.

2) 예시

class Tv10_1 {
    // 속성 : 변수 선언
    boolean power; // 전원상태
    int channel;  // 채널
    String color; // 색깔
    long price; // 가격

    // 위 속성에서 필수로 초기값이 필요한 값들을 초기화 해주는 기본 생성자
    public Tv10_1() {
        this.power = false;
        this.channel = 1;
    }

    //  오버로딩 한 생성자 - 매장 진열 용 일 경우에는 가격과 색깔의 초기화가 필요합니다.
    public Tv10_1(String color, long price) {
        this.power = false;
        this.channel = 1;
        this.color = color;
        this.price = price;
    }

    // 기능 : 메서드 선언
    void power() {  // 전원 기능
        this.power = !power;
        if (this.power) {
            System.out.println("전원 ON");
        } else {
            System.out.println("전원 OFF");
        }
    }

    void channelUp() { // 채널 증가
        this.channel++;
        System.out.println("채널 증가");
    }

    void channelDown() { // 채널 감소
        this.channel--;
        System.out.println("채널 감소");
    }

    // 색깔을 수정하고 자기 자신을 반환하는 메서드
    Tv10_1 changeColor(String color) { // 반환 타입으로 자기자신인 Tv 선언
        this.color = color;
        System.out.println("색깔 변경 완료!");
        return this; // this 는 자기 자신을 가리키는 참조변수!
    }

}

class Tv10_1Main {
    public static void main(String[] args) {
        // 기본 초기화된 Tv10_1 생성
        Tv10_1 tv = new Tv10_1();
        System.out.print("기본 생성자 Tv10_1: ");
        tv.power();

        // 진열 용 Tv10_1 생성
        Tv10_1 exTv = new Tv10_1("보라색", 3456789);
        System.out.print("오버로딩 생성자 Tv10_1: ");
        exTv.power();
        System.out.println("exTv.color = " + exTv.color);
        System.out.println("exTv.price = " + exTv.price);
        System.out.println();

        // 진열 용 Tv10_1 의 색깔을 수정하고 수정된 객체를 참조변수에 저장하겠습니다.
        Tv10_1 exTvThis = exTv.changeColor("파란색");
        // 색깔이 변경된 Tv의 주소가 저장된 참조변수 exTvThis 를 사용하여 변경된 색깔 확인
        System.out.println("색깔이 변경되었는지 확인 exTvThis.color : " + exTvThis.color);
        // 당연히 exTv 이걸로 확인해도 색깔이 변경되어 있습니다.
        System.out.println("exTv.color = " + exTv.color);
    }
}

7. 어노테이션

1) @NoArgsConstructor

(1) 정의

파라미터(인수)가 없는 기본 생성자를 만들어준다.

(2) 사용법

@NoArgsConstructor
public class Order {
    private String food;
    private int price;
    private String makers;

    /*@NoArgsConstructor 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
    public Order() {
    }
    */    
}

@NoArgsConstructor
public class MenuRequest {

	// @NotNull : NotNull을 지정한 id에 대해 null 값인지 체크
	@NotNull
	private int id;
	
	private String menuName;
	
	private String partName;
}

만약, @NoArgsConstructor 를 아래처럼 바꾼다면

@NoArgsConstructor(force=true)

객체 내의 모든 변수가 초기값으로 설정되지 않아 컴파일 에러가 발생
→ force의 기본 값은 false 이므로, @NoArgsConstructor 만 사용하면 변수가 초기값으로 자동 설정된다.

2) @AllArgsConstructor

(1) 정의

모든 필드값을 파라미터(인수)로 받는 생성자를 만들어준다.

주의!
이 어노테이션을 사용하기보다는
build 패턴 or 정적 팩토리 메소드를 사용하는 것이 코드 가동성에 더 좋다.

참고: 정적 팩토리 메서드

(2) 사용법

@AllArgsConstructor
public class Order {
    private String food;
    private int price;
    private String makers;

    /*@AllArgsConstructor를 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
    public Order(String food, int price, String makers) {
        this.food = food;
        this.price = price;
        this.makers = makers;
    }
    */
   
    }

@AllArgsConstructor
@Getter
public class MenuRequest {
	@NotNull
	private int id;
	
	private String menuName;
	
	private String partName;
}
// @AllArgsConstructor 로 인해, 모든 필드가 있는 생성자가 생성됨
MenuRequest menu = new MenuRequest(id, menuName, partName);

3) @RequiredArgsConstructor

(1) 정의

  • 특정 변수만을 활용하는 생성자를 자동완성
  • 생성자의 인자로 추가할 변수(필드 앞)에 final 또는 @NotNull 을 붙이면, 생성자를 자동 생성

(2) 사용법

@RequireArgsConstructor
public class Order {
    @NotNull
    private String food;
    private final int price;
    private String makers;

    /*@RequireArgsConstructor 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
    public Order(String food, int price) {
        this.food = food;
        this.price = price;
    }
    */
    
}

참고: DI, IoC, Bean

4) @Data

참고: 캡슐화, 접근 제한자

5) @Builder

참고: 디자인 패턴 - (2) 생성 패턴 - 2) 빌더 (Builder) - (3) 빌더 패턴

8. 어노테이션의 문제점

1) @AllArgsConstructor, @RequiredArgsConstructor

  1. Order 클래스에는 자동으로 cancelPrice, orderPrice 순서로 인자를 받는 생성자가 만들어진다.

    @AllArgsConstructor
    public static class Order {
        private long cancelPrice;
        private long orderPrice;
    }
    
    // 취소금액 5,000원, 주문금액 10,000원
    Order order = new Order(5000L, 10000L); 
  2. 이 때 만약, 이렇게 두 인자의 위치를 바꾼다면?

    @AllArgsConstructor
    public static class Order {
        private long orderPrice;
        private long cancelPrice;
    }

    두 필드는 동일한 Type 이라서, 기존 생성자 호출 코드에서는 인자 순서를 변경하지 않았음에도 어떠한 오류도 발생하지 않는다.

  3. 그러나 실제로는 입력된 값이 바뀌어 들어가게 된다.

    // 주문금액 5,000원, 취소금액 10,000원. 취소금액이 주문금액보다 많아짐!
    Order order = new Order(5000L, 10000L); 	// 인자값의 순서 변경 없음

해결책
@Builder 사용을 권장한다.

public static class Order {
    private long cancelPrice;
    private long orderPrice;

    @Builder
    private Order(long cancelPrice, long orderPrice) {
        this.cancelPrice = cancelPrice;
        this.orderPrice = orderPrice;
    }
}

// 필드 순서를 변경해도 문제 없음.
Order order = Order.builder().cancelPrice(5000L).orderPrice(10000L).build();
System.out.println(order);

@Builder 는 파라미터 순서가 아닌 이름으로 값을 설정하므로, 리팩토링에 유연하게 대응할 수 있다.

2) @Builder

@Builder 는 기본적으로 @AllArgsConstructor 를 내포하고 있다.

생성자를 package private 으로 만들기 때문에, 외부에서 생성자를 호출하는 일은 쉽게 안 생긴다.
하지만, 해당 클래스의 다른 메소드에서 자동으로 생성된 생성자를 사용할 경우에는 문제 소지가 있다.

해결책
1. @Builder 는 가급적 클래스보다는 직접 만든 생성자 혹은 static 객체 생성 메소드에 붙이는 것을 권장한다.

  1. @Builder 에 명확하게 어떤 역할을 하는 빌더인지를 메소드 이름으로 표현(클래스와 메소드 이름을 지정하는 기능으로) 해주는 것이 좋다.

3) @EqualsAndHashCode

@EqualsAndHashCode 은 equals(), hashCode() 를 자동 생성한다.

  • equals() : 두 객체의 내용이 같은지 동등성을 비교
  • hashCode() : 두 객체가 같은 객체인지 동일성을 비교

두 메소드를 변경가능한(Mutable) 객체에 파라미터 없이 사용할 경우, 문제가 발생할 수 있다.

@EqualAndHashCode
@AllArgsConstructor
@Setter
public static class Sample {
	private Long sampleId;
	private String sampleName;
}

Sample sample = new Sample(lL, "sample_one");

Set<Sample> samples = new HashSet<>();
samples.add(sample); 		// set 에 객체 sample 를 추가

// 실험 1 : set 에 저장 후, 단순 출력
samples.contains(order); 	// 출력 결과 : true

// 실험 2 : set 에 저장 + 필드값 sample1 로 변경 후, 출력
sample.setSampleName("sample1");
samples.contains(sample); 		// 출력 결과 : false

실험 1 과 실험 2 는 동일한 객체 sample 임에도
필드값을 변경해버리면(실험 2), hashCode 가 변경되면서 찾을 수가 없게 된다.

4) @Data

@Data 는 @getter + @setter + @RequiredArgsConstructor + @toString + @EqualsAndHashCode 를 한번에 설정해준다.
따라서 @RequiredArgsConstructor, @EqualsAndHashCode 의 사용을 지양하기 때문에(윗글 참고), 마찬가지로 @Data 의 사용도 권장되지 않는다.


용어

  • 객체 초기화
    - 필드를 초기화 or 메소드를 초루해서 객체를 사용할 준비를 하는 것
    - new 연산자에 의해 생성자가 실행 --> 힙 영역에 객체가 생성됨 --> 객체의 번지가 리턴됨 --> 리턴된 번지는 클래스 변수에 저장됨
  • 컴파일: 컴파일이란 어떤 언어의 코드 전체를 다른 언어로 바꿔주는 과정
  • 컴파일러: 컴파일을 자동으로 수행해주는 소프트웨어 (컴퓨터에서 즉시 실행될 수 있는 형태로 바꿔주는 번역 프로그램)

참고: @AllArgsConstructor , @NoArgsConstructor의 의미
참고: [ Lombok ] 생성자 어노테이션
참고: [Spring]생성자와 의존성 주입
참고: Lombok 사용상 주의점(Pitfall)
참고: 자주 사용되는 lombok, 주의 사항

profile
개발자로 거듭나기!

0개의 댓글