Effective Java - Item 6

초보개발·2022년 8월 8일
0

JAVA

목록 보기
14/15

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

문자열 객체 생성 예시

String s = new String("java"); // 항상 새로운 객체가 생성

String s = "java"; // 항상 같은 문자열을 참조

문자열 생성자를 이용하면 "java" 값을 가진 객체가 새롭게 생성되어 반복문이나 여러번 호출되는 메서드에 있다면 불필요하게 많은 인스턴스가 생성될 수 있다.
반면, 아래와 같은 경우는 문자열 리터럴을 재사용하므로 같은 JVM에 동일한 문자열 리터럴이 존재한다면 그 리터럴을 재사용한다.

정적 팩터리 메서드(아이템1)

Boolean(String)(Java9에서 deprecated됨) 생성자 대신 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는 내부에서 만드는 정규표현식용 Pattern 인스턴스를 한번 사용하고 버려져 GC 대상이 된다. 또한, 입력받은 정규표현식에 해당하는 유한 상태 머신을 만드므로 인스턴스 생성 비용이 비싸다.
성능을 개선하려면 Pattern 객체를 만들어 재사용하는 것이 좋다.

public class RomanNuerals {
	// static final 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 isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

isRomanNumeral 메서드가 한번이라도 호출되지 않는다면 ROMAN 필드는 쓸데없이 초기화 되어버린다. 해당 메서드가 처음 호출될 때 필드를 초기화하는 Lazy initialization(지연 초기화, 아이템83)로 문제를 해결할 수 있으나 추천하지 않는다. 지연 초기화는 측정 가능한 성능 개선 없이 코드를 복잡하게 만들기 때문이다.

어댑터

객체가 불변이면 재사용해도 안전하지만, 명확하지 않는 경우도 존재한다. 어댑터의 경우 실제 작업은 뒷단 객체에 넘기고 자신은 제2의 인터페이스 역할을 해주는 객체다. 즉, 인터페이스를 통해 뒷단 객체로 연결해주는 객체이므로 여러개 만들 필요가 없다.
Map 인터페이스의 keySet 메서드는 Map 객체 안의 키를 모두 담은 Set 뷰를 반환한다. keySet을 호출할 때마다 새로운 Set 객체가 생성될 것이라 생각할 수 있지만, 사실 매번 같은 객체를 반환한다. 따라서 반환받은 Set 객체를 변경하게 되면 그 뒤에 있는 Map 객체를 변경하는 셈이된다.

AutoBoxing

불필요한 객체를 만들어내는 또 다른 예이다. 오토박싱은 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술을 말한다.
오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐릿하게 해주지만 완전히 그 구분을 없애주진 않는다. (성능상엔 다르다는 말)

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

Long 객체 타입의 sum 변수에 기본 자료형인 long 타입의 i를 계속 더해주는 로직이다. 여기서 문제점은 불필요한 Long 객체를 2312^{31}개나 만들어지고 대략 6초가 걸린다. sum의 타입을 long으로 변경할시 0.59초로 엄청 빨라진다.
이처럼 불필요한 오토박싱을 피하기 위해선 Wrapper class를 사용하는 것보다 기본 타입을 사용해야 한다.

"객체 생성은 비싸니 피해라"라는 말이 아니다. 기존 객체를 재사용해야 한다면 새로운 객체를 만들지 마라.

0개의 댓글