Item 6, 불필요한 객체 생성 피하라

Bong2·2022년 4월 20일
0

이팩티브자바

목록 보기
6/9

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

똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
재사용하면 빠르고 세련된다?

문자열 객체 생성

하면 안되는 코드
String s = new String("bikini");
이 코드는 실행될 때마다 String 인스턴스를 새로 만든다. 그래서 반복문을 하면 인스턴스가 수백만 개가 만들어질 수 있다.

개선된 코드
String s = "bikini";
하나의 인스턴스를 사용한다., JVM에 동일한 문자열 리터럴이 존재한다면 그 리터럴을 재사용한다.

static 팩터리 메소드 사용하기

자바 9에서 deprecated 된 Boolean(String) 대신 Boolean.valueOf(String) 같은 static 팩토리 메소드(아이템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})$");
    }

String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.

String.matches는 내부적으로 Pattern 객체를 만들어 쓰는데 그 객체를 만들려면 정규 표현식으로 유한 상태 기계로 컴파일 하는 과정이 필요하다. 즉 비싼 객체다.
Pattern 객체를 만들어 재사용하는 것이 좋다.

public class RomanNumber {

    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();
    }

}

게으른 초기화(lazily initializing)(아이템83)를 사용해서 최적화 할 수 있지만 추천하진 않는다. 보통 지연 초기화는 측정 가능한 성능 개선 없이 구현을 복잡하게 만든다.(아이템67)

어댑터(view)

실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체다.
뒷단 객체 외에는 관리할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

Map 인터페이스가 제공하는 KeySet 메서드는 Map 객체 안의 키 전부를 담은 Set 뷰를 반환한다. keySet을 호출할 때마다 새로운 객체가 나올거 같지만 사실 같은 객체를 리턴하기 때문에 리턴 받은 Set 타입의 객체를 변경하면, 결국에 그 뒤에 있는 Map 객체를 변경하게 된다.

public class UsingKeySet {

    public static void main(String[] args) {
        Map<String, Integer> menu = new HashMap<>();
        menu.put("Burger", 8);
        menu.put("Pizza", 9);

        Set<String> names1 = menu.keySet();
        Set<String> names2 = menu.keySet();

        names1.remove("Burger");
        System.out.println(names2.size()); // 1
        System.out.println(menu.size()); // 1
    }
}

오토박싱

프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸때 자동으로 상호 변환해주는 기술
오토방식은 기본 타입과 박스 타입의 경계가 안보이게 해주지만 그렇다고 그 경계를 없애주진 않는다.

public class AutoBoxingExample {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Long sum = 0l;
        for (long i = 0 ; i <= Integer.MAX_VALUE ; i++) {
            sum += i;
        }
        System.out.println(sum);
        System.out.println(System.currentTimeMillis() - start);
    }
}

sum의 변수를 Long으로 만들었기 때문에 불필요한 Long객체를 2^31만큼 만들어 6초 넘게 걸린다. 그래서 long으로 바꾸면 10배이상 줄일 수 있다.

불필요한 오토박싱을 피하려면 박스 타입 보다는 프리미티브 타입을 사용해야 한다

정리

객체 생성은 비싸니 피해야 한다.로 오해해서는 안된다. 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다. 실패하면 버그와 보안 구멍으로 이어진다. 불필요한 객체 생성은 그저 코드 형태와 성능에만 영향을 끼친다.

profile
자바 백엔드 개발자로 성장하자

0개의 댓글