Java/Effective Java

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

메성 2019. 12. 22. 20:06
반응형

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

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

  • 한 코드의 예를 통해 살펴보자

    String s = new String("Effective Java");
    String s = "Effective Java";
    • 첫번 째 문장은 실행될 때마다 String 인스턴스가 새로 만들어진다.
      • 이 문장은 매우 쓸데 없다.. 만약 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없이 해당 인스턴스가 새로 만들어질 수 있다. 그냥 재사용하면 되는데..
    • 두번 째 문장을 살펴보면 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다. 즉, 반복문이나 빈번히 호출되는 메서드 안에 있어도 가상 머신 안에서 재사용함이 보장되는 것이다

  • 생성자 대신 정적 팩토리 메서드를 제공하는 불변 클래스에서는 정적 팩토리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.

    Boolean(String);            //생성자 사용
    Boolean.valueOf(String);    //팩토리 메서드 사용
    • 생성자는 호출할 때마다 새로운 인스턴스를 만들지만, 팩토리 메서드는 전혀 그렇지 않는다.

  • 생성 비용이 비싼 객체를 반복해서 필요한 경우가 있다면 캐싱하여 사용하기를 권장한다.

  • 다음 문자열이 유효한 로마 숫자인지를 확인하는 메서드를 보자

    static boolean isRomanNumeral(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})...");
    }
    • 이 방식은 문제가 발생하는데, String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.

      • 왜냐하면, 해당 메서드(matches) 안에는 정규 표현식을 표현하는 Pattern 인스턴스가 있는데, 이는 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 되고, 반복적으로 사용하게 되면 쓸데없이 Pattern 인스턴스를 생성하기 때문이다.
    • 성능을 개선하기 위해서는 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 인스턴스를 재사용 하는 것이다.

      static boolean isRomanNumeral(String s) {
          private static final Pattern ROMAN = Pattern.compile(
              "^(?=.)M*(C[MD]|D?C{0,3})...");
      }
      
      static boolean isRomanNumeral(String s) {
          return ROMAN.matcher(s).matches();
      }
      • 이렇게 개선할 시 isRomanNumeral이 빈번히 호출되는 상황에서 성능을 상당히 끌어올릴 수 있다. why? 정규표현식을 표현하는 Pattern 인스턴스를 직접적으로 초기화 및 컨트롤하여 matches()안에서 Pattern 인스턴스를 생성하지 않기 때문에 쓸데없는 객체 생성이 일어나지 않는다.
      • 또한, 개선 전에는 존재조차 몰랐던 Pattern 인스턴스를 static final 필드로 끄집어내고 이름을 지어주오 코드의 의미가 훨씬 잘 드러난다.
  • 하지만 주의해야 할 것은, 이렇게 개선된 isRomanNumeral 방식의 클래스가 초기화 한 후 한번도 호출하지 않는다면 ROMAN 필드는 쓸데없이 초기화된 꼴이다.

    • 메서드를 처음 호출될 때 초기화하는 방식을 지연초기화라고 하는데, 지연초기화는 코드를 복잡하게 만들고, 성능에 크게 개선되지 않을 때가 많아 사용하는 것에 주의를 가져야 할 것이다.

  • 불필요한 객체를 만들어내는 또 다른 예로는 오토박싱을 들 수 있다.

  • 오토박싱

    • 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 대 자동으로 상호 변환해주는 기술이다.
  • 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다

  • 예를 통해 살펴보자

    private static long sum() {
        Long sum = 0L;
        for(long i = 0; i <= Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }
    • sum 변수를 long 타입이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 약 231개나 만들어진 것이다.
    • 단순히 Long 타입을 long으로 바꿔주면 시간은 6.3초에서 0.59초로 빨라진다.
  • 즉, 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박시잉 숨어들지 않도록 주의해야한다.


  • 이번 아이템에서 객체 생성은 비싸니 피해야 한다가 아니라 시점에 맞게 사용하라는 것이다.
  • 일반적으로 요즘 JVM은 부담되지 않는 객체를 생성하고 회수하는 부분은 크게 부담되지 않으므로, 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 일이라면 일반적으로 좋은 작업이다.
  • 아이템 50에서는 방어적 복사(새로운 객체를 만들어야 한다면 기존 객체를 재사용하지마 라)를 다루는데, 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가 필요없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억해두어야 한다.

http://www.yes24.com/Product/Goods/65551284?scode=032&OzSrank=1

 

이펙티브 자바 Effective Java 3/E

자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브러리의 최신 기능을 십분 활용하도록 내용 전반을 철저히 다시 썼다. 모던 자바가 여러 패러다임을 지원하기 시작하면서 자바 개발자들에게는...

www.yes24.com

 

반응형