brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Feb 19. 2018

Effective Java - 객체의 생성과 소멸

#01 생성자 대신 static 팩토리 메서드 사용을 고려하자

Effective Java - 객체의 생성과 소멸


#01 생성자 대신 static 팩토리 메서드 사용을 고려하자


클래스의 인스턴스(instance)를 생성하기 위해서는 기본 생성자를 이용할 수 있습니다. 

기본 생성자를 대신하여, public static 팩토리 메서드를 만들어서 인스턴스를 생성할 수 있습니다.


#01 기본 생성자 & public static 팩토리 메소드
#02 인스턴스 생성


public static 팩토리 메서드를 이용한 인스턴스 생성의 장단점에 대해 알아보겠습니다.




장점 #01. 생성자와 달리 자기 나름의 이름을 가질 수 있습니다.


생성자를 이용하다 보면, 클래스명이나 매개 변수만으로 해당 인스턴스를 파악하기 힘든 경우가 있습니다.

public static 팩토리 메서드를 이용하면 직접 이름을 지을 수 있기 때문에 좀 더 명확하게 인스턴스를 파악할 수 있습니다.


아래 코드를 보면 BigInteger(int, int, Random) 생성자를 통해 소수인 BigInteger 인스턴스를 생성할 수 있습니다.


#03 BigInteger probablePrime 메서드


하지만 생성자 만으로는 어떤 인스턴스인지 파악하기에 힘듭니다.

이 경우 BigInteger.probablePrime이라는 public static 팩토리 메서드를 이용하면 이해하기 더 수월해집니다.


#04 BigInteger 인스턴스 생성


기본 API 문서를 봐도 아래와 같이 기본 생성자가 아닌 별도의 메서드를 통해 구현하는 것을 추천합니다.

#05 Java docs


만약 동일한 매개변수를 통해 서로 다른 인스턴스를 생성해야 한다면, 생성자가 정의된 클래스의 문서를 봐야만 그 코드를 이해할 수 있을 것입니다.

아래와 같이 동일한 String 매개변수(경로 or 이름)를 통해 Video 인스턴스를 생성해야 한다면, 별도의 public static 팩토리 메서드를 통해 차이점을 부각하도록 만들 수 있습니다.


#06 from & find 메서드
#07 Video 인스턴스




장점 #02. 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다.


불변(immutable) 클래스의 경우 이미 생성된 인스턴스를 다시 이용할 수 있기 때문에 굳이 새로운 인스턴스를 재생성할 필요는 없습니다.

이를 위해 이미 생성된 인스턴스를 저장했다가 다시 이용할 수 있습니다.


예를 들어, Boolean.valueOf(boolean) 메서드에서는 아래와 같이 이미 만들어진 Boolean TRUE/FALSE 인스턴스를 이용하기 때문에 매번 Boolean 인스턴스를 재생성하지 않습니다.


#08 Boolean valueOf 메서드


만약 인스턴스 생성 시 리소스가 많이 소모되는 경우에는, 이와 같은 방법으로 성능을 향상할 수 있습니다.


public static 팩토리 메서드를 이용해 여러 번 호출되더라도 이미 생성된 동일 인스턴스를 반환하거나, 직접 제어하여 싱글톤이나 인스턴스 생성 불가 클래스로 만들 수 도 있습니다.





장점 #03. 자신의 클래스 인스턴스만 반환하는 생성자와 달리, static 팩토리 메서드는 자신이 반환하는 타입의 어떤 서브타입 인스턴스도 반환할 수 있다.


반환되는 객체의 클래스를 선택해야 할 때 뛰어난 유연성을 제공합니다.


개념이 모호한데 기본 생성자는 해당 클래스의 인스턴스만을 반환하는 반면, static 팩토리 메서드는 해당 클래스의 인스턴스뿐만 아니라 자신이 원하는 타입의 인스턴스를 반환할 수 있습니다.


public static 팩토리 메서드에서는 반환되는 인스턴스의 클래스가 public이 아닌 inner 클래스가 될 수 있고, static 팩토리 메서드에 전달되는 매개변수의 값에 따라 다양한 클래스의 인스턴스를 반환할 수 있습니다.

서브 타입에 해당되는 클래스라면 어떤 것이든 반환 가능합니다.


이를 통해 객체의 클래스를 버전에 따라 다양하게 활용할 수 있어서 유지보수가 용이하고 성능이 향상될 수 있습니다.


예를 들어, 아래와 같이 java.util.EnumSet 클래스는 public 생성자가 없고 static 팩토리 메서드들만 있으며, 크기에 따라 서로 다른 인스턴스를 반환합니다.

static 팩토리 메서드에서 열거형 요소수가 64개 이하면 RegularEnumSet, 65개 이상이면 JumnoEnumSet 인스턴스를 반환합니다.


#09 EnumSet static 팩토리 메서드


이를 호출하는 클라이언트 입장에서는 static 팩토리 메서드 내부에서 실제로 생성되고 반환되는 클래스의 인스턴스를 알 수 없습니다.

만약 RegularEnumSet 보다 더 나은 클래스가 있다면, 추후 Java 버전에서 추가되어 static 팩토리 메서드를 통해 반환할 수 도 있습니다.

클라이언트 입장에서는 그저 EnumSet의 서브클래스라는 것만 인지하고 있으면 됩니다.




장점 #04. 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.


매개변수화 클래스의 생성자를 호출할 때는 타입 매개변수를 지정해야 합니다.

예를 들면 아래와 같이 생성자를 통하면 타입 매개변수를 두 번 지정해야 합니다.

Map<String, Integer> map = new HashMap<String, Integer>();


static 팩토리 메서드를 이용하면 컴파일러가 타입 매개변수를 해결하도록 할 수 있습니다.

public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}
책에서는 Java 6 기준으로 작성되었기 때문에 Map의 타입 추론이 안되는데, Java 8부터는 기본 지원하고 있습니다.




단점 #01. 인스턴스 생성을 위해 static 팩토리 메서드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질 수 없습니다.


상속을 구현하려면 상위 클래스의 생성자를 호출해야 하는데, 접근할 수 없기 때문에 서브 클래스를 가질 수 없고 상속을 이용할 수 없습니다.


따라서 상속 대신 컴포지션을 이용해야 하는데, 이는 오히려 장점이 될 수 있습니다.

이에 대한 자세한 내용은 추후에 기재하겠습니다.




단점 #02. 다른 static 메서드와 쉽게 구별할 수 없습니다.


사용성 측면에서 단점이 발생할 수 있습니다.

생성자의 경우엔 클래스명과 동일하기 때문에 쉽게 구별할 수 있지만, static 팩토리 메서드는 다른 메서드와 섞여있어 잘 구분되지 않을 수 있습니다.

따라서 javadoc에서 정의하거나, 주석으로 표시하거나, 공통적인 작명 규칙을 만들어서 이를 해결해야 합니다.


static 팩토리 메서드의 공통 명칭 사용 예를 아래와 같습니다.


valueOf - 자신의 매개 변수와 같은 값을 갖는 인스턴스를 반환한다.

#10 String valueOf


getInstance - 매개 변수에 나타난 인스턴스를 반환하지만, 매개 변수와 같은 값을 갖지 않을 수 있다.
싱글톤의 경우 매개 변수가 없고 오직 하나의 인스턴스만 반환한다.

#11 Calendar getInstance


newInstance - getInstance와 유사하나 반환되는 각 인스턴스가 서로 다르다.

#12 Class newInstance




static 팩토리 메서드는 public 생성자와 비교에 여러 장단점이 있습니다.

따라서 상황에 따라 static 팩토리 메서드를 잘 활용하는 것이 중요합니다.




#01 Effective Java


작년 회고에 남겼듯이, 개인적으로 토이 프로젝트를 진행함과 동시에 Effective Java 책을 공부하고 정리하려 합니다.

주위에 물어보니 이 책을 읽다 보면 현자 타임이 매번 온다는데 완독 한번 해보렵니다 ㅎㅎ

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari