본문 바로가기
Java

Effective Java #05 불필요한 객체는 만들지 말라

by NaHyungMin 2016. 4. 13.

기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 편이 낫다.

객체를 재사용하는 프로그램은 메모리 관리에 더 용이하다.


절대로 피해야 할 극단적 예를 하나 들어보자.


String s = new String("stringette");


만위 위의 문장이 순환문(loop)나 자주 호출되는 함수 안에 있으면 그 숫자대로 String 객체가 만들어진다.

* 가령 필자는 2년차 시절, 야구 기록 웹 API XML 반복문 안에 string 문을 통해 작성한 뒤 속도가 현저하게 떨어지는 증상을 겪은 뒤로

  StringBuilder를 사용하기 시작했다.


C#이나 자바나 string 문자가 변경되면 새로 객체가 생성된다.

String s = "stringette"; 이라면 stringette2가 되면 새로운 객체가 생성되기에 StringBuilder를 사용하기를 추천한다.


다음은 변경 가능한 객체를 사용하여 사람이 베이비 붐 세대에 속하는지 아닌지를 알려주는 isBabyBoomer 함수를 사용하는 것을 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
    private final Date birthDate;
 
    //다른 필드 및 함수 생성자 생략
 
    //이렇게 하면 안 된다
    public boolean isBabyBoomer(){
        //생성 비용이 높은 객체를 쓸데없이 생성한다.
        Calendar gmtCall = Calendar.getInstance(TimeZone.getTimeZone("GET"));
        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;
    }
}
cs


정적 초기화 블록(static initializer)를 통한 개선


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {
    private final Date birthDate;
 
    //다른 필드 및 함수 생성자 생략
    
    private static final Date BOOM_START;
    private static final Date BOOM_END;
 
    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1000);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1000);
        BOOM_END = gmtCal.getTime();
    }
 
    public boolean isBabyBoomer() {
        return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
    }
}
cs



개선된 Person 클래스는 Calendar, TimeZone, Date 객체를 클래스가 초기화 될 때 한번만 만든다.
책 저자의 말대로라면 성능이 꽤 많이 좋아지는 것 같지만, 이 경우 Calendar가 객체 생성 비용이 높기 때문이라고 한다.
Person이 처음 생성 될 때, 느린 초기화를 사용하면 어떨까 했는데 책의 저자는 별로 추천하고 싶지 않은 것 같았다.
만약 필수인 Class라면 이 경우도 상관 없을 것 같지만, 생성 될수도 아닐 수도 있는 곳에서는 느린 초기화가 나을 것 같다.

JDK 1.5부터 자동 객체화(autoboxing)이 새로 생겼다고 한다.
기본 자료형(primitive type)과 그 객체 표현형을 섞어 사용할 수 있도록 해준다는 것인데 둘 간의 변환은 자동으로 이뤄진다고 한다.
그렇다면 어떤 부분에서 문제가 생길 지 알아보자.

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
    Long sum = 0L;
    for(long i = 0; i < Integer.MAX_VALUE; i++){
        sum += i;
    }
 
    System.out.println(sum);
}
 
cs

이 코드에서 단순히 sum을 Long으로 선언하고, i를 long으로 선언했다고 자동 캐스팅이 되면서 객체가 추가로 생성된다고 한다.
기본 자료형을 사용해야 할 때와 객체 표현형을 사용할 때가 존재할 때를 제외하고는 그다지 문제가 생길 것 같지 않다는게 개인 의견이다.

이 내용과 다른 관점으로 방어적 복사라는 것이 있다고 한다.

재사용이 가능하다면 새로운 객체를 만들지 말라는 이 얘기와 다르게, 새로운 객체를 만들어야 한다면 기존 객체는 재사용하지 말라는 이야기가 있는데

코드를 작성하다 보면, 상황에 맞게 써야하는게 답인 것 같다.