1. 생성자 대신 정적 팩터리 메서드를 고려하라
정적 팩터리 메서드
GoF 디자인 패턴 중 팩터리 메서드 패턴에서 유래한 것으로 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다. 객체를 private 생성자 + public static 메소드를 통해 캡슐화한다. new Object() --> Object.getInstance()
(이 책에서는 디자인 패턴에서의 팩터리 메서드 패턴과 다르다고 명시하고 있다.)
메서드로 설명하면, 객체 생성의 역할을 하는 클래스 메서드이다.
1-1. Boolean을 통한 예시
boolean
값을 받아 Boolean
객체로 변환
- 클래스는 생성자 대신 정적 팩터리 메서드를 제공 가능
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
1-2. 장점
- 이름
- 생성자로는 반환될 객체의 특성을 제대로 설명하지 못함
- 메소드로 이름을 지어주어 특성 설명 가능
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 됨
- 불필요한 객체 생성 X
- 인스턴스 미리 생성
- 새로 생성한 인스턴스 캐싱
- 유사한 패턴 :
플라이웨이트 패턴(Flyweight pattern)
- 인스턴스 생명주기 통제 :
통제 클래스
- 인스턴스 통제 클래스로 가능한 작업 목록
- 싱글턴
- 인스턴스화 불가
- 불변 값 클래스에서 동치인 인스턴스가 하나임을 보장
- 플라이웨이트 패턴의 근간
- 열거 타입은 인스턴스가 하나만 만들어짐을 보장
- 반환 타입의 하위 타입 객체 반환 가능
- 반환할 객체의 클래스 자유롭게 선택
-> 구현 클래스를 공개하지 않고 객체 반환 가능
-> API 작게 유지 가능
-> 인터페이스 기반 프레임워크를 만드는 핵심 기술
- 인터페이스만으로 복잡한 객체 컨트롤
- 입력 매개변수에 따라 매번 다른 클래스의 객체 반환 가능
- 반환 타입의 하위 타입이기만 하면 됨
- 클라이언트에는 영향이 없게 내부적으로 수정이 가능함
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 됨
- 리플렉션을 이용해 객체 지정 생성
- 서비스 제공자 프레임워크를 만드는 근간
- 대표 프레임워크
JDBC
- 제공자 == 서비스의 구현체
1-3. 단점
- 정적 팩터리 메서드만 제공하면 하위 클래스를 만들지 못함
- 상속을 하려면 public이나 protected 생성자가 필요
- 정적 팩터리 메서드는 프로그래머가 찾기 어려움
2. 생성자에 매개변수가 많다면 빌더 고려
2-1. 점층적 생성자 패턴
- 생성자의 매개변수를 하나씩 늘리는 패턴
- 단점
- 가독성 하락
- 같은 타입의 매개변수 순서 구분하기 힘듦
- 컴파일 시점 발견 불가
- 런타임 시점 발견 가능
2-2. 자바 빈즈 패턴
- 기본 생성자 + setter를 이용한 값 설정
- 점층적 생성자 패턴 단점 보완
- 단점
- 객체 하나 생성 시 메소드 여러번 호출
- 완성되기 전까지 객체의 일관성이 무너진 상태
- 클래스를 불변으로 만들지 못함
2-3. 빌더 패턴
- 앞의 두 패턴 단점 보완
- 고려할 점
- 비교적 큰 생성 비용(그리 크진 않음)
- 매개변수 4개 이상에 적절
- 정말 고정적인게 아니라면 매개변수가 늘어날 수 있으니, 처음부터 빌더 패턴 사용
3. private & Enum 으로 싱글턴 보장
3-1. 싱글턴 생성 방식
- private 생성자 + public static final 필드
- 장점
- 단점
- public이므로 리플렉션으로 private 생성자 접근 가능
- 생성자에서 중복 생성 방지 필요
- private 생성자 + private static 정적 팩토리 메소드
- 장점
- API를 바꾸지 않고도 싱글턴이 아니게 변경 가능
- 정적 팩토리를 제네릭 싱글턴 팩토리롤 만들 수 있음
- 정적 팩토리의 메소드 참조를 Supplier로 사용할 수 있음
- Enum 이용
- 원소가 하나뿐인 열거 타입 생성
- 직렬화 상황, 리플렉션 공격 모두 해결
- 단, Enum외의 클래스를 상속해야 한다면 불가능
3-2. 직렬화&역직렬화 시 단일 인스턴스 보장
- 역직렬화 시 새로운 인스턴스가 생성됨
- 해결 방법
- 필드를 transient로 선언
- readResolve 메소드 제공
4. 인스턴스화 방지
- private 생성자 + 명시적 예외
- 자동으로 기본 생성자를 생성하는 것을 방지
- 외부에서 생성자 호출 방지
- 생성자 호출 시에도 예외로 방지
- 상속 시에도 super()로 인한 예외 방지
- 추상 클래스는 상속 해서 인스턴스화 가능 --> XX
5. 의존 객체 주입
6. 불필요 객체 생성 회피
- 재사용 가능한 경우 최대한 객체 생성 최소화
- ex) String, Pattern
new String()
String.matches(String)
-> Pattern.matcher(String).matches()
- 패턴 인스턴스를 매번 재생성하여, 미리 생성해놓고 재사용
- HashMap.keySet()
- HashMap < AbstractMap < Map
- 새로운 인스턴스가 아닌 AbstractMap의 transient Set을 갖고 있음

- 오토 박싱 주의
- 기본 타입과 박싱된 기본 타입을 섞어 쓸 경우
- 자동으로 박싱을 해주면서 성능 저하
- ex )
Long += long -> Long += Long(long)
- 주의점
- 객체 생성은 비싸니 피하자는 아님, 재사용이 필요하면 확실히 재사용
- 방어적 복사에서 재사용이 훨씬 크리티컬 함
7. 다 쓴 객체 참조 해제
- 가비지 컬렉션에서 heap 메모리에 연결된 객체 참조가 있으면 메모리 해제가 불가능
- 명시적으로 참조를 끊어주어 GC 수행
- 캐시
- 스케줄러로 캐시 청소
- 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시일 경우
- 리스너, 콜백
- 콜백을 등록만 하고 해지하지 않을 경우
- WeakHashMap으로 약한 참조로 저장
8. finalizer와 cleaner 사용 회피
(어렵다, 잠깐 생략)
8-1. 자바의 소멸자
finalizer
가비지 컬렉션이 수행될 때, 자원에 대한 정리 작업을 진행하기 위해 호출되는 소멸자 메서드
public class Object {
@Deprecated(since="9")
protected void finalize() throws Throwable { }
}
"종료자는 사용하면 안된다. 예측이 불가능하고 대체로 위험하고 일반적으로 필요하지 않다"
자바의 finalize 메서드는 실행을 보장하지 않음
cleaner
자바 9에서 도입된 소멸자
생성된 Cleaner가 더 이상 사용되지 않을 때 등록된 스레드에서 소멸 작업 수행
9. try-finally vs try-with-resource
자바 라이브러리에는 close 메서드로 직접 닫아야 하는 자원이 많음
InputStream
OutputStream
java.sql.Connection
try-finally
자원이 닫힘을 try-finally로 보장 가능
static String example(String param) throws IOException {
ClosableObject obj = new ClosableObject();
try {
obj.do();
} finally {
obj.close();
}
}
자원이 여러 개라면 try-finally가 중첩된다
- 예외 중첩으로 추적하기 어려움
- 코드 가독성 감소
try-with-resources
자바 7에서 나온 기능으로 위 문제 해결 (AutoCloseable 인터페이스 구현 필요)
static String example(String param) throws IOException {
try(ClosableObject obj = new ClosableObject();
ClosableObject2 obj2 = new ClosableObject2()) {
obj.do();
obj2.do();
}
}
- 소멸시킬 자원을 명확히 구분 가능
- catch 문으로 다른 예외에 대한 처리 가능
- finally로 명시 안해줘도 됨
10. equals, 일반 규약을 지켜 재정의 필요
재정의 필요 없는 경우
- 인스턴스가 본질적으로 고유
- 논리적 동시성을 검사할 일이 없음
- 상위 클래스의 equals가 하위 클래스에도 맞음
- private 클래스, package-private && equals를 호출할 일이 없음
재정의 필요
- 논리적으로 객체가 같음을 확인해야할 경우
- 인스턴스 통제 클래스
equals 메소드 동치 관계
- 공통 조건 : null이 아닌 모든 참조 값
- 반사성 : x에 대해 x equals x는 항상 true
- 대칭성 : x, y에 대해 x equals y가 true이면 y equals x도 true
- 추이성 : x, y, z에 대해 x equals y && y equals z 이면 x equals z
- 일관성 : x, y에 대해 x equals y를 호출하면 항상 true만 반환하거나 항상 false만 반환
- null 아님 : 모든 참조값 x에 대해 x equals null은 false