똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 게 나을 때가 있다. 아래는 객체를 재사용하는 방향으로 개선할 수 있는 코드 예시이다.

 

동일한 문자열 반복 생성

아래 코드는 "IU is the best"라는 문자열 객체를 String 생성자에 넘겨 완전히 똑같은 객체를 하나 더 생성한다. 반복문이나 자주 사용되는 메서드에 아래 코드가 있다면, 쓸모없는 객체가 대량으로 만들어질 것이다. 좋은 예시는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스만 사용하기 때문에 같은 VM에서 똑같은 문자열을 사용하는 모든 코드가 같은 객체를 재사용하는 것이 보장된다.

// 나쁜 예시
String s = new String("IU is the best");

// 좋은 예시
String s = "IU is the best";

 

비싼 객체 반복 생성

생성 비용이 비싼 객체인지 매번 명확히 알 수는 없지만, 성능이 갑자기 떨어진다면 비싼 객체를 생성했는지 의심할 수 있다. 정규표현식을 사용해 문자열 매칭을 확인하는 아래 코드가 그 예시이다. String.matches는 정규표현식으로 문자열 형태를 확인하는 쉬운 방법이지만 정규표현식에 사용하는 Pattern 인스턴스는 한 번 사용하고 버려지기 때문에 불변으로 생성하는 것이 좋다. Pattern을 static final로 끄집어내 이름을 지으면 캐싱된 인스턴스를 재사용할 수 있고 코드 의미도 훨씬 잘 드러난다.

// 나쁜 예시
static boolean isRomanNumeral(String s) {
  return s.matches( ... );
}

// 좋은 예시
static boolean isRomanNumeral(String s) {
  private static final Patten ROMAN = Pattern.compile( ... );

  static boolean inRomanNumeral(String s) {
    return ROMAN.matcher(s).matches();
  }
}

 

불필요한 어댑터

일반적으로는 객체가 불변이면 재사용해도 안전하다. 그러나 어댑터(뷰)는 뒷단 객체 외에는 관리할 상태가 없어서, 한 종류의 객체당 어댑터 하나씩만 만들어지면 된다. (== 가변 객체 한 개로 뒷단 객체를 다루어도 된다.)

예를 들어, Map 인터페이스의 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set뷰를 반환한다. 이 Set은 단순히 뷰 역할을 하므로, 매번 같은 객체를 반환하도록 동작한다. 반환한 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀌어야 하고 모두가 똑같은 Map을 대변해야 한다. 따라서 직관과는 다르게 keySet이 반환하는 Set은 가변 객체이더라도 재사용해도 안전하다.

 

클라이언트가 keySet을 두 번 호출해서 받은 setA, setB라는 어댑터(뷰)가 있다고 하자. 이때 setA에 remove 연산을 하면 setB에도 자동으로 적용된다. 둘은 같은 객체(이자 뷰이자 어댑터라고도 부르는 것)를 참조하고 있기 때문이다. 이 어댑터를 수정하면 어댑터에 연결된 Map 객체에도 영향을 미친다. Set뷰라는 가변 객체를 재사용해도 안전한 예시이다. (참고로 keySet은 add, addAll등의 메서드를 지원하지 않고, remove, removeAll, retainAll 등만 지원한다.)

 

오토 박싱

0부터 Integer.MAX_VALUE까지의 합을 출력하는 아래 프로그램은 long에서 Long으로의 언박싱 때문에 불필요한 Long 인스턴스가 만들어진다. 박싱 된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토 박싱을 주의해야 한다.

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

  return sum
}

+ Recent posts