[이펙티브 자바] 객체의 생성과 파괴 Item6 - 불필요한 객체 생성을 피하라

이성훈·2022년 3월 21일
1

이펙티브 자바

목록 보기
7/17
post-thumbnail

이펙티브 자바의 첫 시작은 객체를 생성하고 파괴하는 것에 대한 고찰이다.

"2장 - 객체의 생성과 파괴" 는 다음과 같은 기준으로 맥락을 잡고 있다.

  • 객체를 만들어야 할 때는 언제인가
  • 객체를 만들지 말아야 할 때는 언제인가
  • 올바른 객체 생성 방법은 무엇인가
  • 객체의 불필요한 생성을 피하는 방법은 무엇인가
  • 객체를 제 때에 파괴시키는 방법은 무엇인가
  • 파괴 전에 수행해야 할 정리 작업을 관리하는 요령이 있는가

위와 같은 맥락을 계속 기억하며 공부하자.


  • Item1. 생성자 대신 정적 팩터리 메서드를 고려하라.
  • Item2. 생성자에 매개변수가 많다면 빌더를 고려하라.
  • Item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라.
  • Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라.
  • Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.
  • Item6. 불필요한 객체 생성을 피하라.
  • Item7. 다 쓴 객체 참조를 해체하라.
  • Item8. finalizer와 cleaner 사용을 피하라.
  • Item9. try-finally 보다는 try-with-resources를 사용하라.




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


이번 Item의 경우, 제목 자체가 그렇게 난해하지는 않다.

그래서 이번에는 필자의 관점에 집중하려고 한다.




#  항상 똑같은 기능의 객체인 경우



  • "똑같은 기능의 객체를 매번 생성하기보다는 하나를 재사용 하는 편이 낫다"

객체 지향 방법론이란 자주 사용되는 것들을 객체(하나의 모듈)로 만들고,
언제든지 필요할 때 재사용 할 수 있게 하자는 것이 궁극적인 목표이다.

그런데 이때 객체 생성이 너무 빈번하고 불필요하게 이루어지면 문제가 될 수 있다.


여기서 우리는 불필요하다는 것의 의미를 파악해 볼 필요가 있다.


보통 우리가 클래스로부터 인스턴스를 생성하여 사용할 때,
인스턴스에 조그마한 변화를 줄 수가 있다.

Comment.

예를 들어보자.
A라는 클래스는 어떤 자동차 B를 나타내고 있다고 하자.

B라는 자동차를 생산할 때, 모두 다 A 클래스로부터 인스턴스화 된다.
그런데 아주 단순하게, 그 모든 자동차의 번호판은 다르지 않겠는가?
그래서 A클래스로부터 인스턴스화는 시키되, 필요한 변화를 적용시키게 된다.


그런데 만약 위의 예시에서,
B라는 자동차는 고유한 식별 정도 조차도 없이 모든 정보가 다 같다면 어떨까?

어차피 인스턴스가 모두 같을텐데,
코드를 재사용 한다는 명목으로 매번 인스턴스를 생성하는게 더 낭비이지 않을까?


다음의 예시를 보자.

String s = new String("bikini");

위의 예시는, bikini라는 문장을 생성자한테 넘겨 새로운 String 객체를 만든다.

딱 봤을때 "이게 왜? 어때서?" 라고 생각할 수도 있지만,
이는 정말 쓸데없는 일이다.


위와 똑같은 기능을 하는 다음의 코드를 보자.

String s = "bikini";

둘 모두 문자열을 생성해 변수에 저장하는 역할을 하지만 엄청난 차이가 있다.

첫번째는 매번 객체를 생성하고 있지만 두번째는 그렇지 않다.
그냥 문자열을 하나 만드는건 똑같은데 매번 객체를 생성하는 것은 너무 낭비이다.


보통 생성자의 경우 당연스럽게도 호출될 때마다 객체를 생성하지만,
정적 팩토리 메서드의 경우 반환하는 값을 스스로 조절할 수 있다.

즉, 필요에 따라 기존의 객체를 반환함으로써 불필요한 객체 생성을 막을 수 있다.


사실 뭐.. 요즘은 컴퓨터 스펙이 너무나 좋아졌기 때문에,
웬만한 객체들을 마구잡이로 생성한다고 해도 그렇게 신경쓸 필요는 없다.
(항상 자원의 효율적인 사용을 추구하는 개발자가 아니라면)


하지만 문제는 생성 자체만으로 엄청난 소모비용이 드는 객체들이 있다는 것이다.

이런 객체의 경우 호출될 때마다 생성하는 것보다는,
기존에 만들어두었던 객체를 캐시(Reset) 하고 재사용 하는 것이 낫다.


여기까지 너무 이상적인 이야기지만,
사용하려는 객체가 생성 비용이 비싼 것인지 파악하는 것 자체가 힘들 수 있다.


다시 예를 들어보자.

public class RomanNumerals {

    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

위의 예시는, 입력으로 주어진 문자열이 유효한 로마 숫자인지를 판별한다.
문제점을 찾을 수 없을 만큼 짧은 코드이지만, 문제가 잠재적으로 존재한다.

반환값에서 사용되고 있는 match 메서드가 너무 비효율적이기 때문이다.


Comment.

  • 위에서 match 메서드는 내부적으로 Pattern 인스턴스를 생성한다.
  • 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 isRomanNumeralFast(String s) {
        return ROMAN.matcher(s).matches();
    }

어떤가?

일단 위의 예시에서 문제가 됬던 Pattern 인스턴스를 처음부터 선언 해두었다.

Pattern 인스턴스는 매번 달라지는것 없이 같은 모양이므로,
아예 처음부터 생성해놓고 그것을 재사용하도록 만든것이다.


위와 같이 함으로써,
성능은 훨씬 좋아졌고 코드 또한 매우 명확해졌다.





#  오토박싱 (Auto Boxing)의 경우



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

< 오토박싱이란? >

  • 기본 타입(int 등)을 Wrapper 클래스의 객체 (Integer)로 변환하는 것.
  • Wrapper 클래스란, 기본 타입 데이터를 객체로 취급해야 하는 경우에 객체로 포장해주는 것.

오토박싱의 예는 다음과 같다.

Integer a = 100;

위와 같이 쓸 경우 Integer는 Wrapper 클래스이므로,
오토박싱이 일어나 new Integer(100) 으로 객체를 생성해준다.


문제는 이러한 오토박싱 기능이 불필요한 객체 생성을 발생시킬 때가 있다는 것이다.

다음의 예시를 한번 보자.

public class Sum {
    private static long sum() {
        Long sum = 0L;

        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        return sum;
   
}

위 예시는 양의 정수의 총합을 구하는 메서드이다.

물론 위 코드를 돌려보면 별다른 문제가 없이 돌아간다.
다만 위에서 sum 변수를 long이 아닌 Long으로 선언한 것이 문제가 된다.


위에서 오토박싱의 정의를 이해했다면 알겠지만,
저렇게 될 경우 매순간 객체를 새롭게 생성하게 된다.

완벽하게 비효율적이고 자원을 낭비하는 코드이다.

그러므로, Wrapper 클래스보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 해야한다.




지금까지 불필요한 객체 생성을 피하는 것에 대해서 알아봤다.

다만 여기서 주의해야 할 것은,
"객체 생성은 비싸니까 피해야 한다." 라는 말이 아니라는 것이다.

요새 나오는 JVM의 경우 성능이 좋기 때문에,
프로그램의 명확성, 간결성, 기능을 위해서 객체를 생성하는 것은 오히려 좋다.


또, 이번 장이 "기본 객체를 재사용해야 한다면 새로운 객체를 만들지 마라" 인데,
다른 장은 "새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 마라" 이다.

쉽게 말해 절대적인게 없다는 것이다.


이번 장에서 기본 객체에 대한 새로운 객체 생성을 피하고 재사용하는 방법을 논하고 있지만,

이는 어디까지나 그래야 할 경우 일 때라는 것이다.

그러므로 상황에 맞게 판단하여 가장 최선의 방법을 선택해야 할 것이다.

profile
IT 지식 공간

0개의 댓글