Effective Java 2장 객체의 생성과 호출
Effective-Java 객체의 생성과 호출 (1~7)
ITEM1 : 기본 생성자보다 Static 팩토리 메소드를 만들어라.
- 클래스의 인스턴스(instance) 을 생성하도록 하는 일반적인 방법은 public 생성자(constructor)을 제공하는 것이다.
- boolean primitive 값을 가져와서 Boolean 객체 참조로 변환하여 반환하는 Static Factory
1
2
3
public static Boolean valeOf(boolean b){
return b ? Boolean.TRUE : Boolean.FALSE;
}
- Static Factory 장점
- 기본 생성자와는 다르게 메소드에 이름을 지어줄수 있다.
- 생성자에 전달되는 매개변수가 반환 객체를 잘 나타내지 못하기 때문에, 이름을 잘지은 static 팩토리 메소드가 더 좋음
- ex) BigInteger 클래스에서 소수를 생성하는 probablePrime Static 팩토리 메소드
- 매번 새로운 오브젝트를 생성하지 않아도 된다.
- imutable 불변 클래스의 경우 이미 생성된 인스턴스를 다시 사용할수 있다.
- ex) 싱글톤 팩토리 함수, ( 같은 클래스 생성으로 인해 equals 를 매번 오버라이딩 해주지 않고 그냥 == 로 비교가능함 )
- ex) Boolean.valueOf(boolean) 함수는 매번 새로운 인스턴스를 생성하지 않는다. ( new Boolean(true) 로 한다면 매번 새로운 객체를 생성할 것이다. )
- 자신이 반환하는 타입의 어떤 서브 타입 객체도 리턴 가능함
- ex) Interface-Based framework, Java Collection Framework interface - EnumSet 크기가 커지면 내부적으로 EnumSet 을 상속시킨 JumboEnumSet 을 팩토리 메소드에서 생성해서 알아서 리턴해줌
- ex) Service Provider framework 클래스가 작성되는 시점에 그 메소드로 부터 반환되는 객체의 클래스가 존재하지 않아도 된다.
- 생성자에 변수 타입을 일일이 입력해주어야 하는 불편함을 줄여줄수 있다.
- 기본 생성자와는 다르게 메소드에 이름을 지어줄수 있다.
1
2
3
4
5
Map<Strng, List<String>> = new HashMap<String, List<String>>();
// 아래 처럼 사용 가능하도록!
public static <K,V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
- 생성자에 호출시에 타입 추론처리는 1.6에서는 안된다. 1.8은 되나??
- Static Factory 단점
- 인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질 수 없다
- 다른 여러 스태틱 메소드랑 구별하기 쉽지 않고 코드를 읽기도 쉽지 않음
javadoc 으로 문서화를 잘하고 함수명 convention 을 잘지키면됨
- Static Factory Convention : 아래 함수들은 Static Fatory로 사용된다고 규약을 정한다.
- valueOf : 파라메터와 같은 값을 리턴하는.
- of
- getInstance
- newInstance
- getType
- newType
- Static 팩토리 메소드를 먼저 고려해보고 무심코 public 생성자를 만드는 습관을 버려라.
ITEM2 : 생성자의 매개변수가 많을때는 차라리 빌더를 만들어라.
- 많은 변수를 가지면 Static Factory 나 생성자나 모든 초기화 함수를 만들어 주는데(telescoping construntor)는 한계가 있다.
- 생성자를 간단하게 만들기 위한 방법으로는 Setter 메소드를 만들어주는 JavaBean 패턴이 있다. 초기화를 한후에 setter 로 값을 다 넣어준다.
- 여러번의 메소드 호출로 나누어져 인스턴스가 생성되서 생성과정을 거치는동안 일관된 상태가 유지 되지 못함. ( 모든 초기화가 이루어지기전에 다른 Thread 에서 사용해버릴수도 있다 )
- immutable 클래스를 만들수 없어진다. 즉 Thread 안정성을 유지 할수가없다. 물론 객체를 freeze 하는 함수를 사용할수 있지만 런타임 에러를 만들어 낼 가능성이 있다.
- java 1.5 이상을 쓴다면 기본 Bulder
인터페이스가 존재한다. - 빌더를 사용하면 생성자에는 사용할수없는 가변인자(varargs) 를 사용할수 있다.
- Class 클래스에는 newInstance 라는 추상 팩토리 메소드가 있는데 newInstance 는 항상 객체가 생성될때 클래스의 매개변수가 없는 생성자를 호출하려고 한다. 만약에 그런 생성자가 클래스에 없다면 컴파일 에러가 발생하지 않고 런타임 에러(Instantiation-Exception, IllegalAccessException) 이 발생하므로 컴파일 시점에서 예외 검사를 어렵게 만든다. 빌더를 사용하면 이런 위험을 줄일수 있다.
- 빌더 패턴은 추가적으로 빌더를 생성해야 하므로 생성 비용이 드는 단점이 있다.
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
32
33
34
35
36
37
38
39
40
41
// Builder Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
}
}
// 사용
NutritionFacts cocaCola = new NutritionFacts.Builder(240 ,8). calories(100).sodium(35).carbohydrate(27).build();
- Builder 는 매개변수가 많이 늘어나거나 가변인자를 사용할 경우 고려해보자. telescoping 방법보다 가독성이 좋고 JavaBeans 보다는 휠씬 안전하다.
ITEM3 : 싱글톤 만들때 생성자를 Private 로 해야하고 혹은 enum타입을 통해 만들면 좋다.
- 생성자를 private 로 만들어서 해당 클래스 외부에서 따로 생성하지 못하도록 해야함
1
2
3
4
5
public class Elvis {
public static final Elvis INSTANCE = new Elvis(); // static final!!
private ElvisO { ... } // private 생성자!!
public void leaveTheBuilding() { .. . }
}
- enum 싱글톤을 사용하는 이유?
- enum 없이 싱글톤을 만들면 reflection 을 통해서는 instance 생성자에 접근할수가 있다.
- 싱글톤에서 serializable 을 사용하려면 instance 변수는 transient 로 선언해야 한다. 매번 deserialize 할때마다 instance 에 새로운값이 생성될수 있으므로 transient 를 통해 serialize 할때 제외하도록 설정해 주어야한다.
- java 1.5 이상을 사용하면 enum 을 통해서 싱글톤을 구현하면 좋다. 여러번 초기화 오브젝트가 생성되거나 serialization, reflection attack 등에 상관없이 구현가능하다.
1
2
3
4
5
6
7
public enum Elvis{
INSTNACE;
private String test = "abc"
public void leaveTheBuilding(){
this.test = "ccc"; // INSTANCE.test 도 가능.
}
}
- enum 방식에 싱글톤은 널리 적용되어 있지 않다. 개인적으로도 Serialize 문제가 아니라면 구지 사용할 필요가 없다고 생각된다.
ITEM4 : PRIVATE 생성자로 인스턴스를 생성할수 없게하라.
- java.lang.math 나 java.util.array 같은 util 클래스는 인스턴스가 생성되도록 하면 안되므로 생성자를 piravte 로 선언하면 인스턴스가 생성되는것을 막을수 있다.
- 기본적으로 생성자를 만들지 않아도 자바 컴파일러가 기본 생성자를 만들기 때문에 명시적으로 private 생성자를 선언해주어야한다.
- 추상 클래스를 사용해서 인스턴스를 생성하지 못하도록 하는 방법을 사용하면 안된다. 추상 클래스를 상속해서 인스턴스를 생성 할 수 도 있고 클래스가 마치 상속을 위해 생성된거처럼 잘못 알게된다.
- private 생성자를 만들면 subclass 를 만들수 없으므(컴파일 에러 발생)로 항상좋은건 아니다.
1
2
3
4
5
6
7
public class UtilityClass {
// 디폴트 생성자가 만들어지는것을 방지
private UtilityClass() {
// 혹시나 불러질 가능성 대비해서 (클래스 내부에서) 에러 발생
throw new AssertionError();
}
}
ITEM5 : 필요없는 오브젝트가 생성되는것을 피해라
- Immutable 객체는 항상 재사용이 가능함!!
1
2
3
4
5
6
String s = new String("abc"); // 매번 새로운 객체 생성
String s = "abc"; // "abc" 스트링 풀에서 공부
Boolean.valueOf(String); // 새로운 인스턴스를 생성하지 않지만
new Boolean(String) // 생성자는 새로운 인스턴스를 생성
// Static 팩토리를 사용하는것이 좋다.
- 가변 객체도 객체의 상태가 변경되지 않는다면 static{ } 블락에서 초기화 한후 재사용하자.자주 호출되는 함수에서 매번 객체를 생성하는 일은 하지 않는게 좋다.
- immutable object 는 매번 인스턴스를 만들지 말고 static 블록에서 초기화한후 공유해서 사용해야 한다.
- Map 에 keySet 함수도 매번 새로운 Set를 만들어서 리턴하지 않고 매번 같은 인스턴스를 리턴함.
- java 1.5 이상에서 제공하는 autoboxing 이 일어나지 않도록 해라 Long 과 primitive long 간에 boxing 이 일어나면 추가적인 오버헤드가 발생한다.
1
2
3
4
5
6
7
public static void main(String[] args) {
Long sum =0L;
for (1ong i = 0; < Integer.MAX_VALUE; i++) {
sum += 1; // autoboxing 일어남.
System.out.println(sum);
}
}
ITEM6 : 쓸모없는 Object refreences 를 없애라
- 자바는 C/C++ 과는 다르게 더이상 참조되지 않으면 객체들이 사용하던 메모리가 자동으로 회수된다. 마법은 아니므로 주의!
1
2
3
4
5
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 쓸모없는 reference 를 제거해주어야한다.
- null 처리를 해주면 참조를 제거해서 메모리에서 사라지도록 할수있고, 잘못된 참조로 원하지 않게 작동하지 않고 NullPointerException 이 바로 일어나도록 할수있다.
- 클래스가 자기자신의 메모리를 관리할때 프로그래머가 메모리 릭에 대해 조심해야한다. 특정 element 가 free 되면 object reference 들은 모두 null 처리가 되어져야 한다.
- 캐쉬 상황에서 메모리 릭이 발생하기 쉽다. 캐쉬 외부에 캐쉬의 키에 대한 참조가 있을 동안만 캐시에 저장된 항목이 유효한 캐쉬를 구현해야한다. WeakHasMap을 캐시로 사용하면 key값의 외부 참조에 따라 결정되도록 할수있다. (weak reference)
- 콜백과 리스너에서도 메모리릭이 발생하기 쉽다. 명시적으로 콜백을 deregister 시키지 말고 weak reference 를 사용하거나 키값들을 weakHashMap 을 사용해 저장하라.
ITEM7 : finalizer 사용하지 마라.
- Java의 finalizer 는 C++ 의 소멸자가 아니다!
- 신속하게 실행된다는 보장이 없다.
- 혹시나 사용할경우 : 생성된 객체를 종료하는 메소드 호출이 재대로 동작하지 않을경우에 대한 대비 ex) FileInputStream, Timer, Connection 에는 finalizer 가 있다.