개요
- 2장은 객체의 생성과 파괴에 대해 다룸
- 객체를 만들어야 할 때와 만들지 말아야 할 때를 구분
- 올바른 객체 생성 방법
- 불필요한 생성을 피하는 방법
- 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리하는 요령
1. 생성자 대신 정적 팩터리 메서드를 고려하라
- 일반적으로 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자이다.
- 클래스는 생성자와 별개로 정적 팩터리 메서드를 제공할 수 있다.
- JDBC같은 경우가 대표적인 예시이다.
장점
- 이름을 가질 수 있다.
- 생성자에 넘기는 매개 변수와 생성자 자체 만으로는 반환될 객체의 특성을 설명하지 못한다.
하지만, 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
- 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다. 이런 클래스를 인스턴스 통제 클래스 라고한다.
- 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 재활용 하는 식으로 불필요한 객체 생성을 피할 수 있다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.이는 곧 구현 클래스를 공개하지 않고 API를 작게 유지할 수 있다.
- 반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 엄청난 유연성 제공한다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
단점
- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.(딱 눈에 띄지 않기 때문)
- API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.
Naming Convention
#매개변수 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
from
#여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
of
#from과 of의 더 자ㅎ세한 버전
valueOf
#매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
instance or getInstance
#instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
create or newInstance
#getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다.
getType
#newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다.
newType
#getType과 newType의 간결한 버전
type
2. 생성자에 매개변수가 많다면 빌더를 고려하라
- 정적 팩터리와 생성자은 선택적 매개변수가 많을 때 적절히 대응하기 어렵다.
- 이럴때 보통 점층적 생성자 패턴(telescoping constructor pattern)을 사용한다.
- 필수 매개변수 생성자, 필수와 선택 매개변수 1개를 받는 생성자… 선택 매개변수를 모두 받는 생성자까지 모두 만드는 방식
- 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작서하거나 읽기 어렵다.
- 두번째 대안으로 자바빈즈 패턴(JavaBeans Pattern)이 있다.
- 매개변수가 없는 생성자로 만든 후, 세터 메서들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
- 하지만, 객체 하나를 만드려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
- 클래스를 분변으로 만들 수 없다.
- 그래서 객체를 만들고 수동으로 “얼리는”작업을 수행하기도 하지만 실전에서는 거의 쓰이지 않는다.
- 그래서 우리는 빌더 패턴을 사용하자.
- 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 가능
- 메서드 호출이 흐르듯 연결된다는 뜻으로 플루언트 API(fluent API) 혹은 메소드 연쇄(Method chaining)라고도 불린다.
- 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
단점
- 객체를 만드려면 빌더 패턴부터 만들어야 한다.
- 빌더 생성 비용이 크지는 않지만, 성능에 민감한 상황에서는 문제가 될 수도 있다.
- 점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.
3. private 생성자나 열거 타입으로 싱글턴임을 보증하라
- 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 만들 때 필요하다
- 그러나 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워 질수 있다.
public static final 필드 방식의 싱글턴
- private 생성자
- public static final 인스턴스 생성
- 싱글턴임이 API에 명백히 드러나고 간결하다
정적 팩터리 방식의 싱글턴
- private 생성자
- private static final 인스턴스 생성
- getInstance()를 통한 참조
- API를 바꾸지 않고도 싱글턴이 아니게 바꿀 수 있다.
- 예를 들어 스레드별로 다른 인스턴스를 넘겨주게 할 수있다.
- 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있다.
4. 인스턴스화를 막으려거든 private 생성자를 사용하라.
- 정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때가 있다.
- 정적 멤버만 담은 유틸 클래스는 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다.
- 하지만, 생성자를 명시해주지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다.
- 그렇기 때문에 의도치 않게 인스턴스화할 수 있게 되어있다.
- 추상 클래스로 만드는 것은 인스턴스화를 막을 수 없다.
- 하위 클래스를 만들어 인스턴스화 하면 그만이기 때문이다.
- 맥락상 추상 클래스를 상속해서 쓰라는 것처럼 보이기도 한다.
- 따라서, private 생성자를 추가하여 클래스의 인스턴스화를 막는다.
5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
- 많은 클래스는 하나 이상의 자원에 의존한다.
- 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
- 그렇기에 인스턴스를 생성할 때 필요한 자원을 넘겨주는 방식으로 구현하자
- 일종의 의존 객체 주입의 한 형태이다.
- 의존 객체 주입은 생성자, 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있다.
- 또다른 변형으로는 생성자에 자원 팩터리를 넘겨주는 방식이 있다.
6. 불필요한 객체 생성을 피하라
- 똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
- 예컨데, Boolean을 선언하는 것보다, Boolean.valueOf(String)을 사용하는 것이 좋다.
- 생성자는 호출 될 때마다 새로운 객체를 만들지만, 팩터리 메서드는 그렇지 않기 때문이다.
- 생성비용이 비싼 경우에는 필요하다면 캐싱하여 재사용하는 것이 좋다.
- 예컨데, 정규식을 사용하는 것은 성능에 좋지 못하다.
- 한번 Pattern 클래스가 사용되고 버려지고, 입력받은 정규 표현식에 해당하는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 비싸다.
- 따라서, Pattern 인스턴스 클래스 초기화 과정에서 직접 생성해 캐싱해두고, 나중에 메소드가 호출 될 때마다 이 인스턴스를 재사용 하도록 한다.
- 약 6.5배가 빨라진다고 한다 후덜덜;;
- 오토박싱은 불필요한 객체를 만들어낸다.
- Long과 long의 차이에서 오는 내용이다.
- long값을 Long 객체 안에 넣으려고 하면 매번 Long 인스턴스가 만들어졌다가 지워질 것이기 때문에 의도치 않게 성능이 떨어질 수 있다.
7. 다 쓴 객체 참조를 해제하라.
- 자바는 C, C++처럼 메모리를 직접 관리해야하는 언어가 아니지만 그럼에도 조심해야 한다.
- 예컨데, 직접 제작한 스택구조에서 pop할 때 단순히 인덱스값만 조정할 시 문제가 된다.
- 객체르 만들어 놓고, 인데스만 조정하기에 GC의 대상이 되지 않는다.
- 그러다보면 OutOfMemoryError를 맞닦드릴수도 잇다.
- 객체 참조를 끊어야 한다.(null 처리를 한다.)
- null작업을 계속 하면 코드도 더러워지고 불필요한 경우도 있다.
- 그렇기에 우리는 객체의 유효범위(scope)를 잘 정해야 한다.
- 유효범위 밖으로 밀어버리면 자연스레 GC의 대상이 될 것이다.