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

Bobby·2022년 5월 29일
0

이펙티브 자바

목록 보기
6/7
post-thumbnail

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

1. 불변 객체

String 객체를 예로 보면

String s = new String("bikini");
  • new 키워드를 사용해서 String 인스턴스를 만들 수 있는데 이렇게 하면 String 인스턴스를 새로 만든다.
  • 만약 반복문이나 빈번히 호출되는 메소드 안에 있다면 쓸데없는 String 인스턴스가 계속 만들어지게 된다.

개선

String s = "bikini";
  • 이렇게 하면 하나의 String 인스턴스를 사용한다.
  • 그렇기 때문에 이와 같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용한다.

생성자 대신 정적 팩토리 메소드를 사용해 불필요한 객체 생성을 피할 수 있다. (ex: Boolean 생성자 보다는 Boolean.valueOf(String) 팩토리 메소드)


2. 생성비용이 비싼 객체

정규표현식은 생성비용이 비싼 객체중 하나이다.

	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는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.
  • 이 메소드가 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다.
  • Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높다.

개선

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();
    }
}
  • 정규표현식을 표현하는 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메소드가 호출될 때마다 이 인스턴스를 재사용한다.

개선 전 후 속도 차이

  • 1000 회 반복 수행하여 속도 차이 확인
	public static void main(String[] args) {
        boolean result = false;
        long start = System.nanoTime();
        for (int j = 0; j < 1000; j++) {
            result = isRomanNumeral("MCMLXXVI");
        }
        long end = System.nanoTime();
        System.out.println(end - start);
        System.out.println(result);
    }
개선 전개선 후
40421200 ns5786600 ns

3. 오토 박싱

오토 박싱은 기본 타입과 박싱된 타입 사이을 자동으로 변환 해주는 기술이다.
(ex: long <-> Long)

	private static long sum() {
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        return sum;
    }
  • sum 변수를 long 이 아닌 Long 으로 선언해서 불필요한 Long 인스턴스가 약 231 개나 만들어진 것이다.

개선

	private static long sum() {
        long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        return sum;
    }
  • 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않게 오토박싱이 생기지 않토록 박싱된 타입 사용시 주의해야 한다.

개선 전 후 속도 차이

	public static void main(String[] args) {
        long start = System.currentTimeMillis();
        long x = sum();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + " ms.");
    }
개선 전개선 후
10217 ms829 ms

핵심 정리

  • 요즘 JVM의 가비지 컬렉터는 최적화가 잘 되어서 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 일은 크게 부담되지 않는다. 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것은 좋은 과정이다.
  • 아주 무거운 객체가 아니라면 단순히 객체 생성을 피하기 위해 객체 풀(pool)을 생성 하는 것은 좋지 않다.
profile
물흐르듯 개발하다 대박나기

0개의 댓글