일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 회고
- 인프런
- 오라클
- 독서리뷰
- javascript
- Oracle
- 람다
- Head First Design Pattern
- Eclipse
- 카카오톡1차
- 인강리뷰
- 디자인패턴
- 독서
- 우아한테크코스
- Design Pattern
- math
- 공부
- 이펙티브자바
- 인코딩
- 자바
- 알고리즘
- 프로그래머스
- study
- Singleton
- 매핑
- Java
- 후기
- 에러
- spring
- JPA
- Today
- Total
Lee's Grow up
[EFFECTIVE JAVA 3/E] 객체 생성자 파괴 본문
객체를 만들어야 할 때와 만들지 말아야 할 때를 구분하는 법, 올바른 객체 생성 방법과 불필요한 생성을 피하는 방법, 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리하는 요령을 알아봅니다.
아이템 목록
- 생성자 대신 정적 팩토리 메소드를 고려하라
- 생성자에 매개변수가 많다면 빌더를 고려하라
- private 생성자나 열거 타입으로 싱글턴임을 보증하라
- 인스턴스화를 막으려거든 private 생성자를 사용하라
- 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
- 불필요한 객체 생성을 피하라
- 다 쓴 객체 참조를 해제하라
- finalizer와 cleaner 사용을 피하라
- try - finally 보다는 try - with - resources를 사용하라
1. 생성자 대신 정적 팩토리 메소드(static factory method)를 고려하라
- 클래스는 생성자와 별도로 그 클래스의 인스턴스를 반환하는 단순한 정적 메소드를 제공할 수 있다.
-
정적 팩토리 메소드는 디자인 패턴의 팩토리 메소드와 다르다. 어떤 디자인 패턴중 일치하는 패턴은 없다.
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
장점
- 이름을 가질 수 있다
생성자 자체와 넘겨주는 매개변수 많으로 생성자의 특성을 쉽게 알 수가 없다.LeeGrowUp()
,LeeGrowUp(int)
,LeeGrowUp(int,String)
해당 생성자를 보고 각각의 생성자가 어떤 역할을 하는지 특성을 알수 있는가? 반면 _정적 팩토리_는 이름만 잘 지으면 반환될 객체의 특성을 묘사할 수 있다.BigInteger(int, int,Random)
와BigInteger.probaleprime( )
중 어떤 것이 소수 BigInteger 를 반환한다는 의미가 명확한지 생각해 보자.
- 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. 이 덕분에 불변 클래스는 불필요한 객체 생성을 피할 수 있고, 인스턴스 통제 클래스로 만들 수 있다. 인스턴스 통제 (instance-controlled) 클래스는 인스턴스를 통제할 수 있는 클래스를 말한다.
싱글톤(singleton)
,인스턴스화 불가 (noninstantiable)
,불변 값 클래스
에서 동치인 인스턴스가 단 하나임을 보장할 수 있다 ( a == b 일 때만 a.equals(b)가 성립 ) , 또한 인스턴스 통제는플라이웨이트 패턴
의 근간이 된다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. 이 능력은 반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 유연성을 선물하고, API를 만들 때 이 유연성을 응용하면 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있다. 이는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다. ( 반환 타입의 하위타입이면 된다. )
EnumSet
클래스는 생성자 없이 정적 팩토리만 제공하는데, 원소의 수에 따라 하위 클래스 중 하나의 인스턴스 반환 ex )EnumSet
원소가 64개 이하이면RegularEnumSet
, 65개 이상이면JumboEnumSet
를 반환
- 정적 팩토리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
- 이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다 ( JDBC 등 ) 이런 서비스 제공자 프레임워크 패턴에는
브리지 패턴(Bridge pattern)
,의존 객체 주입(dependency injection, 의존성 주입)
등이 있다.
- 이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다 ( JDBC 등 ) 이런 서비스 제공자 프레임워크 패턴에는
단점
- 상속을 하려면
public
이나protected
생성자가 필요하니 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
상속보다 컴포지션을 사용하도록 유도하고, 불변타입으로 만드려면 이 제약을 지켜야 한다는 점에서 강점일수도 있다. - 정적 팩토리 메소드는 프로그래머가 찾기 어렵다. 생성자처럼 API에 명확히 드러나지 않는다, 정적 메소드는 메소드일 뿐으로
docs
에서 특별하게 취급하지 않는다. 따라서 사용자가 인스턴스화하려고 했는데, 생성자가 없으면static factory method
를 찾으면 된다. 아래는 정적 팩토리 메소드의 명명 방식들이다.from
: 매개변수 하나 받아서 인스턴스화ex Date.from ()
of
: 여러개의 매개변수를 받아서 인스턴스화ex EnumSet.of ()
instance / getInstance
: 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.create / newIntance
:getInstance
와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장getType
:getInstance
와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 사용newType
:newInstance
와 같으나, 생성할 클래스가 아닌 클래스에 팩토리 메소드를 정의할때 사용type
:getType
와newType
의 간결한 버전
결론
정적 팩토리 메소드와 public 생성자도 각자의 쓰임새가 있으니 장단점을 이해하고 사용하자, 그래도 정적 팩토리를 사용하는게 더 유리한 경우가 많으므로 public
생성자를 제공하던 습관이 있다면 고치자
2. 생성자에 매개변수가 많다면 빌더를 고려하라
- 생성자와 정적 팩토리 메소드는 동일한 단점을 가진다, 매개변수가 많이 필요한 경우이다.
2-1. 점층적 생성자 패텬
NutritionFacts tomato = new NutritionFacts('tomato', 100, 1, 2, 3, 4);
NutritionFacts banana = new NutritionFacts('banana', 200, 4, 3, 2, null);
N번째 인자에 넘기는 값이 무엇인지 해당 생성자의 시그니처를 봐야 알 수 있다. 또한 매개변수가 많아질 수록 많은 그에 따른 생성자를 작성해야 하고, 복잡해진다.
2-2. 자바빈 패턴
NutritionFacts tomato = new NutritionFacts();
tomato.setName("tomat");
tomato.setFat(23);
현재 VO로 많이 사용하는 방식이며, 객체 하나를 만들려면 메소드를 여러 번 호출해야 하고, 객체가 완전히 생성되기 전까지 일관성(consistency)
가 무너진 상태에 놓이게 됩니다. 또한 쓰레드 안정성을 얻으려면 추가 작업이 필요
2-3. 빌더 패턴
NutritionFacts tomato = new NutritionFacts.Builder()
.name("tomato")
.fat(23)
.build();
필수 인자를 빌더의 생성자에 인자로 선언해주고, 나머지는 빌더의 세터로 사용. 체이닝 기법을 사용하기 때문에 가독성이 좋고, 각각의 인자가 무엇을 의미하는지 명확하다. 또한 객체의 불변화 가능
3. private 생성자나 열거 타입으로 싱글톤임을 보증하라
싱글턴(singleton)
이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.
3-1. public static file 필드 방식의 싱글턴
public static final LeeGrowUp INSTANCE = new LeeGrowUp();
이와 같이 선언하게 되면 한번만 호출되기 때문에 LeeGrowUp.INSTANCE
는 싱글턴이 보장된다.
3-2. static factory method 방식의 싱글턴
public class LeeGrowUp {
private static final LeeGrowUp INSTANCE = new LeeGrowUp();
public static LeeGrowUp getinstance( return LeeGrowUp; )
}
이 방식은 API의 변경이 유연하며, 정적 팩토리를 제네릭 싱슬턴 팩토리르 만들수 있다는 점이다, 또한 정적 팩토리의 메소드 참조를 공급자로 사용할 수 있다는 점이다. 이러한 장점들이 굳이 필요하지 않다면 public
필드 방식이 좋다.
3-3. enum 타입 방식의 싱글턴 - 바람직한 방법
public enum LeeGrowUp{
INSTANCE;
}
대부분의 상황에서는 원소가 하나뿐인 enum
타입이 싱글턴을 만드는 가장 좋은 방법이지만, 싱글턴이 Enum외의 클래스를 상속해야 한다면 이방법은 사용 불가
위 3-1
,3-2
의 경우 직렬화 또는 리플렉션의 공격에대한 별도의 처리가 필요하지만 3-3
의 경우는 위 두가지 상황을 추가 노력 없이 방지해준다.
4. 인스턴스화를 막으려거든 private 생성자를 사용하라
- 정적 메소드와 정적 필드만을 담은 클래스를 만들고 싶을 경우 ex
java.lang.Matha
,java.util.Array
- 컴파일러가 자동으로 기본 생성자를 추가해주므로, 사용자가 해당 클래스를 인스턴스화 할 가능성이 생긴다. 그렇기 때문에
private 생성자
를 명시적으로 추가해주자. 또한 상속도 불가능하게 막을 수 있다.
5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
많은 클래스가 하나 이상의 자원에 의존한다. 이럴 때 정적 유틸리티 클래스나 싱글턴으로 구현하면 문제가 발생 아래 2가지의 예제이다.
5-1. 정적 유틸리가 잘못 사용 된 예
public class SpellChecker {
private static final Lexicon dictionary = new Dicionary();
private SpellChecker() { }
}
5-2. 싱글톤이 잘못 사용 된 예
public class SpellChecker {
private final Lexicon dictionary = new Dicionary();
private SpellChecker( ... ) { }
public static SpellChecker INSTANCE = new SpellChecker(...);
}
자원에 따라 동작이 달라야 한다고 가정할 때 위와 같이 사용하면 적합하지가 않다. 이럴 경우 의존성 주입을 통해 필요한 자원을 정적 팩토리 메소드 또는 빌더로 넘겨주는 방식이 좋다.
5-3. 의존 객체 주입 예
public class SpellChecker {
private final Lexicon dictionary ;
private SpellChecker( Lexicon dictionary ) {
this.dictionary = dictionary;
}
}
위 패턴의 변형으로 의존 객체가 아닌 Factroy를 넘겨주는 방식도 있다. 즉 팩토리 메소드 패턴 (Factory Method Pattern)이 있다.
public class SpellChecker {
public SpellChecker(Supplier <? extends Lexicon> dicFactory){
this.dictionary = dicFactory.get();
}
}
6. 불필요한 객체 생성을 피하라
String str = new String("LeeGrowUp");
보다 String str = "LeeGrowUp";
가 훨씬 좋다. 전자의 경우 선언시 새로운 인스턴스가 만들어지고 메모리 영역을 할당받지만, 후자의 경우 상수풀 영역에 올라가기 때문에 같은 문자열의 경우 재사용이 가능하다. 같은 이유로 new Boolean(String)
보다 Boolean.valueOf(String)
가 좋다.
7. 다 쓴 객체 참조를 해제하라
GC 를 사용하는 언어라고 메모리 관리에 더 이상 신경을 쓰지 않아도 되는게 절대 아니다.
자칫 메모리 누수
가 일어나 시스템의 성능에 저하가 발생할 수 있다.
해결 방안
- 다쓴 객체는
null
처리를 해준다.NullPointerException
을 통한 오류 검출의 이점도 존재, 단 객체 참조를null
처리하는 일은 예외적인 경우에만 해당 :Stack 클래스
- 가장 좋은 방법은 변수의 유효 범위
scope
밖으로 밀어내는 것, GC가 자동으로 객체를 소멸 - 캐쉬 또한 메모리 누수의 주범이다. 엔트리가 살아 있는 캐쉬가 필요한 상황이 아니라면
WeakHashMap
을 사용하여 캐시 생성, 또는LinkedHashMap.removeEldestEntry
등 사용을 권장. 즉Weak reference
로 사용하거나Weak reference
를 사용해 구현된 API를 이용
8. finalizer와 cleaner 사용을 피하라
- 자바에서는 객체의 소멸을 기본적으로
Garbage Collection
이 관리하지만,finalizer
과cleaner
을 제공해준다.finalizer
의 경우java 9
부터 사용 자체를deprecated
로 지정 - 자바에서의
finalizer
과cleaner
의 경우 C나 C++의 소멸자라고 생각하면 안된다. 자바에서는 언제 실행이 될지 예측할 수 없고, 상황에 따라 위험[Thread Stop]
할수가 있고 상당히 성능을 저하 시키기 때문에 일반적인 경우 불필요 - 그렇다고 완전 필요 없는건 아니다,
AutoCloseable
클래스 같은 경우 안전망으로clener
을 사용
결론
cleaner
또는finalizer
의 경우 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자, 물론 이런 경우라도 불확실성과 성능 저하에는 주의가 필요
9. try-finally 보다는 try-with-resources를 사용하라
자바 라이브러리에는 InputStream
,OutputStrea
등 close()
메소드를 호출해 직접 닫아줘야 하는 자원이 많이 존재한다. 그러나 실제 뛰어난 프로그래머라도 실수를 많이 하기 때문에 try-with-resources
를 추천
결론
꼭 회수해야 하는 자원을 다룰 때는 try-finally
말고 try-with-resources
를 사용, 예외는 없다. 코드의 가독성 안정성등이 훨씬 보장된다.