[교재 EffectiveJava] 아이템 6. 불필요한 객체 생성을 피하라

반응형
728x90
반응형

불필요한 객체 생성을 피하라

똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다. 재사용은 빠르고 세련되다. 

 

new String() 하지 말것

아래 코드를 보자.

 

쓰면 안되는 코드
String s = new String("bikini"); // 따라하지 말 것

위 코드는 실행될 때마다 String 인스턴스를 새로 만든다. 완전히 쓸데없는 행위다. 

 

권장되는 코드
String s = "bikini";

위 코드는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다. JVM 안에서 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다. 

 

 

String.matches()를 사용할때 주의할것

생성 비용이 아주 비싼 객체가 있는데, 이런 '비싼 객체'가 반복해서 필요하다면 캐싱하여 재사용하길 권한다. 

package me.whiteship.chapter01.item06;
import java.util.regex.Pattern;

public class RomanNumerals {
    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

    public static void main(String[] args) {
        boolean result = false;
        long start = System.nanoTime();
        for (int j = 0; j < 100; j++) {
            //TODO 성능 차이를 확인하려면 xxxSlow 메서드를 xxxFast 메서드로 바꿔 실행해보자.
            result = isRomanNumeralSlow("MCMLXXVI");
        }
        long end = System.nanoTime();
        System.out.println(end - start);
        System.out.println(result);
    }
}

1) String.matches()

정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다. 이 메서드가 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한번 쓰고 버려져서 곧바로 가비지 컬렉션의 대상이 된다. Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높다.

 

▶ String 클래스의 matches()

public boolean matches(String regex) {
    return Pattern.matches(regex, this);
}

 

▶ Pattern 클래스의 matches()

public static boolean matches(String regex, CharSequence input) {
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);
    return m.matches();
}

// Pattern.compile 메서드
public static Pattern compile(String regex) {
    return new Pattern(regex, 0);
}

 

위 코드의 성능을 개선

필요한 정규표현식을 포현하는 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용한다.

package me.whiteship.chapter01.item06;
import java.util.regex.Pattern;

public class RomanNumerals {
    // 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다.
    private static final Pattern ROMAN = Pattern.compile( // return new Pattern(regex, 0);
            "^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

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

    public static void main(String[] args) {
        boolean result = false;
        long start = System.nanoTime();
        for (int j = 0; j < 100; j++) {
            result = isRomanNumeralFast("MCMLXXVI");
        }
        long end = System.nanoTime();
        System.out.println(end - start);
        System.out.println(result);
    }
}

 

 

불필요한 객체를 만드는 오토박싱(auto boxing)

오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어쓸 때 자동으로 상호 변환해주는 기술이다. 

/**
 * 오토박싱, 언박싱
 */
public class Sum {
    private static long sum() {
        // Long을 long으로 변경하여 실행해 보세요.
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        return sum;
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        long x = sum();
        long end = System.nanoTime();
        System.out.println((end - start) / 1_000_000. + " ms.");
        System.out.println(x);
    }
}

sum 변수를 long이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 무수히 많이 생성된다. (long 타입인 i가 Long 타입인 sum에 더해질때마다) 

박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.

 

 

반응형

Designed by JB FACTORY