-
이펙티브 자바 [2] 객체 생성과 파괴Java 2022. 11. 8. 15:42
생성자 대신 정적 팩토리 메서드를 고려해라 (아이템 1)
디자인 패턴의 팩토리 메서드와는 다름
클라이언트가 클래스의 인스턴스를 얻는 수단으로는 public 생성자 외 클래스는 정적 팩토리 메서드를 제공할 수 있다.
정적 팩토리 메서드의 장점
- 이름을 가질 수 있음
- 클래스의 시그니처가 같은 생성자가 여러 개 필요 시 생성자를 정적 팩토리 메서드로 변경하고 각각 차이점을 잘 알아 볼 수 있는 이름으로 지어주자
- 호출 시 매번 인스턴스를 새로 생성하지 않아도 된다.
- 새로 생성한 인스턴르를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있음 (플라이웨이트 패턴)
- 반복되는 요청에 같은 객체를 반환하는 식으로 언제 어느 인스턴스가 살아 있게 할지를 철저히 통제할 수 있다.
플라이웨이트 패턴 인스턴스를 공유시켜 new 연산자를 통해 생성되는 인스턴스의 메모리 낭비를 줄일 수 있는 패턴
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
- 반환할 객체의 클래스를 선택할 수 있는 유연성이 있음.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 클라이언트는 클래스의 존재를 모른체 팩토리가 건네주는 클래스를 받을 수 있다.
- 정적 팩토리 메서드를 작성하는 시점에는 반환할 클래스가 존재하지 않아도 된다.
- 클라이언트를 구현체로부터 분리시켜 준다.
정적 팩토리 메서드의 단점
- 상속을 하려면 public or protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 이 제약은 상속보다 컴포지션 사용하도록 유도하고 불변 타입으로 만들려면 해당 제약을 지켜야 한다는 점에서 오히려 장점으로 받아들일 수 있다.
- 정적 팩토리 메서드는 프로그래머가 찾기 힘들다.
- 별도의 문서화 없이는 사용자는 정적 팩토리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.
각각 장단점이 존재하기에 무분별한 public 생성자 제공을 고치자
생성자에 매개변수가 많다면 빌더를 고려해라 (아이템 2)
보통 빌더 패턴의 경우 4개 이상의 매개변수가 되어야 값어치를 하며, 빌더 패턴은 계층적으로 설계된 클래스와 함께 사용하기 좋다.
만약 빌더 패턴이 없을 경우
자신이 필요한 인자만 넣어 생성하는 점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기가 힘들어 진다.
또 다른 두 번째 대안으로는 자바빈즈 패턴이 있지만 이는 객체 하나를 만들려면 여러개의 메서드를 호출해야 하며, 언제 누가 어디서 setter 로 값을 변경했는지 디버깅 하기 힘들어 지며, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다. 그렇기에 자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으므로 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해주어야 한다.
private 생성자나 열거 타입으로 싱글턴임을 보증해라 (아이템 3)
싱글턴의 전형적인 예로는 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다. 하지만 클래스를 싱글턴으로 만들 경우 이를 사용하는 클라이언트에서 테스트하기가 어려워 질 수 있다.
싱글턴을 만드는 방식으로는 보통 두 가지이며, 두 방식 모두 생성자는 private 으로 감춰 두고 유일한 인스턴스에 접근이 가능한 public static 멤버를 하나 마련해둔다. 여기서 나뉘는 방식의 차이로는 필드가 public , private 이냐로 나뉠 수 있다.
public 장점
- 싱글턴임이 API에 명백히 드러난다. public static 필드가 final 이니 절대로 다른 객체를 참조할 수 없다.
- 간결함
private 장점 (정적 팩토리 메서드)
- API를 변경하지 않고 싱글턴이 아니게 변경이 가능 유일한 인스턴스를 반환하던 팩토리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있음
- 원한다면 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다.
- 정적 팩토리의 공급자(supplier)로 사용할 수 있다. 가령 Elvis::getInstance 를 Supplier<Elvis> 로 사용하는 방식이다.
이러한 private 장점이 필요 없으면 public 필드 방식이 좋으며, 해당 싱글턴 클래스를 직렬화 하려면 단순히 Serializable 을 구현한다고 선언하는 것만으로 부족하다. 모든 인스턴스 필드를 일시적이라고 선언하고 readResolve 메서드를 제공해야 한다. 이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때 마다 새로운 인스턴스가 만들어 진다.
추가로 싱글턴을 만드는 방식으로 원소가 하나인 열거 타입 enum을 선언하는 것이다. 이 방식이 조금 부자연스러워 보일지어도 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다. public 필드 방식과 비슷하며, 더 간결, 추가 노력 없이 직렬화 가능하며 심지어 아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는걸 막아준다.
인스턴스화를 막으려거든 private 생성자를 사용하라 (아이템 4)
private 생성자를 만들경우 컴파일러가 자동으로 기본 생성자를 만들어주는 것을 막을 수 있으며, 클라이언트가 인스턴스화가 가능한지 여부에 대한 혼란을 막을 수 있다.
자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (아이템 5)
사용하는 자원에 따라 동작이 달라지는 클래스에서는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다. 또한 해당 자원들을 클래스가 직접 만들게 해서도 안 된다. 그렇기에 인스턴스를 생성 시 생성자에 필요한 자원을 넘겨주는 방식 (생성자 주입)을 이용해서 의존 객체를 주입해라.
쓸만한 변형으로, 생성자에 자원 팩토리를 넘겨주는 방식이 있다. 팩토리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다. 즉, 팩토리 메서드 패턴을 구현한 것이다. 자바 8에서 소개한 Supplier<T> 인터페이스가 팩토리를 표현한 완벽한 예다. Supplier<T> 를 입력으로 받는 메서드는 일반적으로 한정적 와일드카드 타입을 사용해 팩토리의 타입 매개변수를 제한해야 한다. 이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든지 생성할 수 있는 팩토리를 넘길 수 있다.
불필요한 객체 생성을 피해라 (아이템 6)
생성자 대신 정적 팩토리 메서드를 제공하는 불변 클래스에서는 정적 팩토리 메서드를 사용해 불필요한 객체 생성을 막을 수 있다. 인스턴스 생성 비용이 높은 객체의 경우 정적 초기화 과정에서 직접 생성해 캐싱해두고, 나중에 해당 객체를 호출할 때 마다 캐싱해둔 인스턴스를 재사용하면 생성 비용을 절약할 수 있다.
박싱된 기본 타입보다는 기본 타입을 사용하여, 의도치 않은 오토박싱을 주의하자.
다 쓴 객체 참조를 해제하라 (아이템 7)
객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.
finalizer와 cleaner 사용을 피하라 (아이템 8)
결론은 네이티브 자원 회수용이 아니라면AutoCloseable 를 구현받아 try-with-resoure 를 사용하자.
자바에서 지원하는 객체 소멸자로 finalize, cleaner 가 존재하지만 자바 9부터는 finalize 메서드의 경우 언제 실행, 성능 문제, 보안 문제를 일으켜 deprecated 되었다.
finalize 대안으로 자바9에서 cleaner 가 등장했지만 이 또한 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할이지만 즉시 호출되리라는 보장이 없어 성능 저하를 감당하고 네이티브 피어가 심각한 자원을 회수할때 사용한다.
네이티브 피어는 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 의미한다.
try-finally 보다는 try-with-resources를 사용하라 (아이템 9)
try-finally 코드는 실용적이지 못할 만큼 코드가 지저분해지므로 AutoCloseable 인터페이스를 구현받은 클래스의 경우 사용 후 자원이 close 되는 try-with-resources 를 사용하자.
'Java' 카테고리의 다른 글
서블릿 동작원리 (0) 2021.09.16 냄새와 휴리스틱 (0) 2021.07.22 메서드 메모리관리 (0) 2020.06.17 jMeter 사용하기 (with MQTT) (2) 2020.05.12 소켓 통신 I/O Stream (0) 2020.05.07 - 이름을 가질 수 있음