1. 이름을 갖을 수 있다
예를 들어 BigInteger.probablePrime
은 값이 소수인 big integer를 반환한다는 것이 명확하게 드러난다.
시그니처가 같은 여러 생성자가 필요할 것 같으면 생성자를 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 만들면 된다.
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
인스턴스를 미리 만들어놓고 새로 생성한 인스턴스를 캐싱하여 활용할 수 있다. 예를들어 Boolean.valueOf(boolean)
메서드는 객체를 생성하지 않는다. 즉 인스턴스를 통제할 수 있어서 싱글턴 또는 인스턴스화 불가한 클래스로 만들 수 있다.
3. 반환 타입의 하위 타입 객체를 반환해도 된다.
이 능력을 통해 사용자에게 엄청난 유연성을 제공할 수 있다. 구현 클래스를 공개하지 않고 API를 작게 유지할 수 있다. 또한 이를 사용하는 클라이언트는 그 클래스를 인터패이스로만 다루게 된다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반활할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 제공자가 계속해서 기능이 업데이트된 클래스를 반환하더라도 클라이언트는 이에 신경쓰지 않고 사용할 수 있다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
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 NutritionFacts(int servingSize, int servings){
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories){
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat){
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium){
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate){
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
다음은 선택 매개변수가 많을 때 활용할 수 있는 자바빈즈패턴을 사용해보았다.
class NutritionFacts {
private int servingSize; // 필수
private int servings; // 필수
private int calories; // 선택
private int fat; // 선택
private int sodium; // 선택
private int carbohydrate; // 선택
// 자바빈즈패턴
public NutritionFacts() {
this.servingSize = 0;
this.servings = 0;
this.calories = 0;
this.fat = 0;
this.sodium = 0;
this.carbohydrate = 0;
}
//setters
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
점증적 생성자 패턴의 안정성과 자바 빈즈 패턴의 가독성을 겸비한 빌더 패턴이다.
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; // 선택
private NutritionFacts(Builder builder){
servings = builder.servings;
servingSize = builder.servingSize;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
// 빌더 패턴
public static class Builder{
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 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 sodium (int val){
sodium = val;
return this;
}
public Builder carbohydrate(int val){
carbohydrate = val;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
}
다음은 해당 클래스를 사용하는 클라이언트 코드이다
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
똑같은 기능의 객체를 매번 생성하기보다는 객체 하느를 재사용하는 편이 나을 때가 많다.
String s = new String("hello");
이 코드는 실행될 때마다 String인스턴스를 새로 만든다.
이 문장이 반복문 안에 있다면 쓸데없는 String 인스턴스가 수백만 개 만들어질 수도 있다.
String s = "hello";
이 코드는 새로운 인스턴스를 만드는 대신 하나의 String 인스턴스를 사용한다.
예를 들어 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})$");
}
여기서의 문제점은 matches 메서드가 내부에서 사용하는 Pattern 인스턴스가 계속해서 생성되고 가비지 컬렉션 대상이 된다는 것이다.
Parrern 인스턴스는 입력받은 정규표현식에 해당하는 finate state machine을 만들기 때문에 인스턴스 생성 비용이 높다.
성능을 개선하려면 불변인 Pattern 인스턴스 클래스를 캐싱하는 방법이다.
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)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();
}
다음과 같이 static 변수에 해당 인스턴스를 초기화하여 올려놓고 계속해서 재사용한다면 성능을 크게 향상시킬 수 있다.
불필요한 객체를 만들어내는 또 다른 예로 오토박싱(auto boxing)을 들 수 있다. 오토박싱은 프로그래머가 기본타입과 박싱된 기본타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.
//시간 측정
long start = System.currentTimeMillis();
Long sum = 0L;
for(long i = 1; i <= Integer.MAX_VALUE; i++){
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("실행 시간 : " + (end - start) / 1000.0);
다음 코드에서는 sum변수에 문제가 있다 Long type는 박싱된 기본타입으로 int의 max value까지 총합을 보관하는 변수이다.
여기서 보면 이 Long타입에 long(기본타입)이 계속해서 더해지고 있는데 이때마다 오토박싱이 일어나면서 불필요한 객체가 생성되고 있다.
내 컴퓨터에서 해당 코드는 3.239초의 실행시간이 걸렸고 sum의 타입을 long으로 바꾸었을 때 0.74초의 실행시간으로 크게 줄어들었다.
박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 실행되지 않도록 주의하자
가비지 컬렉터를 갖춘 언어를 사용한다면 메모리 관리에 더 이상 신경쓰지 않아도 된다고 오해할 수 있는데, 절대 사실이 아니다.
스택을 구현한 코드를 보자
class Stack{
private Object[] element;
private int size;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
this.element = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object obj){
ensureCapacity();
element[size++] = obj;
}
public Object pop(){
if (size == 0)
throw new EmptyStackException();
return element[--size];
}
private void ensureCapacity(){
if (element.length == size){
element = Arrays.copyOf(element, 2 * size + 1);
}
}
}
이 코드에서 기능상 특별한 문제는 없어보이지만 메모리 누수라는 심각한 문제가 발생하고 있다.
pop함수에서 스텍에서 꺼내진 객체들을 지금은 가비지 컬렉터가 회수하지 않는다.
element 배열의 활성영역 밖의 참조들이 모두 여기에 해당한다. 여기서 활성 영역이란 size보다 인덱스가 큰 elemet배열 참조들이다.
따라서 pop코드를 제대로 구현하려면 다음과 같이 구현해야 한다.
public Object pop(){
if (size == 0)
throw new EmptyStackException();
Object result = element[--size];
element[size] = null; // 다 쓴 참조 해제
return result;
}
이 예시에서 볼 수 있는 주의할점은 stack 처럼 (element배열을 직접 관리하는) 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다.
캐시 역시 메모리 누수를 일으키는 주범이다. 객체 참조를 캐시에 넣고 사용한 후에도 한참을 그냥 놔두는 일을 자주 접할 수 있다. 캐시를 사용한다면 쓰지 않는 엔트리를 가끔씩 청소해주어야 한다.
참고
이펙티브 자바