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

윤들윤들·2020년 12월 10일
0

EffectiveJava

목록 보기
6/17
post-thumbnail

Item6은 불필요한 객체를 매번 만들지 말고 재사용해서 쓰는편이 좋다에 대한 개념으로 시작합니다

지금 보는 코드는 Item6에 해당하는 극단적인 예입니다 바로 String 을 new 를 통해 인스턴스를 만드는 방법입니다

String owner = new String("윤들윤들");

위의 코드는 해당 코드가 실행될때 마다 새로운 String 인스턴스를 생성합니다.
해다 코드가 반복문이나 반복 호출되는 메서드 안에 있다면 쓸데없는 String 인스턴스가 엄청나게 많이 만들어지게 됩니다.


따라서 위의 코드는 아래와 같이 개선시킬 수 있다.

String owner = "윤들윤들";

이 방식을 사용하면 반복되어 호출되어도 "윤들윤들"이라는 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다.

생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다. ( Item1때 valueOf같은 메서드들을 통해 재사용 가능했었음 )
그래서 생성자 방식은 자바9에서 deprecated로 지정되었다고 한다.

생성자는 호출될때마다 새로운 객체를 만들지만 정적 팩터리 메서드는 전혀 그렇지 않다.

생성 비용이 비싼 객체도 있지만 반복해서 필요하다면 캐싱하여 재사용하길 권한다 !


책에서는 정규표현식을 활용한 예제가 나와있었습니다.

static boolean isRomanNumberal(Strings) {
	return s.matches("정규표현식 일부" + "정규표현식 일부");
}

위 코드는 정규식으로 문자열에대해 검사하는 표현식이 있었을것이다. 하지만 중요한 상황에서 반복해 사용하기엔 적합하지가 않다.

해당 메서드가 한번만 쓰고 버려진다고 생각해보자 해당 정규표현식 pattern 인스턴스는 한번 사용되고 바로 버려저 가비지 컬렉션의 대상이 됩니다. Pattern은 입력받은 정규표현식에 해당하는 유한상태머신을 만들기 때문에 인스턴스의 생성 비용이 높다고합니다.

이러한 문제 때문에 해당 코드에 대한 성능을 개선하려면 필요현 정규표현식을 표현하는 불변인 Pattern인스턴스 클래스를 초기화과정에서 직접 생성해 캐싱해두고 나중에 메서드가 호출될때 마다 재사용 한다면 성능이 좋아질것입니다.

public class RomanNumberals {
	private static final Pattern ROMAN = Pattern.compile("정규표현식");
    
    static boolean isRomanNumberal(Strings) {
    	return ROMAN.matcher(s).matches();
    }
}

위와 같이 개선하면 빈번히 메서드가 호출되는 상황에서 성능을 상당히 끌어 올릴 수 있다.

책에있는 예제에서는 약 6.5배정도 빨라졌다고 나와있네요.


Map 인터페이스의 keySet메서드는 Map안에 있는 모든 Key들을 담은 Set을 리턴한다.

지금 보면서도 놀랐지만 저 또한 keySet을 호출할때 새로운 Set 인스턴스가 생성되어 반환될거라고 생각했지만 알고보니 같은 Set인스턴스를 반환해주고 있었다.

public class KeyMapTest {
    public static void main(String[] args) {
        Map<String,String> maps = new HashMap<>();
        maps.put("HELLO","WORLD");
        maps.put("name", "윤들윤들");
        maps.put("age", "28");

        Set<String> keySetFirst = maps.keySet();
        Set<String> keySetSecond = maps.keySet();

        System.out.println("keySetFirst = " + keySetFirst.hashCode());
        System.out.println("keySetSecond = " + keySetSecond.hashCode());
        System.out.println(keySetFirst == keySetSecond);
    }
}

// 실행결과 ( 실제 테스트했던 데이터 결과 입니다 )
keySetFirst = 72094780
keySetSecond = 72094780
true

모두가 똑같은 Map 인스턴스를 대변하기 떄문에 keySet을 여러개 만들어도 상관없지만 그럴 필요도, 이득도 없다.


오토박싱

불필요한 객체를 만드는 또 하나의 예는 바로 우리가 흔히 사용하는 오토박싱입니다.

오토박싱은 우리가 기본타입과 박싱된기본타입을 섞어 쓸 때 자동으로 변환해주는 기술이다 .
오토 박싱같은 경우는 기본 타입과 그에 대응하는 박싱된 타입의 구분을 흐려주지만 완전하지는 않다. Item61에 자세히 설명이 나옵니다.

다음 예제에는 의도하지 않은 오토 박싱이 숨어들어있다.

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

해당 코드는 sum 의 변수 타입을 프리미티브 타입이 아닌 박싱된 Wrapper타입인 Long 으로 만들었기 때문에 불필요한 Long 인스턴스가 여러개 생성이 된다 .

박싱된 기본 타입(Wrapper)보다는 기본 타입(Primitive type)을 사용하고 의도치 않은 오토박싱이 발생하지 않게 조심하라.

마지막으로
Item6에서 나오는 내용으로 '객체 생성은 비싸니 피해야 한다' 로 오해하면 안된다.
요즘의 JVM에서는 작은 객체를 생성하고 회수하는 일이 크게 부담이 되지 않아서 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것이라면 좋은 일이다.

Item6은 Item50과 대조적이다. Item50에서 다시 확인하겠지만 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억하면 될듯하다.

방어적 복사에 실패하면 버그와 보안 구멍으로 이어지지만 객체생성은 코드 형태와 성능에게만 영향을 미친다.

profile
Front&BaackEnd를 재미있게 공부하고싶은 개발자 YundleYundle

0개의 댓글