객체 생성과 파괴
아이템1
생성자 대신 정적 팩토리 메서드를 고려하라
클래스는 생성자와 별도로 정적 팩토리 메서드(static factory method)를 제공할 수 있다.
래퍼(wrapper)
기본 자료형(int, double, boolean 등)의 데이터를 인스턴스(객체)로 만들기 위해 사용(포장) 하는 클래스
사용 이유
1. 제네릭, 자료구조, 매개변수 등 기본 자료형이 아닌 레퍼런스 타입을 필요로 하는 경우가 많고 메서드를 갖고 있어 다양하게 활용이 가능하기 때문
2. 인스턴스를 생성(heap 메모리에 저장)하여 상속 및 재사용이 가능
3. 문자열(String)과 기본 잘형 간 형 변환하여 사용이 가능
박싱(boxing)
기본 자료형의 데이터를 래퍼(wrapper) 클래스의 객체로 만드는 과정
ex) int -> Integer
int primitiveInt = 1;
Integer wrapperInt = Integer.valueOf(primitiveInt);
언박싱(un-boxing)
래퍼(wrapper) 클래스의 데이터를 기본 자료형으로 얻어내는 과정
ex) Integer -> int
Integer wrapperInt = new Integer(0);
int primitiveInt = wrapperInt.intValue();
정적 메서드 예제
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
정적 팩토리 메서드의 장점
1. 이름을 가질 수 있다.
반환될 객체의 특성을 쉽게 묘사 가능
ex) 생성자 BigInteger(int, int, Random)과 정적 팩토리 메서드인 BigInteger.probablePrime 중 어는 쪽이 값이 소수인 BigInteger를 반환하는 의미를 더 잘설명되어있는 이름이 무엇인지?
public class Foo {
String name;
public Foo(String name) {
this.name = name;
}
public static Foo withName(String name) {
return new Foo(name);
}
public static void main(String[] args) {
// 생성자 보다 정적 팩토리 메서드가 눈에 읽기 편하다.
Foo foo = new Foo("sh");
Foo foo2 = Foo.withName("sh");
}
}
생성자는 시그니처에 제약이 있다. 똑같은 타입을 파라미터로 받는 생성자 두 개를 만들수 없으니깐 정적 팩토리 메서드를 사용하는게 유용하다.
// 동일한 파라미터 생성자 때문에 컴파일 오류
public Foo(String name) {
this.name = name;
}
// 동일한 파라미터 생성자 때문에 컴파일 오류
public Foo(String address) {
this.address = address;
}
// 동일한 파라미터 생성자 보다는 정적 팩토리 메서드 사용하는것이 유용함
public static Foo withAddress(String address) {
Foo foo = new Foo();
foo.address = address;
return foo;
}
2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
불변 클래스(immutable class)는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 새성을 피할 수 있다.
public Foo() {
}
private static final Foo GOO_FOO = new Foo();
public static Foo getFoo() {
return GOO_FOO;
}
결과
Foo foo1 = new Foo("sh");
Foo foo2 = new Foo("sh");
Foo foo3 = Foo.getFoo();
Foo foo4 = Foo.getFoo();
foo1: Foo@2a139a55
foo2: Foo@15db9742
foo3: Foo@6d06d69c
foo4: Foo@6d06d69c
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
클래스에서 만들어 줄 객체의 클래스를 선택하는 유연함이 있다.
클라이언트의 입장에서는 인터페이스만 가지고 사용하기 때문에 개념적인 무게가 줄어들었고
클래스 입장에서는 public으로 제공해야 할 API가 줄었다.
개념적인 무게란 : 프로그래머가 API를 사용하기 위해 익혀야 하는 개념의 수와 난이도
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
public static Foo getFoo(boolean flag) {
return flag ? new Foo() : new BarFoo();
}
static class BarFoo extends Foo {
}
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
정적 팩토리 메서드의 단점
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
이 제약은 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야한다는 점에서 오히려 장점으로 받아들임
2. 정적 팩토리 메서드는 프로그래머가 찾기 힘들다
메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 해결해야함