Java/이펙티브 자바(Effective Java)

[이펙티브 자바(Effective Java)][객체 생성과 파괴] - 1. 생성자 대신 정적 팩터리 메서드를 고려해라

얄루몬 2022. 4. 4. 17:32

📖본 포스팅은 'Effective java - 조슈아' 님의 책를 보고 작성되었습니다.


[객체 생성과 파괴]

객체 생성과 파괴 파트(part)에서 배우는 것들...

  • 객체를 만들 때와 만들지 말아야 할 때를 구분
  • 올바른 객체 생성과 불필요한 생성 피하는 방법
  • 객체를 파괴할 때 올바르게 파괴하는 방법

[생성자 대신 정적 팩토리 메서드를 고려해라]

[인스턴스 변수의 초기화]

클래스를 가지고 객체를 생성하면, 해당 객체는 메모리에 즉시 생성된다. 

그러나 이렇게 생성된 객체는 모든 인스턴스 변수가 아직 초기화되지 않은 상태기 때문에 자바에서는 클래스 변수와 인스턴스 변수는 별도로 초기화하지 않으면, 설정되어 있는 기본값으로 자동 초기화 된다.

 

그러나 사용자가 원하는 값으로 인스턴스 변수를 초기화하려면, 일반적인 초기화 방식으로는 초기화할 수 없다.

인스턴스 변수 중에는 private 변수도 있는데 private 변수의 경우 사용자나 프로그램이 직접적으로 접근할 수 없다. 

따라서 private 변수에 접근하기 위해서는 초기화만을 위한 public 메소드가 필요하게 된다. 이런 초기화를 위한 메소드는 객체 생성 후에 사용되기 전까지 반드시 인스턴스 변수의 초기화를 위해 호출되어야 한다.

📌출처: http://www.tcpschool.com/java/java_methodConstructor_constructor

요약

  • 클래스를 가지고 객체를 생성하면 해당 객체는 메모리에 즉시 생성되고, 이때 별도의 작업이 없다면 기본값으로 자동 초기화 해준다.
  • 그러나 사용자가 원하는 값으로 초기화하려할 때 접근 지정자가 private인 경우엔 사용자나 프로그램이 직접 접근할 수 없기 때문에 이를 위한 초기화 메소드가 필요한데 이 역할을 생성자가 하게 된다.

[생성자?]

자바에서는 객체의 생성과 동시에 인스턴스 변수를 원하는 값으로 초기화할 수 있는 생성자(constructor)라는 메소드를 제공한다. 이때 자바에서 생성자의 이름은 해당 클래스의 이름과 같아야 한다.

생성자의 특징

  1. 생성자는 반환값이 없지만 반환 타입을 void로 선언하지 않는다.
  2. 생성자는 초기화를 위한 데이터를 인수로 전달받을 수 있다.
  3. 객체를 초기화하는 방법이 여러 개 존재할 때는 하나의 클래스가 여러 개의 생성자를 가질 수 있다
    • 즉, 생성자도 하나의 메소드이기 때문에 메소드 오버로딩이 가능하다는 의미다.
    • 오버로딩이란? 같은 메소드 이름으로 반환값, 매개변수가 다르게 들어간 것을 의미한다. 그러나 생성자의 경우엔 반환타입이 명시되어 있지 않아야 하기때문에 매개변수가 다르게 들어간 것을 의미할 것이다.

생성자의 오버로딩의 예시

Car(String modelName) {}

Car(String modelName, int modelYear) {}

Car(String modelName, int modelYear, String color) {}

Car(String modelName, int modelYear, String color, int maxSpeeds) {}

생성자의 선언

클래스이름() { } //매개변수가 없는 생성자 선언
클래스이름(인수1, 인수2..., 인수n) { } //매개변수가 있는 생성자 선언

생성자 호출

자바에서는 new 키워드를 사용하여 객체를 생성할 때 자동으로 생성자가 호출된다. 

class Car{
	private String name;
    private int number;
    private String color;
    
    Car(String name, int number, String color){
    	this.name = name;
        this.number = number;
        this.color = color;
    }
}

[정적 팩토리 메서드?]

생성자를 통해서 객체를 생성하고 초기화 하는 것과 다르게 정적 팩토리 메서드는 생성자가 아닌 정적(static) 메서드를 통해서 객체를 생성하는 것을 정적 팩토리 메소드라고 한다.

  • 클래스의 인스턴스를 반환하는 단순한 정적 메서드
  • 객체 생성의 역할을 하는 클래스 메서드

정적 팩토리 메서드 네이밍 컨벤션

  • from
  • of
  • value of
  • instance 혹은 getInstance
  • creat 혹은 newInstance
  • getType
  • newType
  • type
    • 이는 getTypenewType의 간결한 버전임

[정적 팩토리 메소드의 장점]

생성자를 사용해서 객체를 생성하는 것보다 정적 팩토리 메소드를 사용해서 객체를 생성하는 일이 더 큰 효용이 있기 때문에 이를 권장하는 것일 것이다. 그렇다면 정적 팩토리 메소드의 장점은 무엇이 있을까?

  1. 이름을 가질 수 있다.
  2. 호출할 때마다 새로운 객체를 생성할 필요가 없다.
  3. 하위 자료형 객체를 반환할 수 있다.
  4. 객체 생성을 캡슐화할 수 있다.

 

1. 이름을 가질 수 있다.

생성자의 경우엔 클래스 이름과 동일하게 생성자를 만들어야 하기 때문에 이름을 가질 수 없다. 그러나 정적 팩토리 메소드의 경우엔 이름을 가질 수 있다. 이름을 가질 수 있다는 것의 장점은 무엇이 있을까?

public class LottoFactory() {
  private static final int LOTTO_SIZE = 6;
  
  private static List<LottoNumber> allLottoNumbers = ...; // 1~45까지의 로또 넘버
    
  public static Lotto createAutoLotto() {
    Collections.shuffle(allLottoNumbers);
    return new Lotto(allLottoNumbers.stream()
            .limit(LOTTO_SIZE)
            .collect(Collectors.toList()));
  }

  public static Lotto createManualLotto(List<LottoNumber> lottoNumbers) {
    return new Lotto(lottoNumbers);
  }
  ...
}

두 메서드 모두 로또 객체를 자동으로 생성하고 수동 생성하고 반환하는 정적 팩토리 메서드이다. 이 메서드는 이름만 보아도 로또 객체를 자동, 혹은 수동으로 생성하는지 이해가 되기 때문에 해당 생성의 목적을 이름에 표현할 수 있게 되어 가독성이 좋아지는 효과가 있다.

 

2. 호출할 때마다 새로운 객체를 생성할 필요가 없다.

정적 팩토리 메서드와 캐싱구조를 함께 사용하면 매번 새로운 객체를 생성할 필요가 없어지게 된다.

 

3. 하위 자료형 객체를 반환할 수 있다.

상속을 이용할 때 이 특징을 이용할 수 있다. 이는 생성자의 역할을 하는 정적 팩토리 메서드가 반환값을 가지고 있기 떄문에 가능한 특징이다.

시험 점수에 따라 결정되는 하위 등급 타입을 반환하는 정적 팩토리 메서드를 만들면, 다음과 같이 분기처리를 통해 하위 타입의 객체를 반환할 수 있다.

public class Level {
  ... 
  public static Level of(int score) {
    if (score < 50) {
      return new Basic();
    } else if (score < 80) {
      return new Intermediate();
    } else {
      return new Advanced();
    }
  }
  ...
}

 

4. 객체 생성을 캡슐화할 수 있다.

정적 팩토리 메서드는 객체 생성을 캡슐화하는 방법이기도 하다.

캡슐화? 
데이터의 은닉을 말한다. 여기서 생성자를 클래스의 메서드 안으로 숨겨 내부 상태를 외부에 드러낼 필요 없이 객체 생성 인터페이스를 단순화시키는 것을 말한다.

DTO와 Entity간에는 자유로운 형 변환이 가능해야 한다. 정적 팩토리 메서드의 경우엔 내부 구현을 모르더라도 쉽게 형 변환을 할 수 있도록 한다.

public class CarDto{

	private String name;
    private int position;
    
    public static CarDto from(Car car){ //정적 팩토리 메서드
    	return new CarDto(car.getName(), car.getPosition());
    }
}


//Car -> CarDto로 형변환
CarDto carDto = CarDto.from(car); //정적 팩토리 메서드를 사용해서 형변환을 한 경우

CarDto carDto = new CarDto(car.getName(), car.getPosition); // 생성자를 사용한 경우

객체간의 형변환이 필요하고 여러 번의 객체 생성이 필요한 경우라면 생성자보다는 정적 팩토리 메서드를 사용하는 것이 좋다.