본문으로 바로가기
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.



기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 편이 더 낫다. 객체를 재사용하는 프로그램은 더 빠르고 우아하다. 변경 불가능 객체는 언제나 재사용할 수 있다. 절대로 피해야 할 극단적 예를 하나 들어보자. 아래 예제를 실행될 때마다 String 객체를 만드는데, 쓸데 없는 짓이다. 생성자 호출로 만들어 지는 모든 객체와 기능이 같다. 만일 아래 문장이 순환문이나 자주 호출되는 메서드 안에 있다면 수백만 개의 String 객체가 만들어 질 것이다. 아래 코드에서 5번과 같이 사용하는 것이 바람직하다.


1
2
3
4
5
//최악의 코드
String str = new String("Effective Java");
 
//바람직한 코드
String str = "Effective Java";



5번 라인같이 실행을 하면 객체를 만드는 대신, 동일한 String 객체를 사용한다. 게다가 같은 가상 머신에서 실행되는 모든 코드가 해당 객체를 재사용 하게 된다. 생성자와 정적 팩터리 메서드를 함께 제공하는 변경 불가능 클래스의 경우, 생성자 대신 정적 팩토리 메서드를 이용하면 불필요한 객체 생성을 피할 수 있을 때가 많다. 예를 들어, Boolean(String) 보다는 Boolean.valueOf(String) 쪽이 대체로 바람직하다. Boolean 클래스의 중요 코드를 아래에 정리해보았다.


1
2
3
4
5
6
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
 
public static Boolean valueOf(String s) {
    return parseBoolean(s) ? TRUE : FALSE;
}



변경 불가능 객체뿐 아니라, 변경 가능한 객체도 재사용할 수 있다. 변경할 일이 없다면 말이다. 그런데 그럴 때 하지 말아야 할 일이 있다. 꽤 사소해 보이지만 흔히 벌어지는 일이다. 아래 예제는 값을 바꿀 일이 없는 Date 객체(Date 객체는 변경 가능하지만 final을 사용해서 변경 불가능 하게 만들었다.)에 관한 예제이다.

예제에 포함된 Person 클래스는 사람을 표현하며, 어떤 사람이 베이비 붐 세대에 속하는지 아닌지를 알려주는 isBabyBoomer 메서드(1946년과 1964년 사이에 태어난 사람이면 참을 반환)을 가지고 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
    private final Date birthDate;
    
    public Person(Date date) {
        this.birthDate = date;
    }
    
    public boolean isBabyboomer() {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1000);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1000);
        Date boomEnd = gmtCal.getTime();
        
        return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
    }
}



위의 isBabyBoomer 메서드는 호출될 때마다 Calendar 객체 하나, TimeZone, Date 객체 2개를 쓸데없이 만들어 댄다. 이렇게 비효율적인 코드는 정적 초기화 블록을 통해 개선하는 것이 좋다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
    private final Date birthDate;
    public static final Date boomStart;
    public static final Date boomEnd;
    
    public Person(Date date) {
        this.birthDate = date;
    }
    
    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1000);
        boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1000);
        boomEnd = gmtCal.getTime();
    }
    
    public boolean isBabyboomer() {
        
        return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
    }
}



이렇게 개선된 클래스는 Calendar ,TimeZone, Date 클래스가 초기화 될 때 한 번만 만든다. isBabyBoomer가 호출될 때마다 만들지 않는다. isBabyBoomer가 자주 호출되는 메서드였다면 성능은 크게 개선될 것이다. 이 책에서는 10만번 호출했었을 때 개선되기 전과 후가 250배 이상 차이가 났음을 알려주었다.


만일 개선된 Person 클래스가 초기화된 다음에 isBabyBoomer가 호출되지 않는다면 쓸데 없이 초기화 하여 메모리에 올라갔기 때문에 메모리 낭비가 되는 것이다. 그런 상황은 초기화 지연 기법(lazy initialization)을 사용하면 피할 수 있다. isBabyBoomer 메서드가 처음으로 호출될 때 초기화 하는 것이다. 하지만 추천할 방법은 아니다. 초기화를 지연시키면 구현이 복잡해질 뿐더러 앞서 달성한 것 이상으로 성능을 개선하기도 어렵기 때문이다. 


JDK 1.5부터 오토박싱(자동객체화)가 생겼는데 기본 자료형을 참조형으로 바꾸어준다. 오토박싱을 모른다면 블로그에 오토박싱 과 언박싱에 대한 글의 내용을 보고 참조하길 바란다. 둘 간의 변환으로 기본 자료형과 객체 표현형 사이의 의미적인 차이는 미미하지만 성능적으로는 무시하기 어렵다.


아래 예제는 나와야 할 성능보다 훨씬 느리다. 이유는 long가 아니라 Long를 이용하여 더한 sum의 새로운 객체를 Long type으로 만들어 주기 때문이다. 여기서 얻을 수 있는 교훈은 객체 표현형 대신 기본 자료형을 사용하고, 생각지도 못한 자동 객체화가 발생하지 않도록 유의하라는 것이다.


1
2
3
4
5
Long sum = 0L;
 
for(long i =0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}



여기서 하는 말은 객체 만드는 비용이 높으니 무조건 피하라는 것이 아니라 생성자 안에서 하는 일이 작고 명확하면 객체 생성과 반환은 신속하게 이루어진다. 객체를 만들어서 코드의 명확성과 단순성을 높이고 속도를 향상시킨다면 만드는 것이 좋다. 객체 풀도 객체 생성 비용이 높지 않다면 사용하지 않는 것이 좋다. 객체 풀을 만드는 비용이 정당화된 객체는 데이터베이스 연결이다.