이펙티브 자바 - 2장 객체 생성과 파괴

김민우·2022년 9월 15일
0

이펙티브 자바

목록 보기
1/2

2장 객체 생성과 파괴

아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라.
아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보장하라.
계속해서 매일 하루에 한개씩 정리할 예정


아이템 1. 정적 팩터리 메서드

  • 장점 1. 이름을 가질수 있다.
    Static Factory Method는 (클래스명과 동일해야하는 생성자와 달리) 고유의 이름을 가질 수 있다.

  • 장점 2. 호출할 때마다 인스턴스를 새로 생성하지 않아도 된다.
    불변 클래스의 경우 새로운 인스턴스를 생성하지 않고 기존 인스턴스를 캐싱 후 재활용 가능

  • 장점 3. 하위 클래스를 반환 가능
    Animal 클래스와 이를 상속하는 Dog, Hamster 클래스가 있다고 생각해보자. Animal() 생성자는 Animal만 리턴할 수 있지만 Static Factory Method를 사용하면 Dog이든 Hamster든 마음대로 리턴할 수 있다.

  • 장점 4. 함수가 하나의 하위 클래스만 반환 하지 않아도 된다.

  • 장점 5. 동적으로 반환할 객체를 로딩한다.

단점

  • 상속을 하려면 public, protected 생성자가 필요하다
    생성자 없이(혹은 private 생성자가 있고) Static Factory Method만 가지고 있는, 클래스는 상속이 불가능하다. 이후 item 17, 18에 추가 내용이 나오는데, 단점으로만 생각하기는 어렵다고 한다.

  • 프로그래머가 찾기 어렵다
    API 문서에 생성자는 발견하기 쉬운 반면 Static Factory Method는 다소 찾기 어려운 특성이 있다

[느낀 점]
프로젝트 할때 왜 정적 팩터리 메서드를 사용하지 의문이 들었는데 그 이유를 알 것 같다!


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

점층적 생성자 패턴

  • 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

자바빈즈 패턴

  • 매개변수가 없는 생성자를 호출해 인스턴스를 만들고, Setter 를 이용해 원하는 값을 할당해준다
    객체하나를 완성시키기 위해선 메서드를 여러 개 호출, 완전한 값을 세팅하기 전에는 일관성이 무너진다.

  • 클래스를 불변으로 만들 수 없으며, 스레드 안전성을 위해선 추가작업이 필요하다.

빌더 패턴

  • 필수 매개변수만으로 생성자를 호출해 빌더객체를 얻은 뒤, 빌더가 제공하는 일종의 Setter 메소드를 이용해 원하는 매개변수를 설정한다. 마지막으로 매개변수가 없는 build 메서드를 호출해 객체를 얻는다.

  • 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
    빌더는 보통 생성할 클래스 안에 정적 멤버클래스로 만들어둔다.

 /**
     * 메시지 정보를 받아 메시지를 등록합니다.
     * @param userRoom 방 번호
     * @param sender    메시지 전송한 유저 정보
     * @param target    메시지를 전달받을 유저 정보
     * @param data  메시지 정보
     * @return Letter 생성된 메시지
     */
    public Letter createMessage(UserRoom userRoom, User sender, User target, LetterSendData data) {
        Letter letter = letterRepository.save(
                Letter.builder()
                        .content(data.getContent())
                        .heart(data.getHeart())
                        .sender(sender)
                        .target(target)
                        .room(userRoom)
                        .build()
        );
        return letter;
    }

사이드 프로젝트에서 적용했던 빌더 패턴 예시

아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보장하라.

싱글턴이란? 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.

싱글턴을 만드는 방법 3가지
1. private 생성자와 public static 필드

  • public이나 protected 생성자가 없으므로 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 단 하나뿐임을 보장한다.
  1. private 생성자와 public static 정적 팩토리 메소드
    장점
  • API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다. getInstance() 메소드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있다.

  • 제네릭 싱글턴 팩토리로 만들 수 있다.

  • 정적 팩토리의 메소드 참조를 공급자(매개변수x, 리턴값o)로 사용할 수 있다. — Elly::getInstance를 Supplier 변경 가능

  1. 원소가 하나인 Enum 타입 선언
  • 더 간결하고 추가 노력 없이 직렬화할 수 있다.
    아주 복잡한 직렬화 상황이나, 리플렉션 공격에서도 제 2의 인스턴스 생성을 막아준다.
    따라서, 대부분의 상황에서는 이 방법이 싱글턴을 만드는 가장 좋은 방법!

아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 추상 클래스를 만들면 상속해서 쓰라는 뜻으로 오해가 가능하다. private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.

public class UtilityClass {
  // 기본 생성자가 만들어지는 것을 막는다.(인스턴스화 방지용)
  private UtilityClass(){
  }
}

이 코드는 어떤 환경에서도 클래스가 인스턴스화되는 것을 막아준다. 또한 상속을 불가능하게 한다. 왜냐하면 하위클래스에서는 무조건 상위클래스의 생성자를 호출하게 되는데 private으로 선언했으니 접근할 길이 막히기 때문이다.


아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.

이런 방식에는 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다.(의존 객체 주입)

아래 방식은 실제로 스프링 부트 사이드 프로젝트를 진행할때 사용한 컨트롤러의 코드이다.


@RestController
public class BookController{
  private final BookService bookservice;
  
  public BookController(BookService bookService){
  this.bookService = bookservice;
 }
 //이하 생략
}
# 이 방식 말고 스프링에서는 자동으로 의존 객체를 주입해주는 @RequiredArgsConstructor 를 추가해줄 수 있다.

이 방식은 불변을 보장하여 여러 클라이언트가 의존 객체를 안심하고 공유할 수 있다.

또한 다른 방식으로는 생성자에 자원 팩터리를 넘겨주는 방식이 있다. 팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체(팩터리 메서드 패턴)


아이템 6. 불필요한 객체 생성을 피하라

같은 기능의 객체를 새로 생성하는 대신에, 객체 하나를 재사용하는 편이 나을수도 있다.

  string s = new String("java");

String을 new로 생성하면 항상 새로운 객체를 만들게 된다. 따라서 String 객체를 생성하는 것이 올바르다.

  string s = "java"

이 코드는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 재사용 함.
String pool의 플라이웨이트 패턴: 같은 내용의 String 객체가 선언된다면 기존의 객체를 참조하게 한다.

static factory 메서드 사용하기

생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 불필요한 객체 생성을 피할 수 있음. 생성자는 호출할 때마다 새로운 객체를 만들지만, 팩터리 메서드는 그렇지 않다.

  Boolean true1 = Boolean.valueOf("true");
  Boolean true2 = Boolean.valueOf("true");

무거운 객체

만드는데 메모리나 시간이 오래 걸리는 객체 즉 "비싼 객체"를 반복적으로 만들어야 한다면, 캐싱해두고 재사용해야 함.

 static boolean isRomanNumeralSlow(String s) {
   return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
           + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}

이 메서드가 내부에서 만드는 정규 표현식용 Pattern 인스턴스는 한 번 쓰고 버려저서 곧바로 가비지 컬렉터 대상이 된다.
Pattern은 생성비용이 높은 클래스 중 하나, 늘 같은 Pattern이 필요함이 보장되고 재사용 빈도가 높다면 (static final)로 초기에 캐싱해 놓고 재사용 가능하다.

public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile(
            "^(?=.)M*(C[MD]|D?C{0,3})"
                    + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeralFast(String s) {
        return ROMAN.matcher(s).matches();
    }
}

오토박싱

private static long sum() {
	Long sum = 0L;
	for(long i=0; i<=Integer.MAX_VALUE; i++) {
		sum += i;
	}
	return sum;
}

오토박싱은 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술인데, long 타입이 Long 타입으로 오토박싱되어 프로그램이 현저히 느려진다.

본 글은 이펙티브 자바 정리를 참고 및 발췌하였으며, 필자의 주관적인 생략이 담겨있음과 동시에 부정확할 수 있습니다.

profile
Backend Developer

0개의 댓글