Reading/이펙티브 자바

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

going.yoon 2022. 4. 24. 22:26

만약 내가 새로 인스턴스를 습관적으로 새로 생성하려고 하는 상황이 있다고 생각해보자.

그럼 바로 new 생성자를 통해서 인스턴스를 생성할 것이 아니라, 내가 만드려고 하는 대상이 불변 클래스인지 아닌지 생각해보고, 그 클래스의 정적 팩터리 메서드를 사용할 수 있는지 생각해보자.

 

정적 팩터리 메소드를 사용하면 불필요한 객체 생성을 피할 수 있기 때문이다. Boolean을 생성자로 생성하는 것보다 Boolean.valueOf(String)이라는 팩토리 메소드를 사용하는 것이 좋다는 얘기다.

 

만약 생성비용이 비싼 객체라면 어떨까? 불필요한 생성을 줄임으로써 성능을 상당히 개선할 수 있을 것이다. 이런 비싼 객체가 반복해서 필요하다면 '캐싱'을 통한 재사용이 아주 유용할 수 있다.

static boolean isRomanNumeral(String s){
	return s.matches // 이 메소드 안에서 pattern 인스턴스가 일회성으로 사용됨. 생성비용 높음.
    	("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}

위의 예시에서 사용되는 mathes메소드는 내부에서 Pattern 인스턴스를 생성한다.  이 인스턴스는 한 번 쓰고 버려져서 가비지 컬렉션 대상이 된다. 또한 입력받은 정규 표현식에 해당하는 유한 상태 머신(fininte state machine)을 만들기 때문에 인스턴스 생성 비용이 높다. 따라서 이 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해서 캐싱을 해두고, 이 인스턴스를 재사용한다.

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 isRomanNumeralFast(String s) {
    return ROMAN.matcher(s).matches(); // 인스턴스 재사용
}

 

 

불필요한 객체 생성1 - 어댑터

어댑터는 뷰(view)라고도 불리는데, 실제 작업은 뒷단 객체에 위임하고 자신은 제 2의 인터페이스 역할만 해주는 객체를 말한다.

예를들어 Map인터페이스의 KeySet 메소드는 매번 같은 set 인스턴스를 반환한다. 반환된 set 인스턴스가 만약 일시적으로 가변이더라도 반환된 인터페이스들은 기능적으로 모두 똑같다. 즉, 반환한 객체 중 하나를 수정하면 다른 모든 객체가 바뀐다. 모두가 똑같은 Map 인터페이스를 대변하기 때문이다. keySet이 매번 인스턴스를 새로 생성해서 set 인스턴스를 반환한다면 그것은 객체를 불필요하게 생성한다고 할 수 있을 것이다.

 

불필요한 객체 생성 2 - 오토박싱 (auto boxing)

오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다. 기능상의 문제는 없지만 성능에서는 그렇지 않다.

private static long sum() {
	Long sum = 0L;
    for( long i = 0 ; i < Integer.MAX_VALUE ; i++ ){
    	sum += i; // primitive 타입인 long -> Long으로 변환되면서 불필요한 Long 인스턴스 생성
    }
    
    return sum;
}

위의 예시처럼 오토박싱의 과정에서 약 6배의 성능 차이를 낼 수 있다. 그렇기 때문에 박싱된 기본타입보다는 기본 타입을 가용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의해야 한다.