ch1.객체의 생성과 파괴

item1. 생성자 대신 정적 팩터리 메서드를 고려하라

정적 팩터리 메서드의 장점

  1. 이름을 가질 수 있다.
  2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  3. 반환 타입의 하위 타입의 객체를 반환 할 수 있다.
  4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환 할 수 있다.
  5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

이름을 가질 수 있다.

  • 이름을 다르게 해도 메소드 시그니처가 완벽히 같아서 생성자 2개를 만드는 것이 불가능 하지만 정적 팩터리 메서드를 사용하면 가능합니다.
  • 이름을 가질 수 있다는 건 메소드 매개변수의 시그니처가 동일해도 메소드를 정의할 수 있습니다.
    public class Order {
    
      private boolean prime;
      private boolean urgent;
      private Product product;
    
      public Order(Product product, boolean prime) {
          this.product = product;
          this.prime = prime;
      }
    
      public Order(Product product, boolean urgent) {
          this.product = product;
          this.urgent = urgent;
      }
    }
    

    ```java public class Order {

    private boolean prime; private boolean urgent; private Product product;

    public static Order primeOrder(Product product){ Order order = new Order(); order.prime = true; order.product = product; return order; }

    public static Order urgentOrder(Product product){ Order order = new Order(); order.urgent = true; order.product = product; return order; }

}


-----

### 인스턴스 통제 클래스를 만들 수 있습니다. 
- 반복되는 요청에도 같은 객체를 반환해서 이를 **인스턴스 통제 클래스**라 합니다.
- 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  * **Boolean 클래스의 valueOf** 메소드입니다.
  * 매개변수에 따라 미리 생성된 인스턴스를 반환합니다. 
- **생성 비용이 큰 객체가 자주 요청**되는 상황에선 성능 향상을 노릴 수 있습니다.
  * 객체를 캐싱, 재사용 하는 패턴인 **flyweight pattern**의 근간이 됩니다. 
- non instantiable class(인스턴스화 불가능 클래스) 를 만들 수 있습니다.
- 대표적인 non instantiable class로는 유틸리티성 클래스가 있습니다. 보통 class에 `final`을 붙여 만듭니다. 
```java
//미리 생성된 인스턴스
public static final Boolean TRUE = new Boolean(true);

//정적 팩토리 메소드
@IntrinsicCandidate
public static Boolean valueOf(boolean b) {
  return (b ? TRUE : FALSE);
}

반환 타입의 하위 타입 객체를 반환할 수 있다.

  • 구현 클래스 공개하지 않고도 그 객체를 반환할 수 있습니다.
  • java 8 이후 아래처럼 인터페이스에 정적 팩토리 메서드 선언이 가능해져서 인터페이스의 인스턴스를 대신 생성 해주는 동반 클래스(companion class)가 없어도 됩니다.
  • java 9 이후엔 아래처럼 private이 가능해졌습니다.
    public interface HelloService {
      String hello();
        
      static private void prepare(){
          // blah blah
      }
    
      static HelloService of(String lang){
          if(lang.equals("kr")){
              return new HelloKorean();
            }else{
              return new HelloAmerican();
          }
      }
    }
    

정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

  • 인터페이스만 있고 인터페이스 구현체는 존재하지 않아도 된다. (정적 팩토리 메소드가 있는 상태에서..)
  • 아래와 같이 서비스 프로바이더 프레임워크를 이용해 호출 할 수 있습니다.
    public interface HelloService {
      String hello();
    }
    

    ```java public class HelloServiceFactory {

    public static void main(String[] args) { ServiceLoader loader = ServiceLoader.load(HelloService.class); Optional helloServiceOptional = loader.findFirst(); helloServiceOptional.ifPresent(helloService -> { System.out.println(helloService.hello()); }); }

}


#### 🤔 왜 서비스 프로바이더 프레임워크를 사용할까?
- 아래처럼 **HelloChina(구체적인 구현체)** 를 직접 호출하면 되지 굳이 왜 **ServiceLoader**와 같은 서비스 프로바이더 프레임워크니 하는걸까?
  * ServiceLoader를 사용하면 구체적인 구현체 의존적이지 않습니다. 
  * 어떤 HelloService 구현체가 올지 모르기 때문에 굉장히 유연한 코드를 짤 수 있습니다.
- **JDBC** 가 대표적인 서비스 프로바이더 프레임워크입니다!
```java
public class HelloServiceFactory {

    public static void main(String[] args) {
        HelloService helloService = new HelloChina();
        System.out.println(helloService.hello());
    }

}

정적 팩토리 메소드의 단점

  1. 상속을 할 수 없다.
  2. 프로그래머가 찾기 어렵다.

상속을 할 수 없다.

  • 상속보다 컴포지션 사용을 하게 되므로 다르게 보면 장점이다.

프로그래머가 찾기 어렵다.

  • 메소드이기 때문에 생성자와 다르게 눈에 잘 띄지 않음.
  • 정적 팩터리 메서드에서 흔히 사용하는 명명 방식들을 사용하면 찾기 쉬워지는 편이다.
    • from : 매개변수 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
      • Date date = Date.from(instance)
    • of : 매개변수 다수 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
      • Set<Rank> faceCards = EnumSet.of(Jack, Queen, King)
    • valueOf : from 과 of 보다 조금 더 자세한 버전입니다.
      • BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE)
    • instance (getInstance) : 매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하지 않습니다.
      • StackWalker luke = StackWalker.getInstance(instance)
    • create (newInstance) : 매번 새로운 인스턴스를 생성합니다.
      • Object newArray = Array.newInstance(classObject, arrayLen)
    • get(Type) : instance와 같지만 반환 받길 원하는 클래스와 정적 팩터리 메소드가 정의된 클래스가 다릅니다.
      • FileStore fileStore = File.getFileStore(instance)
    • new(Type) : create와 같지만 반환 받길 원하는 클래스와 정적 팩터리 메소드가 정의된 클래스가 다릅니다.
      • BufferdRedader br = Files.newBufferdReader(instance)
    • type : get(Type), new(Type)의 간단한 명명법
      • List<Object> list = Collections.list(objectList)

댓글남기기