[Effective Java] 아이템 6 : 불필요한 객체 생성을 피하라

2022. 3. 16. 00:14Java/Effective Java

728x90
반응형

재사용성

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

재사용을 하면 반응 속도가 더 빠르고 코드도 세련되어 보인다. 특히 불변 객체는 언제든 재사용할 수 있다.

 

String s = new String("bikini");

이런 코드는 사용하지 않아야 하는 극단적인 예이다.

 

왜냐? 코드가 실행 될 때마다 String 인스턴스를 계속 생성해낸다. 만약 이 코드가 반복문이나 자주 호출되는 메소드 안에 존재한다면 실행시 쓸데없는 생성만 계속 될 것이다.

 

어떻게 사용해야 하나?

String s = "bikini";

이렇게 매번 new 생성자로 새로 생성되는 것이 아니라. 딱 하나의 인스턴스만 사용하게 사용하자.

이와 같이 인스턴스를 사용하면 같은 객체를 재사용할 수 있게 보장해준다.

 

생성자를 통해 생성하는 대신 정적 팩토리 메소드를 제공하는 불변 클래스에는 불필요한 객체 생성을 막을 수 있다.

 

Boolean(String)

Boolean.valueOf(String)

1번의 형태보다는 2번의 형태를 사용하는 것이 재사용성으로는 좋다.

더보기

이 생성자는 자바 9에서 사용 자제 API(deprecated)로 지정되었다.

 

재사용성과 생성 비용

 

재사용을 하더라도 생성 비용이 비싼 객체도 많이 있다. 

생성 비용이 비싼 객체는 반복 사용을 해야한다면 캐싱하여 재사용하는 것이 좋다.

 

정규 표현식을 나타내기에 String.matches 메소드를 사용할 수 있다. 캐싱으로 사용하기에 가장 쉬운 방법이지만, 성능이 중요한 구종일 경우 반복 사용하기는 어렵다.

static boolean isRomanNumeralSlow(String s) {
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}

이를 보완하기 위해

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 isRomanNumeralFast(String s) {
    return ROMAN.matcher(s).matches();
}

코드를 이렇게 작성한다. Pattern 인스턴스는 유한 상태 머신은 만들기 때문에 인스턴스 생성 비용이 높다.

성능 개선을 위해 static final 로 초기에 캐싱해놓고 재사용한다.

 

이렇게 사용하면 성능을 끌어 올릴 수 있다.

 

동일한 인스턴스가 있다면 하나만 쓰자

Map 인터페이스의 KeySet 함수로 Set 인스턴스를 반환하자. 하지만 동일한 Map에서 호출하는 메소드는 같은 Map을 대변하기 때문에 거기서 생성되는 인스턴스들은 결국 모두 동일한 Map을 가지는 것이다.

Map<String, Object> test = enw HashMap<>();
test.put("123", "123");

Set<String> test1 = test.keySet();
Set<String> test2 = test.keySet();

여기서 사용된 1, 2는 같은 Key Set을 반환하기 때문에 불필요한 할당을 줄이는 것이 좋다.

 

오토박싱

Long sum = 0L;
long sum = 0L;

Long = 박싱된 기본 타입, long = 기본 타입

 

프로그래머가 원시 타입과 박스 타입을 섞어 사용할 수 있게 해주고 (언)박싱을 자동으로 해준다.

하지만 서로의 경계를 없애지는 않는다.

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

 

그냥, 진짜 필요한 것이 아닌 경우에는 기본 타입의 것을 사용하자.

728x90
반응형