Programming/JAVA
[Effective JAVA 3S/E] 객체 생성과 파괴 - item1
sky Jeong
2021. 6. 18. 14:12
개요
- 객체를 만들어야 할 때와 만들지 말아야 할 때 구분
- 올바른 객체 생성 방법과 불필요한 생성 피하기
- 제때 파괴됨을 보장하며, 파괴 전 수행할 정리 작업 관리 요령
[1] 생성자 대신 정적 팩터리 메서드 고려하기
- 클래스의 인스턴스 생성 방법
- public 생성자를 통한 생성
- 정적 팩터리 메서드(static factory method)
- 클래스의 인스턴스를 반환하는 단순한 정적 메서드 임
- 디자인 패턴과 일치하는 패턴은 없음
정적 팩토리 메소드 — 소중한 나의 개발일기 (tistory.com)
public 생성자를 통한 생성의 문제점 [ 클래스 인스턴스 생성으로 인한 문제]
1. 생성된 인스턴스의 목적성
- 어떤 객체가 어떤 목적으로 생성되었는지 나타낼 수 없다는 단점이 있음
- 다행히 intelliJ는 각 Parameter의 명칭이 나타나 변수명으로 확인할 수 있으나, 기본적으로 직관성이 떨어짐
2. 매번 새로운 인스턴스의 생성의 필요가 없음
조건을 걸어보겠습니다. 만약 한 번 기록된 필드 값을 변경할 수 없는 불변 클래스에서 설농탕을 순두부찌개로 바꿔보겠습니다.
위에서 볼 수 있듯 인스턴스를 다시 생성해야 합니다. 그러면 Garbage가 늘어나겠지요. Hashcode를 조회하면 다른 값이 나타남을 확인할 수 있습니다.
Hashcode가 다르다는 것은 새로운 메모리가 할당된 것입니다.
3. 반환할 객체의 클래스가 존재하지 않으면 반환할 수 없다.
public 생성자를 이용한 방법에서는 해당 클래스의 인스턴스를 반환하는 것이 불가합니다.
정적 팩토리 메소드
장점
- public 생성자(인스턴스 생성)을 통한 호출에서 생기는 문제점을 해결하기 위해 고안된 기술
- 클래스의 인스턴스를 반환하는 static 메소드를 의미함
- 팩토리 메소드 내부에 이미 생성된 객체를 반환하거나, 새로운 객체 생성 가능
1. 이름 지정을 통한 public 생성자의 목적성 문제 해결 가능
- 이름을 지정할 수 있어 생성된 인스턴스의 의미를 명시할 수 있음
- 즉, 정적 팩터리 메서드는 이름만 잘 지은다면 반환될 객체의 특성을 쉽게 묘사할 수 있음
[예시] BigInteger(int, int, Random)과 정적 팩터리 메서드인 BigInteger.probablePrime 중 어느 쪽이 소수인 BigInteger를 반환하는지 직관적으로 알 수 있음
2. 매번 새로운 인스턴스 생성의 문제 해결 가능
즉, 불변 클래스의 인스턴스를 미리 만들드는 등의 불필요한 객체 생성을 피할 수 있음
- Coke -> Apple -> Coke 하였을 때, Coke의 hashcode가 변하지 않았음을 확인할 수 있음
- 이로 생성 비용이 큰 같은 객체가 자주 요청되는 상횡에서 성능을 상당히 끌어올려 줌
- 대표적 예시: Boolean.valueOf(boolean)
- 객체를 생성하지 않음
- 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아있게 할지 철저히 통제할 수 있음 :: 이와 같은 클래스를 인스턴스 통제(instance-controlled) 클래스라고 함
- 같은 객체가 자주 요청되는 상황에 사용한다면 성능으 상당히 끌어올릴 수 있음
- 인스턴스 통제 이유
- 싱글턴으로 만들 수 있음
- 인스턴스화 불가로 만들 수 있음
- 불편 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있음
- 인스턴스 통제의 이점
불변 클래스 이점
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력을 가짐 (이해 안 감 ㅠ)
- 반환 객체의 클래스를 자유롭게 선택할 수 있어 엄청난 유연성을 가짐
- 이 유연성으로 API를 만들 때, 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있음
[하늘쓰 이해] 그러니까.. 사용하는 클라이언트는 내부 로직을 이해할 필요 없이 매개인자로 원하는 값을 잘 받아 올 수 있다는 건가?
==> ?? 그러면 4번이랑 같은 내용인데;; - API를 작게 유지한다. = 프로그래머가 별도의 문서를 찾아가며 실제 구현 클래스가 무엇인지 알아보지 않아도 됨
[예시] java.util.Collections는 45개 클래스를 공개하지 않기에 API 외견을 작게 만들 수 있었음 - 이로 정적 팩터리 메서드를 사용하는 클라이언트는 얻은 객체를 인터페이스만으로 다루게 됨
4. 입력 매개변수에 따라 다른 클래스 객체 반환 가능
- 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관 없음
- 예시로 들자면..
EnumSet 클래스는 public 생성자 없이 오직 정적 팩터리만 제공함. 이는 OpenJDK에서 원소의 수에 따라 두 하위 클래스 중 하나의 인스턴스를 반환함. 64개 이하면 long 변수로 관리하는 RegularEnumSet 인스턴스, 65개 이상이면 long 배열로 관리하는 JumboEnumSet의 인스턴스를 반환함 - 위 예시에서 클라이언트는 팩터리가 주는 객체가 어느 클래스의 인스턴스인지에 상관 없이 EnumSet의 하위 클래스이기만 하면 됨
즉, EnumSet이라는 상위 개념을 받은 하위 클래스를 받을 수 있으면 된다는 뜻! - 쉽게 얘기하자면 이는 리스코프 치환의 원칙을 따른 하위 클래스를 반환하면 된다는 것!
단, 이는 입력된 매개변수에 따라 반환되는 클래스가 달라진다는 것임.
5. 반환할 객체의 클래스가 존재하지 않으면 반환할 수 없는 문제 해결
- 서비스 제공자 프레임워크를 만드는 근간
- 대표적: JDBC(Java Databse Connectivity)
- 서비스 제공자 프레임워크에서의 제공자 => 서비스 구현체
- 구현체들은 클라이언트에서 제공하는 역할을 프레임워크가 통제해 클라이언트를 구현체로부터 분리함
- 서비스 제공자 프레임워크의 구성
- 서비스 인터페이스 (service interface) : 구현체 동작 정의
- 제공자 등록 API (provider registration API) : 구현체 등록
- 서비스 접근 API (service access API) : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
- 서비스 제공자 인터페이스 (service provider interface) : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체
- 예시. JDBC
- Connection : 서비스 인터페이스 역할
- DriverManager.registerDriver : 제공자 등록 API 역할
- DriverManager.getConnection : 서비스 접근 API 역할
- Driver : 서비스 제공자 인터페이스 역할
단점
1. 정적 팩토리 메소드만 제공할 경우 하위 클래스를 만들 수 없음
- 상속은 public이나 protected 생성자가 필요함
- 정적 팩터리 메서드는 private 생성자로 인해 하위 클래스 생성을 할 수 없음
2. 정적 팩토리 메서드는 프로그래머가 찾기 어려움
- API 설명에 명확히 드러나지 않아 사용자가 직접 정적 팩토리 메소드 방식 클래스를 인스턴스화할 방법을 알아야 함
- 그렇기에 API 문서를 잘 작성해야 하며, 메서드 이름 또한 널리 알려진 규약을 따라 지는 방식을 택해야 함