2장 객체 생성과 파괴
아이템 1 : 생성자 대신 정적 팩터리 메서드를 고려하라
전통적으로 public 생성자를 사용하고 더 나아가 정적 팩터리 메서드를 제공할 수 있다.
public class item1 {
public static void main(String[] args) {
//System.out.println("hello");
System.out.println(item1.valuOf(true));
}
public static Boolean valuOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
}
public 생성자 대신 정적 팩터리 메서드를 제공 시 좋은 다섯 가지 장점이 있다.
장점
- 이름을 가질 수 있다.
→ 여러 형태의 생성자를 만들면 어떤 생성자인지 알기가 어렵지만 정정 팩터리 메서드명으로 유추 할 수 있으면 의미를 파악하기 쉽다.
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
→ 불변 클래스는 인스턴스를 캐싱하여 재활용한다.
→ 플라이웨이트 패턴과 비슷한 기법이다.
→ 반복되는 요청에 같은 객체를 반환하는 인스턴스 통제 클래스이다.
- 통제 클래스는 싱글턴으로 만들거나 인스턴스화 불가로 만들 수 있다.
→ 동치인 인스턴스를 하나임을 보장할 수 있다.
⇒
불변클래스(아이템17), 싱글턴(아이템3), 인스턴스화 불가(아이템4), 열거 타입(아이템34)
- 통제 클래스는 싱글턴으로 만들거나 인스턴스화 불가로 만들 수 있다.
- 반환 타입의 하위 타입 객체를 반환 할 수 있는 능력이 있다.
→ 반환 할 객체를 선택 할 수 있는 유연함을 가진다.
→ 구현 클래스를 공개하지 않아도 그 객체를 반환 할 수 있어 API를 작게 유지 할 수 있고 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.
→ 자바 8 이전에는 인터페이스를 반환하는 정적 메서드가 필요하면 "Types"라는 동반 클래스를 만들어 그 안에 정의하는 것이 관례였고 자바 컬렉션 프레임워크는 45개의 유틸리티 구현체로 제공하며 단 하나의 인스턴스화 불가 클래스인 java.util.Collections에서 정적 팩터리 메서드를 통해 얻도록 했다.
→ 자바 8부터는 인터페이스가 정적 메서드를 가질 수 있게 되어 public 정적 멤버는 인터페이스에 두면 되지만 private은 가질 수 없다.
→ 자바 9부터는 private 정적 메서드까지 가질 수 있지만 정적 필드와 정적 멤버 클래스는 public이어야 한다.
→ private의 제한으로 별도로 private을 모아두는 package-private 클래스를 두어야 할 수도 있다.
⇒
인터페이스 기반 프레임워크(아이템20), 인터페이스만으로 다룬다.(아이템64)
- 입력 매개변수에 따라 매법 다른 클래스의 객체를 반환할 수 있다.
→ 메인 클래스의 하위 클래스의 형으로 조건과 상황에 따라 반환할 수 있다.
→ EnumSet에는 RegularEnumSet과 JumboEnumSet이 하위 클래스도 있고 65개 이하, 이상으로 반환하지만 어떻게 동작하는지 EnumSet을 사용하면서 알 필요가 없다.
⇒
EnumSet클래스(아이템36)
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지도 않아도 된다.
→ 서비스 제공자 프레임워크를 만드는 근간이 되며 JDBC가 대표적이다.
→ 서비스 제공자 프레임워크는 3개의 핵심 컴포넌트와 서비스 제공자 인터페이스로 구성된다.
→ 구현체의 동작을 정의하는 서비스 인터페이스 : Connection이 서비스 인터페이스 역할
→ 제공자가 구현체를 등록 할 때 사용하는 제공자 등록 API : DriverManager.registerDriver가 제공자 등록 API 역할
→ 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API: DriverManager.getCOnnection이 서비스 접근 API역할
→ 서비스 제공자 인터페이스 : Driver가 서비스 제공자 인터페이스 역할을 수행
→ 자바 5부터는 java.util.ServiceLoader라는 범용 서비스 제공자 프레임워크가 제공되었다.
⇒
리플렉션(아이템65), 의존 객체 주입 프레임워크(아이템5)
단점
- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
→ 유틸리티 구현 클래스들은 상속을 할 수 없다. 단, 컴포지션을 사용하도록 유지하고 불변 타입으로 만들려면 이 제약은 장점으로 볼 수 있다.
- 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
→ 메서드에 대한 명확한 지침을 바로 명확하게 알기가 어렵다.
→ 이를 위해 API 문서를 잘 써놓고 관례의 명명 규칙을 따라 문제를 완화한다.
- from : 매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
Date d = Date.from(instant);
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf : from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance 혹은 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
StackWalker luke = StackWalker.getInstance(option);
- create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
- getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 메서드가 반환할 객체의 타입이다.
FileStore fs = Files.getFileStore(path);
- newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 메서드가 반환할 객체의 타입이다.
BufferedReader br = Files.newBufferedReader(path);
- type : getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);
- from : 매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
⇒ 컴포지션(아이템18), 불변타입(아이템17)
무조건 public 생성자를 만들기보다 정적 팩터리를 사용하는게 유리한 경우가 많으니 잘 고려해보자.
아이템 2 : 생성자에 매개변수가 많다면 빌더를 고려하라
생성자 매개변수가 많은 상황에서의 대안 세가지
- 생성자의 매개변수가 많아지면 점층적 생성자 패턴을 사용하는 경우가 많은데 각 생성자가 어떤 의미를 가지고 매개변수가 무엇인지, 필수인지, 선택인지 알기가 어렵다.
- 자바빈즈패턴으로 대체 할 수 있지만 객체를 만들려면 여러개의 메소드를 호출해야 하여 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 되며 불변이 될수 없어 setter는 지양해야 한다. 요즘 실무에서 많이 겪고 있는 문제이다.
- 얼리기(freezing)로 대체 할 수 있다고 하는데 java에서는 찾아보니 없는걸로 보이지만 setter에 얼렸는지 확인하는 함수를 만들어서 사용 할 수 있다.
- 점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 겸비한 빌더 펴턴이다.
- 필수 매개변수만 생성자를 호출해 빌더 객체를 얻고 빌더 객체가 제공하는 세터 메서드들로 원하는 선택 매개변수들을 설정하고 build 메서드로 객체를 얻는다
- 빌더 패턴은(파이썬과 스칼라에 있는) 명명된 선택적 매개변수를 흉내 낸 것이다.
- 빌더 패턴은 계층적으로 설계 된 클래스와 함께 쓰기에 좋다.
- lombok의 builder와 유사
⇒ 매개변수를 실수로 순서를 바꿔보내면 알기 어려운 버그가 된다.(아이템51), 불변(아이템17), 공격에 대비해 이런 불변식을 보장하려면 빌더로부터 매개변수를 복사한 후 해당 객체 필드들도 검사해야 한다.(아이템50)
API는 시간이 지날수록 매개변수가 많아지는 경향이 있으니 애초에 빌더로 시작하는게 나을때가 많다.
아이템 3 : private 생성자나 열거 타입으로 싱글턴임을 보증하라
싱글턴은 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다. 하지만 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다.
- 타입을 인터페이스로 정의하고 인터페이스를 구현 한 싱글턴이 아니면 mock으로 대체 할 수 없다.
싱글턴을 만드는 방식 두 가지
- 생성자를 private으로 감추고 유일한 인스턴스로 접근 하는 public static final 멤버를 둔다.
- 초기화 할 때 딱 한번만 호출 되므로 유일한 인스턴스를 가진다.
- 예외로는 권한이 있는 클라이언트가 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출 할 수 방어로는 생성자를 수정하여 두번쨰 객체가 생성되면 예외를 던지면 된다고 책에서 그러는데 난 안된다.
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { } public void leaveTheBuilding() { }
- 정적 팩토리 메서드를 public static 멤버로 제공한다.
- public 필드로 싱글턴임을 API에 명백하게 드러나고 간결하다.
- API를 바꾸지 않아도 싱글턴이 아니게 변경 할 수 있다.
- 원한다면 정적 팩토리를 제네릭 싱글턴 팩터리로 만들수 있다
public class Elvis { public static final Elvis INSTANCE = new Elvis(); public static Elvis getInstance() { return INSTANCE; } private Elvis() { }
주의 사항
- 위 두 방식으로 만든 싱글턴 클래스를 직렬화하고 역직렬화 할 경우 모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공하지 않으면 역질렬화 하면서 새로운 인스턴스가 생성 된다.
싱글턴을 만드는 마지막 방법
- 원소가 하나인 열거 타입을 선언한다.
- public 필드 방식과 비슷하지만, 더 간결하고, 추가 노력 없이 직렬화 할 수 있고, 심지어 복잡한 직렬화 상황이나 리플렉션 공격에서도 완벽히 막아준다.
⇒ 정적 팩터리를 제네릭 싱글턴팩터리로 만들수 있다는점이다.(아이템30), 정적 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있다는 점이다.(아이템43,44), 모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야 한다.(아이템89)
대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.
아이템 4 : 인스턴스화를 막으려거든 private 생성자를 사용하라
객체 지향적 개발 방식과 다르게 정적 메서드와 정적 필드를 담은 클래스를 만드는 경우가 있는데 예외적으로 쓰임새가 있는 경우가 있다.
- java.lang.Math, java.util.Arrays, java.util.Collections ...
- final 클래스와 관련 된 메서드들
위 메서드들은 인스턴스로 사용하려고 만든게 아니지만 컴파일러가 자동으로 기본 생성자를 만들기 때문에 사용자는 자동으로 생성된 것인지 구분 할 수 없게 된다.
인스턴스화를 막는 방법
- 추상 클래스로 만드는것으로는 인스턴스화를 막을 수 없다. 하위 클래스를 만들어 인스턴스화 하면 되지만 상속해서 쓰라는 뜻으로 오해 할 수 있다.
- 컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 경우이니 private 생성자를 추가하면 된다.
public class Item4 {
private Item4() {
throw new AssertionError();
}
}
⇒ 상속해서 쓰라는 뜻으로 오해할 수 있으니 더 큰문제이다.(아이템19),
인스턴스를 막으려면 생성자에 private을 하자
아이템 5 : 자원을 직접 명시하지 말고 의존 객체 주임을 사용하라
하나 이상의 자원에 의존하는 클래스의 경우 의존 객체 주입을 사용해야 한다.
맞춤법 검사기는 여러 언어별, 특수 어휘용 등의 사전에 의존하는 경우를 예를 들 수 있다. 사전 하나로 모든 상황에 대응할 수 없다.
- 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
- 의존 객체 주입은 딱 하나의 자원만 사용하지만, 자원이 몇 개든 의존 관계가 어떻든 관계 없이 잘 작동하고 불변을 보장한다.
- 생성자, 정적 팩터리, 빌더에 똑같이 응용할 수 있다.
public class Item5 {
private final Lexicon dictionary;
public Item5(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {
}
}
더 나아가서는 생성자에 자원 팩터리를 넘기는 방식으로 호출 할 떄마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체로, 팩터리 메서드 패턴이라고 한다.
- 자바 8에서 소개한 Supplier 인터페이스가 팩터리의 완벽한 예이다.
- 입력 메서드는 한정적 와일드 카드로 받아 명시한 하위 타입은 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.
Mosaic create(Supplier<? extneds Tile> tileFactory {...}
⇒ 불변(아이템17), 정적팩터리(1), 빌더(아이템2), Supplier<T>를 입력으로 받는 메서드는 일반적으로 한정적 와일드카드 타입을 사용(아이템31)
⇒ 스프링에서는 생성자 주입을 사용하면 junit에 용이하다.
하나 이상에 의존하고 자원에 영향을 받으면 의존 객체 주입 패턴으로 구현하는 방법으로 구현하면 유연성, 재사용성, 테스트 용이성을 기막히게 개선해준다.
아이템 6 : 불필요한 객체 생성을 피하라
똑같은 기능의 객체는 하나를 재사용하는 편이 나을 때가 많고 불변 객체는 언제든 재사용 할 수 있다.
String s = new String("bikini"); // 사용 하지 말자
String ss = "bikini"; // 하나의 인스턴스를 사용하는 방법
Boolean b = new Boolean("true"); // 비권장 API
Boolean bb = Boolean.valueOf("true"); // 권장 API
- 생성자는 호출할 때마다 새로운 객체를 만들지만, 팩터리 메서드는 전혀 그렇지 않다. 불변 객체만이 아니라 가변 객체라 해도 사용변경되지 않으면 재사용할 수 있다.
- 생성 비용이 아주 비싼 객체도 있으니 반복해서 필요하다면 캐싱하여 재사용하길 권한다.
- 아래 코드 정규표현식용 Pattern 인스턴스는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높은데 한번 사용하고 gc 대상이 된p>
static boolean isRoamnNumeral(String s) { return s.matches("^(?=.>M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V/I{0,3})$"); }
- 아래처럼 캐싱하여 재사용해 성능을 개선한디.
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 isRoamnNumeral(String s) { return ROMAN.matcher(s); }
- 개선 후 클래스가 초기화 된 후 한번도 사용하지 않으면 ROMAN 필드는 쓸데 없이 초기화 하게되는데 처음 호출 될 때 지연 초기화를 불필요한 초기화를 없을 수 있지만 권하지는 않는다. 한다고 해도 성능이 크게 개선 되는 경우가 드물다.
- 아래 코드 정규표현식용 Pattern 인스턴스는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높은데 한번 사용하고 gc 대상이 된p>
- 덜 명확하고 직관에 반대되는 상황에서 어댑터는 실제 작업은 뒷단 객체에 위임하고 자신은 제2의 인터페이스 역할을 해주는 객체이므로객체 외에는 관리 할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.
- Map 인터페이스의 keySet 메서드는 Map객체 안에 키 전부를 담은 Set 뷰를 반환하는데 keySet을 호출 할 때마다 새로운 Set 인스턴스를 보장하지 않는다.
- 반환 한 인스턴스가 가변이어도 반환 된 인스턴스들은 기능적으로 똑같고 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀐다.
- 그러므로 keySet이 뷰 객체를 여러 개 만들어도 상관은 없지만 그럴 필요도 이득도 없다.
- 오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술로 기본 타입과 그에 대응하는 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.
- 기본 타입과 박싱 된 타입은 의미상 같지만 성능에서는 다르다.
- 아래 코드는 정상적으로 구동되지만 느리다. long이 아닌 Long을 선언해 불필요한 인스턴스가 약 231개나 만들어진다.(대략 long 타입인Long 타입인 sum에 더해질 때마다)
private static long sum() { Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; } return sum; }
- 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
- 아주 무거운 객체가 아니면 단순한 객체 생성은 피하려고 사용자만의 객체 풀을 만들지 말자.
- 재사용하기 좋은 예로는 데이터베이스 연결 같은 경우는 생산 비용이 비싸니 재사용하는 편이 좋다.
⇒ 불변 객체(아이템17), 지연 초기화(아이템83), 성능은 크게 개선되지 않을 떄가 많기 때문이다.(아이템67), 의미상으로는 별다를 것 없지만 성능에서는 그렇지 않다.(아이템61), 방어적 복사(아이템50)
방어적 복사(아이템50)와 대조적인 아이템6이다. 기존 객체를 재사용 하되 객체를 만들어야 할 때는 만들어라이다. 방어적 복사에 실패하면 언제 터져 나올지 모르는 버그와 보안 구멍으로 이어지지만, 불필요한 객체 생성은 그저 코드 형태와 성능에만 영향을 준다.
아이템 7 : 다 쓴 객체 참조를 해제하라
c와 c++처럼 메모리를 직접 관리 하지 않아도 되는 gc가 좋지만 그래도 해줘야 할 경우가 있다.
1. 메모리 누수의 문제
- 아래 소스는 정상적으로 동작하지만 다 쓴 참조를 여전히 가지고 있기 때문에 메모리 누수가 발생한다.
- 다 쓴 참조는 스택을 쌓았다 꺼냈다 하면서 늘어난 size에서 발생한다.
- gc는 이를 제어할 수 없고 사용자가 관리 해주어야 하는 메모리 영역이다.
public class Item7 {
private Object[] elements;
private int size;
private static final int DEFAULT_INTTIAL_CAPACITY = 16;
public Item7() {
elements = new Object[DEFAULT_INTTIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
OBject result = elements[--size];
elements[size] = null;
return result;
// return elements[--size];;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
메모리 누수 해결 방법
- 해법으로는 사용한 참조에 null 처리 하면 된다.
- 다만, 사람인지라 실수 할 수 있으니 gc에게 맡겨서 처리하는 방법으로 찾아보자.
2. 캐시를 사용할 때 참고 할 점
- 키를 참조하는 동안만 엔트리가 살아 있는 캐시가 필요한 경우에는 WearkHashMap을 사용해 캐시를 만들면 다 쓴 엔트리는 즉시 제거 된다.
- 캐시 엔트리의 유효 기간을 정확히 정의 하기 어려운 경우 시간이 지날수록 가치를 떨어뜨려서 스케줄러로 청소를 하는 방법을 흔히 사용한다.
3. 리스너 혹은 콜백을 만들거나 사용 할 경우
- 클라이언트가 등록만 하고 해지 않거나 조치하지 않고 쌓여만 갈 경우 약한 참조(ex : WeakHashMap) 로 저장하면 gc가 즉시 수거해 간다.
⇒ 제네릭 버전(아이템29), 변수의 범위를 최소가 되게 정의(아이템57)
메모리 누수는 수년간 잠복할 수도 있어서 발견을 하려면 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원하면 발견되기도 하지만 예방법이 가장 중요하다.
아이템 8 : finalizer와 cleaner 사용을 피하라
finalizer와 cleaner는 이렇다.
- 자바에서 제공하는 finalizer는 예측할 수 없고 상황에 따라 위험할 수 있어 일반적으로 불필요하다. 또한, 자바9에서 사용 자제 API가 되었다.
- 자바에서 제공하는 cleaner는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불 필요하다.
- finalizer와 cleaner는 즉시 수행된다는 보장이 없어 실행 되기까지 얼마나 걸릴지 알 수 없으므로 제때 실행되어야 하는 작업은 절대 할 수없다.
- 자바 언어 명세는 어떤 스레드가 finalizer를 수행 할지 명시하지 않고 cleaner는 자신을 수행할 스레드를 제어할 수 있지만 백그라운드에서 수행되어 gc 통제하에 있기 때문에 즉시 수행된다는 보장이 없다.
- 수행 시점뿐 아니라 수행 여부조차도 보장하지 않아 종료 작업이 수행되지 못한 채 프로그램이 중단될 수도 있다.
- 상태를 영구적으로 수정하는 작업에는 절대 finalizer나 cleaner에 의존해서는 안 된다.
- 성능 문제도 심각하다. AutoCloseable 객체를 생성하고 gc를 하면 12ns 걸리고(try-with-resources) finalizer를 사용하면 550ns가 걸렸다. cleaner는 500ns 걸렸고 안전망 형태로 사용하면 빨라지지만 객체 하나를 생성, 정리, 파괴하는데 66ns로 5배 차이가 난다.
- finalizer를 사용한 클래스는 finalizer 공격에 노출되어 보안 문제를 일으 킬 수 있다.
- 직렬화 과정에서 예외가 발생하면 생성 되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수도 있는데 정적 필드에 자신의 참조를 할당하여 gc가 수집하지 못하게 막고 허용되지 않았을 작업을 수행하게될 수도 있다.
- final이 아닌 클래스를 finalizer 공격으로부터 방어하려면 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.
finalizer과 cleaner 대신에 AutoCloseable을 사용하자
- AutoCloseable 객체를 다 사용하고 나서 close 메서드를 호출 한다.
- try-with-resources를 같이 쓰자. try-with-resources와 try-finally로 해결하는게 좋고 둘 중 try-with-resources가 아주 좋다.
finalizer와 cleaner를 적절하게 사용하는 방법
- 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 아예 안하는것보다 나으니 안전명 역할을 하도록 사용하지만 그럴만한 값있는지 심사숙고하고 FileInpuStream, FileOutputStream, ThreadPoolExcutor가 대표적이다.
- 네이티브 피어와 연결 한 객체에서 사용 가능하며 네이티브 객체란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 말한다.
- 네이티브 피어는 자바 객체가 아니니 gc는 존재를 알지 못하여 네이티브 객체까지 회수 하지 못하므로 cleaner나 finalizer로 처리하기 적당하다.
- 단, 성능 저하를 감당하고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당한다.
- cleaner의 사용 방법
- cleaner를 안전망으로 활용하는 AutoCloseable 클래스
- 방 청소인 run 메서드가 호출 되는 상황은 둘 중 하나이다.
- Item8의 close 메서드가 호출 될 경우
- gc가 Item8을 회수 할 경우 cleaner가(바라건대) State의 run 메서드를 호출해줄 것이다.
public class Item8 implements AutoCloseable { private static final Cleaner cleaner = Cleaner.create(); private static class State implements Runnable { int numJunkPiles; State(int numJunkPiles) { this.numJunkPiles = numJunkPiles; } @Override public void run() { System.out.println("방 청소"); numJunkPiles = 0; } } private final State state; private final Cleaner.Cleanable cleanable; public Item8(int numJunkPiles) { state = new State(numJunkPiles); cleanable = cleaner.register(this, state); } @Override public void close() { cleanable.clean(); } public static void main(String[] args) { try (Item8 i = new Item8(7)) { System.out.println("안녕~"); } } }
⇒ try-with-resources(아이템9), 생성자나 직렬화 과정(readObject와 readReseolve 메서드/ 아이템12), 자동으로 바깥 객체의 참조를 갖게 되기 때문이다.(아이템24)
finalizer는 가급적 지양하고 cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자
아이템 9 : try-finally보다는 try-with-resources를 사용하라
InputStream, OutputStream, java.sql.Connection 등 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많고 닫지 못하거나 않으면 성능 이슈로 미칠 수 있다.
try-finally는 더 이상자원 회수의 최선의 방책이 아니다.
- 하나의 try-finally일 경우는 크게 문제가 없다.
static String firstLineOfFile(String path) throws Exception { BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { br.close(); } }
- try-finally를 하나 더 추가하면 가독성이 떨어지고 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다.
static void copy(String src, String dst) throws IOException { InputStream in = new FileInputStream(src); try { OuputStream 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(); } }
try-finally를 대신 할 try-with-resources
자원에서 AutoCloseable을 구현해야 하며 이미 자바 라이브러리와 서드파티 라이브러리들의 수 많은 클래스와 인터페이스가 이미 AutoCloseable을 구현하거나 확장해뒀다.
위와 같이 두 예외가 발생해도 첫번째 예외와 두번째 예외 모두 표출 되어 디버깅에 용이하다.
- 기본형
static String firstLineOfFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
- 복수의 자원일 경우
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); } }
- catch절과 함께 쓰는 모습
static String firstLineOfFile2(String path, String defaultVal) { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } catch (IOException e) { return defaultVal; } }
⇒ finallizer는 그리 믿을만하지 못하다(아이템8)
꼭 회수 할 경우에는 예외 없이 try-with-resources를 사용하자.
'나(다) > 책' 카테고리의 다른 글
이펙티브 자바 - 4장 : 클래스와 인터페이스 (0) | 2020.12.20 |
---|---|
이펙티브 자바 - 3장 : 모든 객체의 공통 메서드 (0) | 2020.12.18 |
이펙티브 자바 - 1장 : 들어가기 (0) | 2020.12.12 |
니체의 말2 (0) | 2020.12.08 |
니체의 말1 (0) | 2020.12.05 |