-
[Java] 이펙티브 자바 (1) - 객체 생성과 파괴Java 2020. 4. 18. 00:28반응형
아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라
정적 팩터리 메서드가 생성자보다 좋은 장점 다섯 가지
1. 이름을 가질 수 있음
2. 호출될 떄마다 인스턴스를 새로 생성하지 않아도 됨
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있음
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있음
5. 정적 팩터리 메서드를 작성하는 시점에서 반환할 객체의 클래스가 존재하지 않아도 됨
정적 팩터리를 사용했을 때의 단점
1. 상속을 하려면 public or protected 생성자가 필요하므로 정적 팩터리 메서드만 제공할 경우 하위 클래스를 만들 수 없음
2. 정적 팩터리 메서드는 프로그래머가 찾기 어려움
정적 팩터리 메서드에 흔히 사용하는 명명 방식
- from : 매개변수 하나를 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
- valueOf : from과 of의 자세한 버전
- instance 또는 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음
- create 또는 newInstance : instance 또는 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
- getType : getInstance와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용. Type = 팩터리 메서드가 반환할 객체의 타입
- newType : newInstance와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용. Type = 팩터리 메서드가 반환할 객체의 타입
- type : getType과 newType의 간결한 버전
용어
Static Factory Method(정적 팩터리 메서드) : 해당 클래스의 인스턴스를 반환하는 단순한 정적 메서드
Instance-Controlled Class(인스턴스 통제 클래스) : 언제 어느 인스턴스를 살아있게 할지를 통제하는 클래스
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라
점층적 생성자 패턴을 사용한다면?
매개변수의 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려움
자바빈즈 패턴을 사용한다면?
객체 하나를 만들 때 메서드를 여러 개 호출해야하고, 객체가 완전히 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 됨
클래스를 불변으로 만들 수 없고, 쓰레드 안전성을 얻으려면 프로그래머가 추가 작업을 해줘야 함
빌더 패턴을 사용한다면?
점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비
빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수(named optional parameters)를 흉내낸 것
계층적으로 설계된 클래스와 함께 쓰기 좋음
용어
Telescoping Constructor Pattern(점층적 생성자 패턴) : 필수 매개변수만 받는 생성자 ~ 선택 매개변수를 전부 다 받는 생성자까지 늘려나가는 방식
JavaBeans Pattern : 매개변수가 없는 생성자로 객체를 만든 후 Setter 메서드를 통해 원하는 매개변수의 값을 설정
아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보장하라
Singleton 클래스의 단점
클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려워질 수 있음
- 인터페이스를 구현한 싱글턴이 아니라면 mock 구현으로 대체할 수 없기 때문
Singleton을 만드는 방법 1 - public static final Field 방식
private 생성자가 public static final 필드인 INSTANCE를 초기화할 때 딱 한 번 호출됨
장점) 해당 클래스가 싱글턴임이 API에 드러남, 간결함
예외) AccessibleObject.setAccessible(리플렉션 API)을 사용한 private 생성자 호출
- 생성자를 수정하여 두 번째 객체가 생성되려 할 때 예외를 던지면 됨
신경쓸 부분) 직렬화하려면 단순히 Serializable만 구현하는 것으로는 부족함. (아이템 12장, 89장 참고)
Singleton을 만드는 방법 2 - 정적 팩터리 메서드 방식
장점) API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있음, 원할 경우 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있음, 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있음(Jamie::getInstance를 Supplier<Jamie>로) - 세 가지 모두 필요 없다면 public static final Field 방식 추천
예외) public static final Field 방식과 동일
신경 쓸 부분) public static final field 방식과 동일
Singleton을 만드는 방법 3 - 원소가 하나인 열거 타입 선언
장점) 간결하고, 추가 노력 없이 직렬화할 수 있고, 복잡한 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막아줌
단, 싱글턴이 Enum 외의 클래스를 상속해야한다면 해당 방법은 사용할 수 없음(구현은 상관 없음)
용어
Singleton(싱글턴) : 인스턴스를 오직 하나만 생성할 수 있는 클래스
아이템 4. 인스턴스화를 막으려면 private 생성자를 사용하라
유틸리티 클래스
단순히 정적 메서드와 정적 필드만을 담은 클래스 - 인스턴스로 만들어 쓰려고 설계한 것이 아님
꼭 필요한 것이 아니라면, 객체 지향적인 사고를 저해하므로 남용하지 말 것.
(유틸리티 클래스에서) 인스턴스화를 막으려고 추상 클래스로 만드는 것
하위 클래스를 만들어 인스턴스화 할 수 있으며, 사용자는 상속해서 쓰라는 뜻으로 오해할 수 있음
private 생성자를 사용하면
클래스의 인스턴스화를 막을 수 있고, 상속을 불가능하게 하는 효과도 있음
아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
사용하는 자원에 따라 동작이 달라지는 클래스
- 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 주는 클래스
사용하는 자원에 따라 동작이 달라지는 클래스 - 적합하지 않은 방법
- 정적 유틸리티 클래스 방식이나 싱글턴 방식이 적합하지 않음
- 사용하는 자원이 할당된 필드에서 final 한정자를 제거하고, 다른 자원으로 교체하는 메서드를 추가하는 방식은 어색하고 오류를 내기 쉬우며 멀티 쓰레드 환경에서 쓸 수 없음
사용하는 자원에 따라 동작이 달라지는 클래스 - 적합한 방법
인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식- 의존 객체 주입의 한 형태
아이템 6. 불필요한 객체 생성을 피하라
같은 기능의 객체는 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 수 있음
같은 기능의 객체 예) 같은 문자열, Pattern 객체, 데이터베이스 연결 등
하나를 재사용하는 법 - 싱글턴, 정적 팩터리 메서드를 이용한 캐싱 객체 반환, 변수에 들고 있기 등
Pattern 인스턴스
- 입력받은 정규표현식에 해당하는 유한 상태 기계(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높음불필요한 객체를 만들어내는 또 다른 예 - auto boxing(오토 박싱)
오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만 완전히 없애주는 것이 아님.
박싱된 기본 타입보다는 기본 타입을 싸용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의
용어
finite state machine(유한 상태 기계) : 유한한 개수의 상태를 가질 수 있는 오토마타(추상 기계). 한 번에 오로지 하나의 상태만을 가지게 되며, 현재 상태(Current State)란 임의의 주어진 시간의 상태를 칭함. 어떠한 사건(Event)에 의해 한 상태에서 다른 상태로 변화할 수 있으며, 이를 전이(Transition)라 함.
auto boxing(오토 박싱) : 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 사용할 때 자동으로 상호 변환해주는 것
아이템 7. 다 쓴 객체 참조를 해제하라
가비지 컬렉션 언어에서의 메모리 누수 (1) - 객체 참조 원인
(의도치 않게 객체를 살려두는) 메모리 누수를 찾기가 아주 까다로움, 객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐만 아니라 그 객체가 참조하는 모든 객체를 회수하지 못함. 단 몇 개의 객체가 많은 객체를 회수되지 못하게 할 수도 있고, 잠재적으로 성능에 악영향을 줄 수도 있음.
가비지 컬렉션 언어에서의 메모리 누수 (1) - 객체 참조 해결법
그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것 - 변수의 범위를 최소가 되게 정의했다면 자연스럽게 이뤄짐
자기 메모리를 직접 관리하는 클래스 - 원소를 다 사용한 즉시 그 원소가 참조한 객체들을 다 null 처리(참조 해제)
가비지 컬렉션 언어에서의 메모리 누수 (2) - 캐시 원인
객체 참조를 캐시에 넣고, 넣은 사실을 잊은 채 그 객체를 다 쓴 뒤로도 한참을 그냥 놔둘 경우
가비지 컬렉션 언어에서의 메모리 누수 (2) - 캐시 해결법
- 캐시 외부에서 키(key)를 참조하는 동안만(값이 아님) 엔트리가 살아 있는 캐시가 필요하다면, WeakHashMap을 이용해 캐시를 만들기. 다 쓴 엔트리는 그 즉시 자동으로 제거됨(단, WeakHashMap은 해당 상황에서만 유용)
- 캐시 엔트리의 유효 기간을 정확히 정의하기 어려운 경우, 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용. 쓰지 않는 엔트리를 이따금 청소 > 백그라운드 쓰레드를 이용하거나, 캐시에 새 엔트리를 추가할 경우 부수 작업으로 수행하는 방법이 있음(LinkedHashMap의 removeEldestEntry 메서드)
가비지 컬렉션 언어에서의 메모리 누수 (3) - 리스터(listener) 또는 콜백(callback) 원인 & 해결법
클라이언트가 콜백을 등록만 하고 명확히 해지하지 않으면, 조치하지 않는 한 콜백이 계속 쌓여감
- 콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거해 감(예-WeakhashMap에 키로 저장)
용어
- Garbage Collection(가비지 컬렉션) : 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능
- Entry(엔트리) : 내부 데이터를 의미
- Callback(콜백) : 다른 함수에 인수로 넣을 수 있는 메서드. 이벤트가 발생하면 이에 맞는 작업을 수행하기 위해 코드를 작성
- Listener(리스너) : 특정 이벤트를 처리하는 인터페이스, 이벤트 핸들러라고도 함
- Weak reference(약한 참조) : 명확하게 GC의 회수 대상
아이템 8. finalizer와 cleaner 사용을 피하라
finalizer와 cleaner
자바가 제공하는 객체 소멸자
Java 9부터는 finalizer는 deprecated API로 지정되었으며, cleaner를 대안으로 소개
안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용 - 불확실성과 성능 저하에 유의
finalizer 사용을 피해야 하는 이유
- 예측할 수 없고, 상황에 따라 위험할 수도 있어서 일반적으로 불필요
- finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있음
- finalizer가 있다면 하위 클래스를 이용하여 객체 생성이 생성자에서 예외를 던지더라도 가능할 수도 있음
- final 클래스의 경우 하위 클래스를 만들 수 없으니 공격에서 안전함
- final이 아닌 클래스를 공격에서 방어하려면 아무일도 하지 않는 finalize 메서드를 만들고 final로 선언
cleaner 사용을 피해야 하는 이유
- finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요
finalizer와 cleaner 사용을 피해야 하는 이유
- 즉시 수행된다는 보장이 없음(GC의 알고리즘에 따라 다름) -> 제때 실행되어야 하는 작업은 절대 할 수 없음
- 수행 여부를 보장하지 않음 -> 상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안 됨
- 심각한 성능 문제 동반
아이템 9. try-finally보다는 try-with-resources를 사용하라
회수해야 하는 자원을 다룰 때는 try-finally를 사용하지 말고, try-with-resources를 사용
- 코드가 더 짧고 분명해짐
- 만들어지는 예외 정보도 훨씬 유용
- try-finally를 중첩할 경우, 예외를 잘 가공해주지 않으면 중첩 예외 발생시, 예외 내용이 숨겨질 수 있음(디버깅 어려움)
AutoCloseable 인터페이스
try-with-resource 구조를 사용하려면 해당 자원이 AutoCloseable을 구현해야만 가능
반응형'Java' 카테고리의 다른 글
[Java] 이펙티브 자바 (3) - 클래스와 인터페이스 (0) 2020.04.18 [Java] 이펙티브 자바 (2) - 모든 객체의 공통 메서드 (0) 2020.04.18 [Java] GOF 디자인패턴 용어 정리 (0) 2020.04.16 자바의 I/O (0) 2020.03.13 Stream(스트림) - 8. 스트림의 변환 (0) 2020.03.11