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

동동주·2025년 10월 16일

이펙티브 자바

목록 보기
1/13

관련 github 코드

  1. 객체를 만들어야 할 때와 만들지 말아야 할 때를 구분하는 법
  2. 올바른 객체 생성 방법과 불필요한 생성을 피하는 방법
  3. 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리하는 요령

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

클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다. 클래스는 클라이언트에 public 생성자 대신 (혹은 생성자와 함께 정적 팩터리 메서드를 제공할 수 있다.

  1. 장점
  • 이름을 가질 수 있다. (bigInteger.probablePrime)
  • 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  • 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다
  • 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. (JDBC)
  1. 단점
  • 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
  • 정적 팩터리 메서드는 프로그래머가 찾기 어렵다

핵심정리: 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.

  • @AllArgsConstructor를 많이 썼던 것 같은데 자제해야겠다..

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

점층적 생성자 패턴은 확장하기 어렵다!
대안으로는, 자바빈즈가 있다. 하지만 자바빈즈는 객체 하나를 만들려면 메서들르 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이는 치명적인 단점이 있다고 한다.

그래서 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴을 쓴다.
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. 매개 변수가 4개 이상은 되어야 빌더 패턴의 값어치를 하지만 보통 api는 시간이 지날수록 매개변수가 많아지는 경향을 띄고 있기 때문에 빌더로 시작하는 편이 좋다고 한다.

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

싱글턴은 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다.

싱글턴을 만드는 방식은 둘 중 하나이다.
1. public static 멤버가 final 필드인 방식
2. 정적 팩터리 메서드를 public static 멤버로 제공하는 방식

1번의 장점은 해당 클래스가 싱글턴임이 api에 명백히 드러난다는 점이다.
2번의 장점은 api를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있고, 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다는 점이다.

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

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 하위 클래스를 만들어 인스턴스화를 하면 되기 때문이다.

컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 때뿐이니 private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다. 명시적 생성자가 private이기 때문에 클래스 바깥에서는 접근할 수 없어서 적절한 주석을 달아놓는 것이 좋다. 또한, 이 방식을 적용하면 상속을 불가능하게 하는 효과도 있다.

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

많은 클래스가 하나 이상의 자원에 의존한다. 보통 이런 클래스를 정적 유틸리티 클래스로 구현하거나 싱글턴으로 구현한다. 하지만 사용하는 자원에 따라 해당 방법들이 적합하지 않을 수 있다. (클래스가 직접 만들게 해서도 안된다.)
그럼 해결책으로 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주면 된다.
보통 이 방법을 의존 객체 주입이라고 하는데 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기막히게 개선해준다고 한다.

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

String s = new String("Java");

위 방법은 반복문이나 빈번히 호출되는 메서드 안에 있다면 String 인스턴스가 쓸데없이 수백만 개 만들어질 수 있어서 절대 사용하면 안된다.

String s = "Java";

위 코드로 선언할 것.

  • String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다. 성능을 개선하려면 정규표현식을 표현하는 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용하면 된다.

  • 그렇다고 아주 무거운 객체가 아닌 다음에야 단순히 객체 생성을 피하고자 나만의 객체 풀(pool)을 만들라는 건 아니라고 한다. (요즘 JVM의 가비지 컬렉터는 상당히 잘 최적화되어서 가벼운 객체용을 다룰 때는 직접 만든 객체 풀보다 훨씬 빠르다.) 그래도 데이터베이스 연결과 같이 생성 비용이 비싼 경우에는 재사용하는 편이 낫다고는 한다.

방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억하자.

아이템 7) 다 쓴 객체 참조를 해제하라

  • 메모리 누수: 해당 스택을 사용하는 프로그램을 오래 실행하다 보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 결국 성능이 저하됨

다 쓴 참조란, 문자 그대로 앞으로 다시 쓰지 않을 참조를 뜻한다.
가장 간단한 방법은 다 쓴 참조를 null 처리를 하면 된다. 이러다 보면 null 처리를 하는데 혈안이 되어 오히려 코드가 지저분해지는 경우도 있다. 객체 잠조를 null 처리하는 일은 예외적인 경우여야 한다.
-> 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이다.

  • null 처리를 해야하는 경우
    - 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 함
    • 메모리 누수를 일으키는 주범 중 하나인 캐시 처리를잘 해야 함
    • 리스너 혹은 콜백 확인하기

아이템 9) try-finally보다는 try-with-resources를 사용하라

전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.

예컨대 기기에 물리적인 문제가 생긴다면 firstLineOfFile 메서드 안의 readLine 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패할 것이다. 이런 상황이라면 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다. 그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되어, 실제 시스템에서의 디버깅을 몹시 어렵게 한다.

위와 같은 문제들을 자바 7의 try-with-resources가 해결한다. 숨겨진 예외들이 버려지지 않고, 스택 추적 내역에 '숨겨졌다'는 꼬리표를 달고 출력된다고 한다.

0개의 댓글