코딩못하는사람

[이펙티브 자바]객체 생성과 파괴 본문

자바 메모장/이펙티브 자바

[이펙티브 자바]객체 생성과 파괴

공부절대안함 2021. 6. 20. 04:44

[아이템1] 생성자 대신 정적 팩터리 메서드를 고려하자

클라이언트가 클래스의 인스턴스를 얻기 위해서는 보통 public 생성자를 사용했다. 하지만 다른 방법으로 public static 팩토리 메소드를 사용해서 해당 클래스의 인스턴스를 만드는 방법도 있다.

public static 팩토리 메소드의 장점

1. 이름을 가질 수 있다.

BigInteger.probblePrime를 보자. 기존 생성자로 BigInteger()을 사용하는 것 보다 BigInteger.probblePrime()를 사용하면 만드려는 인스턴스 객체의 특성을 더 알아보기 쉽게 묘사할 수 있다.

 

2.호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.

불변(immutable) 클래스인 경우나 매번 새로운 객체를 만들 필요가 없는 경우에 미리 만들어둔 인스턴스 또는 캐시해둔 인스턴스를 반환할 수 있다

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
} 

매번 새로운 Boolean객체가 아니라 상수를 리턴한다.

 

3.리턴 타입의 하위 타입 인스턴스를 만들 수도 있다.

API를 만들 때 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다고 한다. 다시 얘기하면, 리턴 타입은 인터페이스로 지정하고 그 인터페이스의 구현체는 API로 노출 시키지 않지만 그 구현체의 인스턴스를 만들어 줄 수 있다는 말이다.(전체 노출이 아닌 필요한 부분만 부분적으로)

예로는 java.util.Collections가 해당한다. Collections는 45개의 인터페이스 구현체를 제공하는데 대부분을 인스턴스화 불가 클래스인 java.util.Collections에서 정적 팩터리 메서드를 통해 얻도록 했다.

 

구현체들은 전부 non-public이다. 즉 인터페이스 뒤에 감쳐줘 있고 그럼으로서 public으로 제공해야 할 API를 줄였을 뿐 아니라 개념적인 무게(conceptual weight)까지 줄일 수 있었다.(밖으로 노출되있는 것만 사용하기 때문에)
개념적인 무게- 프로그래머가 어떤 인터페이스가 제공하는 API를 사용할 때 알아야 할 개념의 개수와 난이도.

반환할 객체의 클래스를 자유롭게 선택할 수 있게 함으로써 유연성을 제공한다.

 

4.리턴하는 객체의 클래스가 입력 매개변수에 따라 매번 다를 수 있다.

3번에 따라서 매개변수에 따라서 다를 수 있다. 책에서는 EnumSet을 예로 들었다.

EnumSet의 원소가 64개 이하면 RegularEnumSet,65개 이상이면 JumboEnumSet을 반환한다.

클라이언트는 두개의 존재를 모르지만 그냥 팩터리가 주는 객체를 사용하면 된다. EnumSet의 하위클래스 이기만 하면 된다. 매개변수에 따라서 성능을 개선해서 효율적인 클래스를 반환해주게 하자.

 

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

유연성을 제공하는 static 팩토리 메소드는 서비스 프로바이더 프레임워크의 근본이다. JDBC를 예로 들고 있다.

JDBC의 경우, DriverManager.registerDriver()가 프로바이더 등록 API. DriverManager.getConnection()이 서비스 엑세스 API. 그리고 Driver가 서비스 프로바이더 인터페이스 역할을 한다.
클라이언트가 getConnection()을 했을 때 반환타입으로 mysql,orcale,h2 등 여러가지 드라이버가 있을 수 있다. 서비스 프로바이더에 의해 의존성 객체를 공급받는다고 이해된다.

 

단점

  1. 상속을 위해서는 public이나 protected 생성자가 필요하니 정적 팩터리 메소드만 있다면 하위클래스를 만들 수 없다.
  2. 생성자는 Javadoc 상단에 모아서 보여주지만 static 팩토리 메소드는 API 문서에서 특별히 다뤄주지 않는다. 따라서 클래스나 인터페이스 문서 상단에 팩토리 메소드에 대한 문서를 제공하는 것이 좋겠다.

핵심- 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하자.

 

[아이템2] 생성자에 매개변수가 많다면 빌더를 고려하자.

static 팩토리 메소드와 public 생성자 모두 매개변수가 많이 필요한 경우에 불편해진다.

책에서 사용한 NutritionFacts 예제 클래스를 사용하겠다.

 

해결책1 기본 생성자 사용

NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);

다음과 같은 코드는 이해하기 어렵고 작성하기도 어렵다. 불필요한 기본값을 넘기기도 한다.

 

해결책2 자바 빈 사용

사용아무런 매개변수를 받지 않는 생성자를 사용해서 인스턴스를 만들고, setter를 사용해서 필요한 필드만 설정할 수 있다.

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

이 방식의 단점은 여러번의 set호출을 거치기 때문에 자바빈이 중간에 사용되는 경우 안정적인 상태가 아닐 수 있다.

또한 getter,setter가 있으므로 불변 클래스로 만들지 못하며 쓰레들의 안정성을 보장하지 못한다.

 

해결책3 Builder 사용

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();

필수적인 파라미터를 생성자에 주고 옵셔널한 것들도 세팅을 한번에 가능하다. 롬복을 통하여 쉽게 사용할 수 있다.@Builder

 

핵심-생성자나 정적 팩터리가 처리해야할 매개변수가 많다면 빌더패턴을 선택하는게 더 낫다.

 

[아이템3]-private 생성자나 열거 타입으로 싱글턴임을 보증하자

싱글톤을 사용하는 클라이언트 코드를 테스트 하는게 어렵다. 싱글톤이 인터페이스를 구현한게 아니라면 mock으로 교체하는게 어렵기 때문이다.(?????????????)

1.fianl 필드 사용

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

}

생성자는 오직 최초 한번만 호출되고 Elvis는 싱글톤이 된다(리플렉션을 사용해서 private 생성자를 호출하는 방법이 있어서 예외를 던지게 해야 한다고 한다).

2.static 팩토리 메서드

public class Elvis {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }

}

API를 변경하지 않고로 싱글톤으로 쓸지 안쓸지 변경할 수 있다. 처음엔 싱글톤으로 쓰다가 나중엔 쓰레드당 새 인스턴스를 만든다는 등 클라이언트 코드를 고치지 않고도 변경할 수 있다.

 

위 두 방법 모두 역직렬화에서 같은 타입의 인스턴스가 여러개 생길 수 있어서 모든 인스턴스 필드에 transient를 선언하고 readResolve 메서드를 제공하라고 한다.

//싱글턴임을 보장해줌
private Object readResolve() {
        return INSTANCE;
    }

3.Enum 사용

public enum Elvis {
    INSTANCE;
}

직렬화/역직렬화 할 때 코딩으로 문제를 해결할 필요도 없고, 리플렉션으로 호출되는 문제도 고민할 필요없는 방법이 이다. 상위 클래스를 사용한다면 상속 불가능하지만 인터페이스는 가능하다.

 

하지만 Enum 방식은 이상적인 싱글턴 방식이고 현실적으로는 스프링의 싱글턴을 사용한다.

여러 어노테이션을 붙혀 Spring application context에  Bean으로 등록하여 싱글턴을 사용하는 방식을 쓰게 된다.

 

 

[아이템4]인스턴스화를 막으려면 private 생성자를 사용하라

static 메서드와 static 필드를 모아둔 클래스를 만든 경우 해당 클래스를 abstract로 만들어도 인스턴스를 만드는 걸 막을 순 없다. 상속 받아서 인스턴스를 만들 수 있기 때문이다. 따라서 private 생성자를 달아서 막아주어야 한다.

// Noninstantiable utility class
public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
}

스프링 utility class들은 abstract를 사용하여 인스턴스 생성을 막았다. 상속을 받더라도 인스턴스를 사용할 수 없었다.

유틸리티 클래스

  • 인스턴스 메서드와 인스터스 변수를 일절 제공하지 않고, 정적 메서드와 변수만을 제공하는 클래스를 뜻한다.
  • 클래스 본래의 목적인 '데이터와 데이터 처리를 위한 로직의 캡슐화'를 실행하는 것이 아닌,
    '비슷한 기능의 메서드와 상수를 모아서 캡슐화'한 것이 유틸리티 클래스이다.

https://morningcoding.tistory.com/

예시-Java util ,Spring framework util

 

[아이템5]리소스를 엮을 때는 의존성 주입을 선호하라

 

어떤 클래스가 사용하는 리소스에 따라 행동을 달리 해야 하는 경우에는 스태틱 유틸리티 클래스와 싱글톤을 사용하는 것은 부적절하다. (ex-Repository)

생성자,setter,필드,spring을 사용한 의존성 주입을 사용하는 것이 효율적이다.

 

 

 

출처 및 공부한 곳

이펙티브 자바(3판)을 읽고 백기선님의 강의를 듣고 기록하였습니다.

https://github.com/keesun/study/blob/master/effective-java/Readme.md

Comments