생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사 할 수 있다.
호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다. 생성 비용이 큰 객체가 자주 요청되는 상황에서 성능을 상당히 끌어올려 준다. 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 통제가 가능하다.(인스턴스 통제 클래스)
반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. API를 만들 때 구현 클래스를 공개하지 않고도 그 객체를 반환 가능하다.
입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
정적 팩터리 메서드에 흔히 사용하는 명명 방식 들
from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드.
of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드.
valueOf : from 과 of의 더 자세한 버전.
instance 또는 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음.
create 또는 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환 보장.
getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의 할 때 쓴다.
newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의 할 때 쓴다.
2. 생성자에 매개변수가 많다면 빌더를 고려하라.
점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다. ( 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇 개 인지도 주의해서 세어 보아야 함 )
자바빈즈 패턴(JavaBeans pattern)인 경우, 매개변수가 없는 생성자로 객체를 만든 후 세터(setter) 메서드들을 호출하여 원하는 매개변수의 값을 설정하는 방식인데, 객체 하나를 만들려면 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게된다.(생성자에서 유효성 검사 후 일관성 유지X)
생성자는 private으로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 생성.
public static 멤버가 final 필드인 방식 ( private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번 호출 )
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {};
}
예외가 있다면, 리플렉션 API를 이용하여 private 생성자 호출 가능.
정적 팩터리 방식의 싱글턴 방식
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {};
public static final getInstance() { return INSTANCE; }
}
인스턴스 필드를 일시적(transient) 이라고 선언하고 readResolve 메서드를 제공해야한다.
위의 방식대로 하지 않을 경우, 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어 진다.
열거 타입 방식의 싱글턴
public enum Elvis {
INSTANCE;
// enum 클래스도 멤버 변수 또는 메서드를 가질 수 있다.
}
위 방식의 경우 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이방법은 부적절 하다. ( 인터페이스는 가능 )
4. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.
어떤 클래스가 사용하는 리소스에 따라 행동을 달리 해야 하는 경우에는 스태틱 유틸리티 클래스와 싱글톤을 사용하는 것은 부적절하다.
인스턴스를 생성하는 시점에 필요한 자원을 넘겨주는 방식.
필요한 자원을 생성자 또는 정적 팩터리나 빌더에 넘겨주어서 클래스의 유연성, 재사용성, 테스트 용이성을 개선.
5. 불필요한 객체 생성을 피하라.
똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
Boolean(String) 생성자 대신 Boolean.valueOf(String) 팩터리 메서드를 사용하는 것이 좋다.
생성 비용이 아주 비싼 객체가 있을 경우, 비싼 객체가 반복해서 필요하다면 캐싱하여 재사용한다.
static boolean isRomanNumeral(String s) {
// 정규표현식으로 주어진 문자열이 유효한 로마 숫자인지 확인하는 방법
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
// String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만,
// 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.
// 메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한번 쓰고 버려져서 곧바로
// 가비지컬렉션 대상이 된다.
}
// 성능 개선 코드
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.complie(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$" );
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
오토박싱된 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
6. 다 쓴 객체 참조를 해제하라.
자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다. 다 사용한 즉시 그 원소가 참조한 객체들을 다 null 처리 해줘야 한다.
맴버변수가 아닌 지역변수는 굳이 null 처리 할 필요는 없다. scope에서 나올 경우 바로 GC 대상이 된다.
cache 또한 메모리 누수를 일으키는 주범이 될 수 있다. 이럴 경우 weakHashMap을 사용하여 키가 참조하는 동안만 엔트리가 유효하고 키가 유효하지않은(gc 또는 null) 상황이 되면 엔트리에서 그 즉시 자동으로 삭제될 것이다.
Java에서는 두가지 객체 소멸자를 제공한다. 하나는 finalizer (실행을 예측할 수 없고, 상황에 따라 위험) 또 하나는 cleaner ( 마찬가지로 예측이 불가능하며 느리고 일반적으로 불필요 )
finalizer 메서드안의 로직을 실행 하면서 GC의 수행시간이 지연될 수 있다.
Java 9에서는 finalizer를 사용자제(deprecated) API로 지정 그러나 자바 라이브러리에서는 여전히 사용중.
AutoCloseable 인터페이스의 @overrid close() 메서드를 활용하여 안정망으로 사용하자.
cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수 용으로만 사용을 권고.
// Cleaner 객체 생성
private static final Cleaner cleaner = Cleaner.create();
// cleanable 객체 생성. 수거 대상이 되면 실행
private final Cleaner.Cleanalbe cleanable;
// 수거 쓰레드 등록
cleanable = cleaner.register( this, new Runnable {...} );
8. try-finally 보다는 try-with-resources를 사용하라.
자바 라이브러리에는 close 메서드를 사용하여 직접 닫아줘야 하는 API들이 있다.(InputStream, OutputStream, java.sql.Connection 등)
JDK 7 에서 부터 나온 try-with-resources 사용한다면, 굳이 try-finally 구문을 사용하지 않고 자원이 회수가 가능하며, 에러 스텍 추적 내역에 supprssed 라는 문구와 함께 예외가 누적되어 표현가능하여 에러 추적에 효율적이다.
AutoClosable 인터페이스를 상속 받아 구현된 객체일 경우, try-with-resources를 사용 할 수 있다.
Closable 인터페이스에도 JDK6에서는 아무것도 상속받지 않았지만 7버전에서 부터는 AutoClosable 인터페이스를 상속 받도록 변경되었다.
그로 인해 기존의 Closable 인터페이스를 상속 받고 있는 객체 였다면 try-with-resources를 사용 가능하다.