Effective Java 3/E 북스터디 2장_item)6~9

tth-k·2023년 12월 15일

북스터디

목록 보기
2/2
post-thumbnail

2장 객체 생성과 파괴(Item 6 ~ 9)

아이템 6) 불필요한 객체 생성을 피하라

  • 똑같은 기능의 객체를 매번 생성하기보단 객체 하나를 재사용하는 편이 나을 때가 많다.
String s = new String("apple"); // 따라 하지 말 것!
String s = "apple"; // 개선된 코드

ex) 주어진 문자열이 유효한 로마 숫자인지를 확인하는 메서드 작성

package step2;

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

public class item6_1 {
    // 성능을 훨씬 더 끌어올릴 수 있다
    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는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.
package step2;

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

import java.util.regex.Pattern;

public class item6_2 {
    // 값비싼 객체를 재사용해 성능을 개선한다.
    public 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();
    }
}
  • 이렇게 개선하면 isRomanNumeral이 빈번하게 호출되는 상황에서 성능을 끌어올릴 수있다.

  • Boxing Type 대신 Primitive Type을 권장한다.

package step2;

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

public class item6_3 {
    // Boxing Type
    private static long sum1(){
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }

    // Primitive Type
    private static long sum2(){
        long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }
}
  • sum1과 sum2를 비교했을 때 sum2가 약 10배 정도 빠른 실험 결과가 나왔다.
  • Boxing된 기본타입보다는 기본타입을 사용하고, 의도치 않은 AutoBoxing을 조심해야한다.

아이템 7) 다 쓴 객체 참조를 해제하라

package step2;

// item7 : 다 쓴 객체 참조를 해제하라

import java.util.EmptyStackException;

public class Java_7 {
    // 제대로 구현한 pop 메서드
    public Object pop(){
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }
}
  • 다 사용한 참조 null 처리 시 이점
    - null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 던지며 종료된다.
  • 하지만 모든 객체를 다 사용하자마자 일일히 null 처리 시 지저분해질 수 있다.

  • 객체 참조를 null처리하는 일은 예외적인 경우여야 한다.

  • 핵심정리
    - 메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례가 있다.
    - 누수는 리뷰코드나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다.
    - 예방법을 익혀두는게 중요하다.

item 8) finalizer와 cleaner 사용을 피하라

  • Java의 2가지 객체 소멸자
    - finalizer
    - cleaner

  • finalizer의 특징
    - 예측불가
    - 상황에 따라 위험할 수 있어 일반적으로 불필요하다.

  • cleaner의 특징
    - finalizer의 대안으로 finalizer보다는 덜 위험하다.
    - 예측불가
    - 느리고, 일반적으로 불필요하다.

  • finalizer와 cleaner가 할 수 없는 작업과 문제
    - 제때 실행되어야 하는 작업
    - 상태를 영구적으로 수정하는 작업
    - 심각한 성능 문제

  • finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안문제를 일으킬 수 있다.
    - 객체 생성을 막으려면 생성자에서 예외를 던지는 것만으로도 충분하지만 finalizer가 있다면 그렇지 않다.
    - 해결방안 : final이 아닌 클래스를 finalizer 공격으로부터 방어하려면 아무 일도 하지 않는 finalizer 메서드를 만들고 final로 선언한다.

  • finalizer와 cleaner를 대신해줄 방법 : AutoCloseable
    - AutoCloseable를 구현하고 크라이언트에서 인스턴스를 다 사용하고 나면 close 메서드를 호출하면 된다.

  • 핵심정리
    - cleaner(자바 8까지는 finalizer)는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용
    - 사용 시 불확실성과 성능저하에 주의해야 한다.

item9) try-finally 보다는 try-with-resources를 사용하라

  • 자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야하는 자원이 많다. ex) InputStream, OutputStream, java.sql.Connection 등

  • 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰이지만 자원을 회수하는 최선의 방법이 아니다.

package step2;

// item9 : try-finally 보다는 try-with-resources를 사용하라

import java.io.*;

public class Java_9_1 {
    // 자원이 둘 이상이면 try-finally 방식은 지저분하다.
    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }
}
  • 다음은 9_1 코드에 try-with-resources를 적용한 모습이다.
package step2;

// item9 : try-finally 보다는 try-with-resources를 사용하라

import java.io.*;

public class Java_9_2 {
    // try-with-resources 적용
    static void copy(String src, String dst) throws IOException {
        
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }
}
  • 훨씬 짧고 읽기 수월하며 추후 문제를 찾기에도 좋다.

  • 핵심정리
    - 꼭 회수해야하는 자원을 다룰 때는 try-with-resources를 사용
    - 코드는 더 짧고 분명해지고 만들어지는 예외 정보도 훨씬 유용하다.
    - finally가 필요한 상황이면 try-with-resources를 사용

** 이미지출처) Google yes24

profile
백엔드 취준생 / 코린이 ヾ(≧▽≦*)o

0개의 댓글