본문 바로가기
Java

Effective Java 아이템 6. 불필요한 객체 생성을 피하라

by jayden-lee 2019. 4. 4.
728x90

Effective Java 3판을 학습하며 요약한 내용입니다. 자세한 내용은 책을 참고해주시기 바랍니다.

똑같은 기능의 객체를 매번 생성하는 것보다 객체 하나를 재사용하는 편이 나은 경우가 많다. 재사용은 일단 매번 객체를 생성하는 것보다 빠르다. 특히 불변 객체는 언제든 재사용 할 수 있다. 변하지 않기 때문이다.

 

실무에서 개발 업무를 하다 보면 String을 많이 사용한다. 경험적으로 문자열 값을 처리하는 경우가 많았던 것 같다. String을 리터럴로 선언하면, 같은 객체를 재사용하게 된다. new String() 코드는 문자열 인스턴스를 매번 새로 생성하며, 동일한 문자열 값이더라도 다른 인스턴스를 생성한다.

// String 리터럴로 선언해서 동일한 문자열이면 같은 객체를 사용하는 방식
String s = "Effective Java";

// String 객체를 매번 생성하는 방식
String s2 = new String("Effective Java");

 

생성자는 호출할 때마다 매번 새로운 인스턴스를 생성한다. 반면에 정적 팩터리 메서드에서는 불필요한 객체 생성을 피할 수 있다. 단, 정적 팩터리 메서드는 반드시 동일한 인스턴스를 전달할 필요는 없다.

 

아이템 1에서 Boolean 클래스의 정적 팩터리 메서드에 대해서 알아봤다. 자바 9 부터는 Boolean 생성자를 사용하는 것을 deprecated API로 지정되었다. 이처럼 상황에 따라 매번 객체를 생성하는 것보다 미리 인스턴스를 생성해서 재사용하는 것이 좋다.

 

생성 비용이 아주 비싼(많은 리소스가 드는) 객체가 더러 있다. 이러한 객체를 반복해서 사용한다면, 캐싱해서 재사용하는 것이 좋다.

 

책에서는 정규표현식에 유효한 문자열인지 체크하는 Pattern 클래스를 예를 들어 설명했다. 로마 숫자인지를 체크하는 Pattern 인스턴스를 매번 생성하는 것은 비효율적이다. 자주 사용한다면 클래스 필드에 미리 생성해 캐싱해두고 필요할 때마다 재사용하는 것이 좋다. 미리 생성하는 것조차도 비효율적이다면, 지연 초기화(lazy initialization)로 불필요한 초기화를 없앨 수 있다.

 

다음 코드는 매개변수로 주어진 문자열이 로마 숫자인지 체크하는 예제이다. 유효한지 체크할 때마다 매번 Pattern 인스턴스를 생성하고 있다.

public static boolean isRomanNumeral(String s) {
    return s.matches("^(?=[MDCLXVI])M*D?C{0,4}L?X{0,4}V?I{0,4}$");
}

위 코드에서 Pattern 인스턴스를 캐싱해서 재사용하는 코드로 바꾸면 다음과 같다.

public class RomanNumerals {
    // Pattern 인스턴스를 미리 생성해서 캐싱
    private static final Pattern ROMAN = Pattern.compile(
        "^(?=[MDCLXVI])M*D?C{0,4}L?X{0,4}V?I{0,4}$");

    public static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

 

불필요한 객체를 만들어내는 또 다른 예로 오토 박싱(auto boxing)을 들 수 있다. 오토 박싱은 개발자가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다. int 기본 타입의 박싱된 기본 타입은 Integer이다. 이처럼 Java에서는 기본 타입에 해당하는 박싱된 기본 타입을 제공한다.

 

오토 박싱 기능은 자동으로 변환한다는 이점을 가져다 주지만, 불필요한 객체를 생성할 수 있기 때문에 주의해서 사용해야 한다.

 

다음 코드는 자동으로 오토 박싱을 하면서 불필요한 객체가 생성되는 예제이다.

public static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }

    return sum;
}

sum 변수를 기본 타입 long이 아닌 박싱된 기본 타입 Long으로 선언했기 때문에 불필요한 Long인스턴스를 생성한다. 불필요한 객체 생성을 했기 때문에 당연히 성능도 나쁠 것이다. 이를 해결하기 위해서는 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도지 않은 오토 박싱이 코드에 숨어들지 않도록 주의해야 한다.

댓글