Avoid to create useless instance

jiho·2021년 5월 23일
0

EffectiveJava

목록 보기
7/12

개발자가 가져야할 중요한 소양 중 하나로 중복을 피하는 것. 즉, 불필요한 것을 만들지 않아야하는 것에 관련된 내용입니다.

이펙티브 자바 3판의 아이템 6에 대한 내용입니다.

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

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

재사용은 빠르고 세련됐습니다. 특히 불변 객체는 언제든 재사용할 수 있습니다. 객체가 변하지않다는 것을 보장하니 여러 객체가 여러 다른 스레드에서 사용해도 일관성에 대해서는 큰 걱정이 없습니다.

다음은 불필요한 객체 생성의 예로 String 이 있습니다.

String s = new String("abc");

이 문장은 실행 될 때마다 String 인스턴스를 만들게 됩니다. 불필요한 동작입니다. 이 문장이 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없는 인스턴스가 수백 만개 만들어질 수 있습니다.

String s = "abc";

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

생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스는 정적 팩터리 메세드를 사용해서 불필요한 객체 생성을 피할 수 있습니다.(캐시)

Deprecated 된 API인 Boolean 생성자 대신 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 메서드를 사용하는 것입니다. String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지않습니다.

아래는 String.matches(String regx) 에 대한 설명입니다.

메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 됩니다. Pattern은 입력받은 정규표현식에 해당하는 유한 상태머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높습니다.

위 코드의 성능을 개선하려면 필요한 정규 표현식을 표현하는 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고 나중에 isRomanNumeral메서드가 호출될 때마다 이 인스턴스를 재사용해줘야합니다.

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();
    }
}

이렇게 개선하면 isRomanNumeral이 빈번히 호출되는 상황에서 성능을 상당히 올릴 수 있습니다. 성능뿐만 아니라 코드도 더욱 명확해졌습니다.

Pattern 인스턴스는 클래스 초기화시 생성되는 데 한번도 사용하지 않을 경우, 오히려 메모리 낭비라고 생각을 하여 지연 초기화(lazt initialization)로 불필요한 인스턴스 초기화를 없앨 수 있지만 지연 초기화는 코드를 복잡하게 만들지만 성능은 크게 개선되지 않기 때문입니다.

불필요한 객체 생성의 예 - Auto boxing

불필요한 객체를 만들어내는 또 다른 예로 오토 받싱(auto boxing)을 들 수 있습니다.

Auto Boxing은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호변환해주는 기술입니다. 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만 완전히 없애주지는 않습니다. 하지만 성능에서는 문제가 있을 수 있습니다.

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

결과는 정확하지만 제대로 구현했을 때와 비교해서 훨씬 느린 성능을 보여줍니다.
sum은 박싱 타입인 Long이지만 더해지는 i는 primitive type long이기 때문입니다. 매번 더해줄 때 Long instance가 생성되어 메서드 호출 한번 당 2^31개의 Long 인스턴스가 생성되기 때문입니다.

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

profile
Scratch, Under the hood, Initial version analysis

0개의 댓글