본문 바로가기
독서 정산

[독서정산] Effective Java 2/E - 1부. 객체의 생성과 삭제

by kdohyeon (김대니) 2023. 3. 6.
반응형
Effective Java 2/E

규칙 1. 생성자 대신 정적 팩토리 메소드를 고려해보자

정적 메소드에는 이름이 존재한다.

  • 정적 팩토리 메소드에는 이름이 존재하기 때문에 생성자보다 반환될 객체의 특성을 더 잘 설명할 수 있다. 생산성을 높여주고 가독성을 높인다는 부분에서 공감한다. 대신 메소드의 이름을 잘 작명해야 하겠지?
  • 아래 예시에서 reviewA 와 reviewC 는 단순히 생성자만을 활용했다. 파라미터의 개수가 작아서 관리하기 쉬울 수 있지만 파라미터 개수가 많아진다면?
  • reviewB 와 reviewD 가 생성된 것처럼 정적 메소드를 활용하면 좀 더 이해하기 쉽게 표현할 수 있다.
public static void main(String[] args) {
    Review reviewA = new Review("배송 굳", 5); // 생성자 기반
    Review reviewB = Review.createReviewWithScore("배송 굳굳", 5); // 정적 메소드 기반
    
    Review reviewC = new Review("배송 굳"); // 생성자 기반
    Review reviewD = Review.createReviewWithZeroScore("배송 굳굳"); // 정적 메소드 기반
}

public class Review {
    private final String reviewContent; // "배송이 빨라서 좋았아요"
    private final int score; // 5점
    private final LocalDateTime createdAt; // 2022-01-01 12:12:12
    
    public Review(String reviewContent, int score) {
        this.reviewContent = reviewContent;
        this.score = score;
        this.createdAt = LocalDateTime.now();
    }
    
    public Review(String reviewContent) {
        this.reviewContent = reviewContent;
        this.score = 0;
        this.createdAt = LocalDateTime.now();
    }
    
    public static Review createReviewWithScore(String reviewContent, int score) {
        return new Review(reviewContent, score);
    }
    
    public static Review createReviewWithZeroScore(String reviewContent) {
        return new Review(reviewContent);
    }
}
나는 아래처럼 빈 객체를 만들기 위해 정적 메소드를 많이 사용한다.
public class Review {
    ...
    
    @Builder
    public Review() {
        ....
    }
    
    public static Review empty() {
        return Review.builder().build();
    }    
}

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

  • 변경 불가능 클래스 또는 캐시를 두고 재사용하면 매번 새로운 객체를 만들 필요가 없다.
  • 객체를 만드는 비용이 클 때, 효과적일 수 있다.
public class Review {
    private static final Review EMPTY = new Review("Empty");
    
    public Review(String content) {
        this.content = content;
        ...
    }
    
    public static Review empty() {
        return EMPTY;
    }
}

public statis void main(String[] args) {
    Review emptyReview = Review.empty(); 
}

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

  • 반환되는 객체의 클래스를 유연하게 설정할 수 있다.
  • public 으로 선언되지 않은 클래스의 객체를 반환할 수 있다.
  • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관이 없다.

형인자 자료형 (parameterized type) 객체를 만들 때 편리하다

// 형인자를 연달아 두 번 사용하게 되므로 복잡한 코드가 작성됨
Map<String, List<String>> m = new HashMap<String, List<String>>();

// HashMap 클래스가 제공하는 newInstance() 제네릭 정적 팩토리 메소드
public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}

// 좀 더 간결하게 작성할 수 있음
Map<String, List<String>> m = HashMap.newInstance();

규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용을 고려해보자

  • 점측정 생성자 패턴은 잘 동작하지만 인자 수가 늘어나면 클라이언트 코드를 작성하기 어려워지고, 무엇보다 읽기 어려운 코드가 되고 만다.
  • 빌더 패턴은 인자가 많은 생성자나 정적 팩토리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용하다.
Lombok 라이브러리에서 제공하는 @Builder 어노테이션을 활용하면 쉽게 빌더 패턴을 적용할 수 있다.
public class A {
  ...
  
  @Builder // <-- 빌더 어노테이션
  public A() {
  
  }
}

규칙 3. private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하자

  • 싱글턴은 객체를 하나만 만들 수 있는 클래스인데, 싱클턴 클래스는 클라이언트 테스트를 하기 어려워진다.
  • 원소가 하나인 enum 자료형을 통해 싱글턴을 구현할 수 있다.

규칙 4. 객체 생성을 막을 때는 private 생성자를 사용하자

  • 정적 메소드나 필드만 모아둔 클래스가 필요한 경우에는 private 생성자를 클래스에 넣어 객체 생성을 방지할 수 있다.
public class AClass {
    private AClass() {
        throw new AssertionError();
    }
    ...
}

규칙 5. 불필요한 객체는 만들지 말자

  • 아래처럼 매번 호출될 때마다 새로 생성할 필요가 없는 객체들은 재사용할 수 있도록 구현해야 한다.
String s = new String("string"); // x
Strng s = "string"; // o

규칙 6. 유효기간이 지난 객체 참조는 폐기하자

  • Garbage collector 가 있지만 필요없는 만기 참조는 null 을 할당하여 제거할 수 있다.
  • 하지만 매번 객체 사용이 끝나면 그 참조를 null 처리를 할 필요가 없다. 프로그램만 난잡해진다.
  • 규범이라기 보다는 예외적인 조치가 되어야 한다.

규칙 7. 종료자 사용을 피하자

  • 보통 종료자는 더 이상 참조되지 않는 객체에 할당된 공간을 garbage collector 가 정리하는 곳으로 사용된다 (자바의 try-finally)
  • 종료자는 반드시 실행안될 수도 있기 때문에 중요 상태 정보는 종료자로 갱신하면 안된다.
  • 종료자를 사용하면 프로그램 성능이 심각하게 떨어진다.
  • 그래도 사용한다면, 종료 메소드 호출을 잊을 경우에 대비하는 안전망으로서의 역할을 수행할 수 있다. (그리고 항상 로그를 남겨두자)
  •  
반응형

댓글