이펙티브 자바 스터디 - 객체 생성과 파괴 -1

BRINCE·2022년 11월 28일
1
post-thumbnail

2장 객체의 생성과 파괴

올바른 객체의 생성 방법과 불필요한 생성을 피하는 방법, 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리하는 요령을 알려준다.
(쉽게 말하자면 객체의 생명 주기를 효율적으로 관리하는 방법을 알려준다.)

생성자 대신 정적 팩토리 메소드를 고려하라 🤔

클래스는 생성자와 별도로 정적 팩토리 메소드를 제공할 수 있다. (디자인 패턴에서의 팩토리 메소드와 다르다.)

public static Article of (String title, String content){
	return new Article(title,content);
    } //정적 팩토리 메소드를 선언한 예제

정적 팩토리 메소드가 단순 생성자보다 좋은 이유들 💁🏻

  • 생성자는 이름을 가질 수 없지만, 정적 팩토리 메소드는 이름에 특성을 묘사할 수 있다.
    • 생성자는 이름을 가질 수 없어 객체의 특성을 제대로 설명하지 못한다.
      하지만 정적 팩토리 메소드는 이름만 잘 지으면 객체의 특성을 쉽게 묘사할 수 있다.
  • 호출될 때마다 인스턴스를 새로 생성할 필요가 없다.
    • 인스턴스를 미리 만들어놓거나 새로 생성한 인스턴스를 캐싱하여 재활용 하는 방식으로 불필요한 객체 생성을 피할 수 있다.
  • 반환 타입의 하위 객체를 반환할 수 있는 능력이 있다.
    • 해당 하위 타입의 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있다.
      • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
  • 정적 팩토리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

반면에 단점도 존재한다.

  • 상속을 하려면 public 이나 protected 생성자가 필요하기 때문에 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
  • 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
    • 메서드 이름을 알려진 규약을 따라 지어야지 해당 문제를 완화할 수 있다.

정리 🫵🏻

상대적인 장단점을 이해하고 사용하는 것이 좋다. 하지만 정적 팩토리 메소드를 사용하는게 유리한 경우가 더 많기때문에 무작정 생성자를 제공하던 습관이 있다면 고치면 좋다.

생성자에 매개변수가 많다면 빌더를 고려하라 🤔

정적 팩토리 메소드와 생성자에는 같은 제약이 있다. 선택적 매개변수가 많다면 적절히 대응하기가 어렵다는 점이다.

이렇게 정적 팩토리 메소드에 매개변수가 너무 많고, 그리고 안넣어도 되는 값이 있을때마다 매번 메소드를 새로 선언해주어야 할까? 그리고 매개변수의 몇번째 순서에 어떠한 값을 넣어야 할지 모르는 경우도 생길것이다. 🤦🏻‍♂️

이런 단점을 완화해줄수 있는 빌더 패턴이 있다.

빌더는 점층적 생성자(필요할때마다 생성자를 새로 선언하는)보다 클라이언트 코드를 일고 쓰기가 훨씬! 간결하고 자바빈즈 보다 훨씬 안전하다.

간단하게 롬복 어노테이션으로 빌더를 생성이 가능하다.

    @Builder
    @Getter
    @Setter
    @NoArgsConstructor
    public static class ArticleWithCommentDto {
    ...

빌더를 새로 만들지 않고 어노테이션으로만 등록해준 모습이다.

private UserAccount.UserAccountDto createUserAccountDto() {
        return UserAccount.UserAccountDto.of(
                "uno",
                "password",
                "uno@mail.com",
                "Uno",
                "This is memo",
                LocalDateTime.now(),
                "uno",
                LocalDateTime.now(),
                "uno",
                null
        );
    } //빌더를 사용하지 않을때의 모습. 매개값은 굉장히 많아서 헷갈린다.

    private Article.ArticleWithCommentDto createArticleWithCommentDto() {
        return Article.ArticleWithCommentDto.builder()
                .id(1L)
                .userAccountDto(createUserAccountDto())
                .title("title")
                .content("content")
                .createdAt(LocalDateTime.now())
                .createdBy("Uno")
                .modifiedAt(LocalDateTime.now())
                .modifiedBy("Uno")
                .deleted("N")
                .likeCount(0)
                .articleCommentDtos(null)
                .build();
    } //빌더를 사용할때 모습. 어떠한 변수에 해당 값들이 입력되는지 알려주기떄문에 훨씬 가독성이 좋다.

자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 🫵🏻

//정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private static final Lexicon dictionary = ...; //?
private SpellChecker() {} // 객체 생성 방지
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}

-알라딘 eBook <이펙티브 자바> (조슈아 블로크 지음, 개앞맵시 옮김) 중에서
//싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...; //?
private SpellChecker(...) {}
public static SpellChecker INSTANCE = new SpellChecker(...); //?
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
// 싱글톤으로 관리한다면, 왜 싱글톤으로 관리해야하는지를 잘 생각해보자.
-알라딘 eBook <이펙티브 자바> (조슈아 블로크 지음, 개앞맵시 옮김) 중에서

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

해당 코드를 보면 SpellChecker 가 Dictionary객체를 참고해 동작하는것을 알 수 있다. 해당 Dictionary 객체가 단 하나만 사용된다고 가정한다면 상관 없겠지만
SpellChecker 라는 객체가 매번 상황에 따라 다른 사전을 사용할 수 있는데, 저 코드들은 해당 상황을 통제할 수가 없다는 말이다.

상황을 따져보자면 SpellCheckers는 싱글톤으로 관리가 가능하지만, Dictionary는 여러 종류의 사전이 있을 수 있기 때문에 객체 주입을 사용해야한다.

//의존 객체 주입은 유연성과 테스트 용이성을 높여준다.
public class SpellChecker {
	private final Lexicon dictionary;
	public SpellChecker(Lexicon dictionary) {
		this.dictionary = Objects.requireNonNull(dictionary);
        }
	public boolean isValid(String word) { ... }
	public List<String> suggestions(String typo) { ... }}
//해당 객체가 필요할때마다 선택해서 객체를 주입받도록 하면 된다.
-알라딘 eBook <이펙티브 자바> (조슈아 블로크 지음, 개앞맵시 옮김) 중에서

이 방식을 사용해 클라이언트는 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩토리를 넘길 수 있다. (사전 클래스의 자식 클래스인 영어사전/한글사전 등등 생성자에만 명시해준다면 유연성을 높여줄 수 있다.)

정리 💁🏻

클래스가 하나 이상의 자원에 의존하고, 해당 자원이 클래스 동작에 영향을 준다면 (의존을 한다면?) 싱글톤과 정적 유틸리티 클래스는 사용하지 않는것이 좋다.

다 쓴 객체 참조를 해제하라 🫵🏻

자바는 가비지 컬렉터를 갖춘 언어로 다 쓴 객체를 알아서 회수해간다. 하지만 알게모르게 해당 기능의 편리함때문에 메모리가 누수되는 경우가 있다.

public Object pop() {
	if (size == 0)
		throw new EmptyStackException();
	return elements[--size];
}
//스택을 구현한 코드의 내부이다. 해당 코드에서 어떤 부분이 메모리 누수를 일으킬까?
-알라딘 eBook <이펙티브 자바> (조슈아 블로크 지음, 개앞맵시 옮김) 중에서

해당 코드에서는 스택이 커졌다가 줄어들었을때 스택에서 꺼내어진 객체들을 가비지 컬렉터가 회수해가지 않는다.
이 스택이 해당 객체들의 참조를 여전히 가지고 있기 때문이다.

방법은 간단하게 해당 참조를 다 썼을때 null처리(참조해제) 를 하면 된다.

public Object pop() {
	if (size == 0)
		throw new EmptyStackException();
        elements[size] = null; //다 쓴 참조 해제
	return elements[--size];
}

-알라딘 eBook <이펙티브 자바> (조슈아 블로크 지음, 개앞맵시 옮김) 중에서

객체 참조를 null 처리하는 것은 예외적인 경우여야 한다.

  • 다 쓴 참조를 해제하는 가장 좋은 방법은 해당 참조를 담은 변수를 유효범위 밖으로 밀어내는것이다.

자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다.

  • 원소를 다 사용한 즉시 그 원소가 참조핸 객체들을 모두 null 처리 해줘야 한다.
profile
자스코드훔쳐보는변태

0개의 댓글