제육's 휘발성 코딩
article thumbnail
반응형

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

String name = new String("sasca37"); // 매번 인스턴스 생성
String name2 = "sasca37"; // 상수풀 사용
  • 똑같은 기능의 객체를 매번 생성하기보다 객체 하나를 재사용하는 편이 나을 때가 많다.

불변 클래스라면 (가변 클래스여도 변경의 여지가 없다면) 생성자보다 정적 팩터리 메서드를 사용하여 객체를 재사용하는 것이 효율적이다.

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 이 초기화된 후 한번도 호출되지 않는다면 낭비가 된다. 호출이 된 후에 초기화를 하는 lazy 로딩 방식이 존재하지만, 권하지는 않는다.

객체가 불변이라면 재사용해도 안전함이 명백하다. 하지만 훨씬 덜 명확하거나, 직관에 반대되는 상황도 있다. 어댑터(뷰)는 실제 작언은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체다. 어댑터는 뒷단 객체만 관리하면 된다.

예를 들어 Map 인터페이스의 keySet 메서드는 키 전 부를 담은 뷰를 반환한다. 해당 메서드를 호출할 때마다 새로운 Set 인스턴스가 생성될 것이라 생각할 수 있겠지만, 사실은 매번 같은 Set 인스턴스를 반환할지도 모른다.

불필요한 객체를 만들어내는 예로 auto boxing이 있다. 오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다. 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.

private static long sum() {
  Long sum = 0L; // 래퍼 타입
  for (long i=0; i<=Integer.MAX_VALUE; i++) sum += i;
  return sum;
}
  • long 이 아닌 Long 인 래퍼 타입으로 선언하여 2^31번 인스턴스가 생성된다. 따라서, 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
  • 매번 객체를 생성하고 회수하는 것도 별도의 비용이 발생하므로, 프로그램의 명확성, 간결성, 기능을 통해 판단하자.
반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^