본문 바로가기

프로그래밍/JAVA

[JAVA] Effective Java - 6. 불필요한 객체 생성을 피하라.

반응형

똑같은 기능의 객체를 매번 생성하기 보다는 재사용하는 것이 좋다.

 

예로 불변객체인 String 객체는

    int i = 0;
    while(i < Integer.MAX_VALUE) {
//      String a = new String("a"); // 평균 0.05초
      String a = "a"; // 평균 0.002초
      i++;
    }

 new 생성자로 매번 생성시 쓸데없는 String 인스턴스가 수백만개 생겨난다.

하지만 String 리터럴 사용시 String Contants Pool에 저장되어 재사용되어지므로 생성 되지않는다.

속도 면에서도 차이가 나는걸 볼 수 있다.

 

또 다른 예로는 Boolean(String) 생성자(자바 9 이상 deprecated) 와 Boolean.valueOf(String)가 있다.

마찬가지로 Boolean(String) 생성자는 호출마다 객체를 생성하고,

Boolean.valueOf(String) 팩터리 메서드는 내부의 캐싱된 객체를 재사용한다.

public static Boolean valueOf(String s) {
    return parseBoolean(s) ? TRUE : FALSE;
}

 

생성비용이 큰 객체일 경우 반복 생성 시 성능저하에도 영향이 있다.

로마 숫자인지 확인하는 메서드를 예로

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(String) 메서드를 사용시 반복 사용시 Pattern 객체를 생성하고, 사용, 참조해제가 반복되어 성능이 저하된다. 특히 Pattern객체는 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 생성비용이 더욱 높다.

 * 유한상태머신

   한번에 한가지 상태를 가지는 수학적 모델

   이벤트에 의해 상태가 변할 수 있고 안정성이 높다.

 

밑에 소스 처럼 객체를 직접 생성해 캐싱하여 사용하면 성능과 가독성을 높여준다.

public static final Pattern pattern = 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 pattern.matcher(s).matches();
}

 

Map 인터페이스의 keySet 메서드

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

 

불필요한 객체를 만드는 예로는 오토박싱이 있다.

Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
  sum += i; // 오토박싱 되어 연산수행 마다 Long이 생성됨
}

성능 저하를 불러올 수 있으니 이러한 코드를 주의해야한다.

 

물론 무작정 객체 생성을 피하라는 뜻이 아닌 "불필요"한 객체 생성을 줄이자는 뜻이다.

요즘의 JVM은 작은 객체의 생성, 회수는 크게 부담되지 않고, 객체 생성으로 명확하고 간결해 질 수 있다.

위의 예제들 처럼 명확하게 객체가 재사용되어도 문제가 없을때만 재사용하고, 객체를 생성하는 편이 나을 수 도 있다.

객체 생성을 함으로써 생기는 문제는 성능 저하 수준이지만 재사용 시 생기는 버그는 피해가 더욱 클 수 있기 때문이다.

반응형