Post

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

객체의 재사용

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

  • 생성자 대신 정적 팩터리 메서드를 사용하여 불필요한 객체 생성을 피할 수 있다.
  • ex) Boolean(String) 대신 Boolean.valueOf(String)

생성 비용이 아주 비싼 객체일 경우

String.matches로 정규표현식을 통해 문자열 형태를 확인하는 코드

1
2
3
4
5
static boolean isRomanNumeral(String s) {
    // String.matches 내부에서 새로운 Pattern 인스턴스를 생성
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
  • 메서드 내부에서 만드는 Pattern 인스턴스는 한번 쓰고 버려지므로 가비지 컬렉션 대상이 된다.
  • 성능이 중요한 상황에서 반복해서 사용하기에 적합하지 않다.
1
2
3
4
5
6
7
8
9
10
11
// 값비싼 객체를 재사용해 성능을 개선한다.
public class RomanNumerals {
    // 인스턴스 생성비용이 높은 Pattern 인스턴스를 미리 생성해두고, 해당 인스턴스를 재사용
    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 메서드가 호출될 때마다 재사용하자.

어댑터 (view)

실제 작업은 뒷단 객체에 위임한 채, 자신은 제 2의 인터페이스 역할을 하는 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AdapterExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        
        Set<String> keys1 = map.keySet();
        Set<String> keys2 = map.keySet();
		
        // 반환받은 Set 객체 중 하나를 수정하면 다른 모든 객체도 따라서 수정된다.
        keys1.remove("one");
        System.out.println(keys2.size()); // 1
        System.out.println(map.size()); // 1
    }
}

어댑터는 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

  • 예컨대, Map 인터페이스가 제공하는 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set뷰를 반환한다.
  • keySet을 호출할 때마다 새로운 객체가 만들어질 것 같지만 사실 같은 객체를 반환한다.
  • 즉, 반환된 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀐다.
    • (모두가 같은 Map 인스턴스를 대변하기 때문)
  • 반환된 Set 객체가 일반적으로 가변이더라도 반환된 객체들은 기능적으로 모두 똑같다.
  • 따라서 keySet이 뷰 객체를 여러 개 만드는 건 불필요하다.

오토박싱 (auto boxing)

기본 타입과 박싱된 기본 타입을 섞어 쓸 때 상호 변환해주는 기술

1
2
3
4
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
    sum += i;
}
  • 타입의 구분을 완전히 없애주는 것은 아니어서 성능에 영향을 준다.
  • sum 변수를 long이 아닌 Long으로 선언했기 때문에 성능이 훨씬 느려짐.
  • long 타입인 i가 Long 타입인 sum에 더해질 때마다 불필요한 Long 인스턴스가 만들어지므로.

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

참고 🕶️

자바 정규표현식 Pattern 문법

https://docs.oracle.com/javase/tutorial/essential/regex/

https://regex101.com/ 또는 https://regexr.com/

가비지 컬렉션

https://developers.redhat.com/articles/2021/11/02/how-choose-best-java-garbage-collector#

This post is licensed under CC BY 4.0 by the author.