자바/이펙티브 자바

객체 생성과 파괴

개발자0 2022. 3. 5. 19:45

아이템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. 정적 팩토리 메서드는 프로그래머가 찾기 힘들다

메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 해결해야함