Developer's_til/Effective Java

[item 6] 불필요한 객체 생성을 피하라

Heon_9u 2022. 10. 3. 19:04
728x90
반응형

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

다음 코드 중 첫번째는 절대 하지말아야 할 극단적인 예로 코드가 실행될 때마다 새로운 String 인스터스를 생성한다. 이를 개선한 버전이 두번째 코드다.

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

String s = new String("str");
String s = "str";

 

생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다. 아래 첫번째 생성자는 호출할 때마다 새로운 객체를 만들지만, 팩터리 메서드는 그렇지 않다.

Boolean(String)
Boolean.valueOf(String)

 

다음은 주어진 문자열이 유효한 로마 숫자인지를 확인하는 정규표현식을 예로 들어본다.

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는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.

 이 메서드가 내부에서 만드는 정규표현식용 Pattern 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다. 성능 개선을 위해 아래처럼 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두고 재사용한다.

 

public class RomanNumerals {
    private static final Pattern ROMAN = 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 ROMAN.matcher(s).matches();
}

 실제로 길이가 8인 문자열을 입력하면 약 6.5배정도 빨라진 것을 확인할 수 있다. 물론, isRomanNumeral 메서드를 한 번도 호출하지 않는다면 ROMAN 변수는 쓸데없이 초기화된 꼴이다. 이를 지연 초기화로 불필요한 초기화를 없앨 수는 있지만, 코드를 복잡하게 만들고, 성능은 크게 개선되지 않을 때가 많기 때문에 권하지 않는다.

 

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

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

 sum 변수를 long이 아닌 Long 으로 선언하게 되면 불필요한 Long 인스턴스가 Integer.MAX_VALUE개만큼 만들어진다. 이를 long타입으로 바꾸게 되면 6.3초에서 0.59초로 빨라진다. 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.

 

 

728x90
반응형