티스토리 뷰

(Effective Java) 규칙6. 불필요한 객체 생성을 피하라


불필요한 객체..


  • 기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 편이 낫다.

    • 객체를 재사용하는 프로그램은 더 빠르고 우아함
    • 변경 불가능 (immutable) 객체는 언제나 재사용할 수 있음
  • 절대적으로 피해야 할 극단적 예

      String s = new String("Sample string");
    • 위 문장은 실행될 때마다 String 객체를 만듬
      • "Sample string"은 그 자체로 String 객체임
  • 바람직한 예

      String s = "Sample String";
    • 위 문장은 실행될 때마다 객체를 만드는 대신, 동일한 String 객체를 사용함
      • 같은 머신(VM)에서 실행되는 모든 코드가 해당 객체를 재사용하게 됨
      • "Sample String" 이라는 String 객체를 여러 곳에서 참조하는 것임
  • 생성자와 정적 팩터리 메서드를 함께 제공하는 변경 불가능 클래스의 경우, 생성자 대신 정적 팩토리 메서드를 이용하면 불필요한 객체 생성을 피할 수 있을 때가 많다.

    • 예) Boolean(String) 보다 Boolean.valueOf(String) 쪽이 바람직
    • 생성자는 호출시 마다 새 객체를 만들지만, 정적 팩터리 메서드는 그러지 않음

변경 가능한 객체의 재사용


  • 변경 가능한 객체도 재사용할 수 있다. (변경할 일이 없다면 말이다..)

  • 흔히 발생하는 사소한 문제

    • 문제 예

        public class Person {
                private final Date birthDate;
                //다른 필드와 메서드, 생성자는 생략
      
                //이렇게 하면 안된다!
                public boolean isBabyBoomer() {
                    //생성비용이 높은 객체를 쓸데없이 생성한다
                    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
                    gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
                    Date boomStart = gmtCal.getTime();
                    gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
                    Date boomEnd = gmtCal.getTime();
                    return birthData.compareTo(boomStart) >= 0 && birthData.compareTo(boomEnd) < 0;
              }
        }
      • 위에 보인 isBabyBoomer 메서드는 호출될 때마다 Calendar 객체 하나, TimeZone 객체 하나, 그리고 Date 객체 두 개를 쓸데 없이 만들어 냄
    • 개선된 예

        public class Person {
            private final Date birthData;
      
          /**
           * 베이비 붐 시대의 시작과 끝
           */
          private static final Date BOOM_START;
          private static final Date BOOM_END;
      
          static {
            Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_START = gmtCal.getTime();
            gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_END = gmtCal.getTime();
          }
      
          public boolean isBabyBoomer() {
            return birthData.compareTo(BOOM_START) >= 0 &&
                birthData.compareTo(BOOM_END) < 0;
          }
        }
      • 개선된 Persen 클래스는 Calendar, TimeZone 그리고 Date 객체를 클래스가 초기화 될 때 한번만 만듬
      • boomStart/End를 지역변수에서 static final 필드로 바꾸자 이 두 날짜가 상수라는 사실이 분명하게 드러나 읽기 좋은 코드가 됨
  • 재사용 가능한 여부가 분명하지 않은 경우 (여담)

    • 예) Map 인터페이스의 keySet() 메서드는 Map 객체의 Set 뷰를 반환하는 데 여러번 호출 했을 때에도 동일한 Set 객체를 반환함
      • 객체 하나가 변경되면 다른 객체도 변경이 된다는 뜻
      • keySet으로 뷰 객체를 여러 개 만들어도 될 것 같지만 쓸데없긴 마찬가지

AutoBoxing


  • JDK 1.5 부터 쓸데없이 객체를 만들 새로운 방법이 생겼다. (자동 객체화(autoboxing))

    • 자바의 기본 자료형과 그 객체 표현형을 섞어 사용할 수 있도록 해줌 (자동으로 변환이 이루어짐)
    • 의미적인(semantic) 차이는 미미하지만 성능 차이는 무시하기 어려움
  • 성능 차이 예

      //무시무시할 정도로 느린 프로그램. 어디서 객체가 생성되는지 알겠는가?
      public static void main(String args[]) {
          Long sum = 0L;
          for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += 1;
          }
          System.out.println(sum);
        }
    • Long과 long의 성능 차이는 7배 가량 난다고 한다.
      • sum에 더해질 때 마다 Long 객체는 생성됨
    • 여기서의 교훈은, 객체 표현형 대신 기본 자료형을 사용하고, 생각지도 못한 자동 객체화가 발생하지 않도록 유의하라는 것

핵심은..


  • 객체를 만드는 비용이 높으니 무조건 피하라는 것은 아니다.
    • 생성자 안에서 하는 일이 작고 명확하면 객체 생성과 반환은 신속하게 이루어짐 (최신 JVM이면 더더욱)
    • 객체를 만들어서 코드의 명확성과 단순성을 높이고 프로그램의 능력을 향상시킬 수 있다면, 일반적으로는 만드는 것이 좋음
  • 객체 풀(object pool)을 만들어 객체 생성을 피하는 기법은 객체 생성 비용이 극단적으로 높지 않다면 사용하지 않는 것이 좋다.
    • 데이터 베이스 연결 같은 비용이 높은 객체들을 당연히 재사용하는 것이 맞음

끝으로

이 글이 도움이 되었다면, Google 광고 한번씩 클릭 부탁 드립니다. 🙏🙏🙏

광고 클릭은 많은 힘이 됩니다!

반응형
댓글