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

2021. 6. 23. 13:24책/이펙티브자바

객체의 재사용

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

특히, 불변 객체는 언제든 재사용할 수 있다.

String 의 객체 생성

String 객체를 생성하는 방법은 new 연산자와 리터럴을 사용하는 방법이 있다.

String s1 = new String("kingsubin");
String s2 = new String("kingsubin");

// 이 방법은 매번 새로운 객체를 생성함.
System.out.println(s1 == s2); // false

String s3 = "kingsubin";
String s4 = "kingsubin";

// 이 방법은 String Constant Pool 이란 곳에서 재사용되어짐.
System.out.println(s3 == s4); // true

String Constant Pool이란? | Java String Pool

비싼객체

생성 비용이 아주 비싼 객체가 있는데, 이런 '비싼 객체'가 반복해서 필요하다면 캐싱하여 재사용 하기를 권한다.

여기서 비싼 객체라는 것은 인스턴스를 생성하는데 드는 비용이 크다는 의미이다.

→ 메모리, 디스크 사용랑, 대역폭 등이 높을수록 생성 비용이 비싸다는 이야기를 한다.

보통 Connection, Pattern 인스턴스들이 비싼 객체로 소개된다.

// 1
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})$");
}

// 2
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();
    }
}
  • 1번의 경우 반복해 사용하기엔 적합하지 않다.

    • 메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스가 한번 쓰고 버려져서 GC 대상이 되기 때문이다.
    • 여기서Pattern인스턴스는 생성 비용이 높은 인스턴스이다.
  • 2번의 경우 성능을 개선시켰다.

    • Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두었고, 나중에 isRomanNumeral 메소드가 호출될 때마다 이 인스턴스를 재사용한다.

오토박싱

오토박싱은 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.

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

    return sum;
}

sum 변수를 long이 아닌 Long 으로 선언했기 때문에 sum += i 과정에서 불필요한 Long 인스턴스가 약 2^31개가 만들어졌기에 비용이 들어 시간이 오래 걸린다.

여기서 Long sum 을 long sum 으로 바꾸면 메소드 시간이 책에서는 6.3 → 0.59초로 단축되었다고 한다.

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

결론

  • 객체 생성은 비싸니 피해야 한다 → X
    • 요즘의 JVM 에서는 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 일이 크게 부담되지 않음.
    • 프로그램의 명확성, 간결성, 기능을 위해 객체 추가생성은 일반적으로 좋은일이다.
  • 생성 비용이 비싼 객체의 재사용을 고려해보자 → O