생성자 대신 정적 팩터리 메서드를 고려하라
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE;
}
정적 팩터리 메서드가 생성자보다 좋은 장점 다섯 가지
1. 이름을 가질 수 있음 : 이름을 통하여 기능 유추가 가능, 생성자는 시그니처에 제약이 있음, 똑같은 타입을 파라미터로 받는 생성자 두개를 만들 수 없으니까 그런 경우에도 정적 팩토리 메소드를 사용하는 것이 유용함
//정적 팩토리 메소드 public static Item newIphone(){ return new Item(apple, camera, text); } //생성자를 사용하는 경우 Item iphone = new Item(apple, camera, text);
public Foo(String name){ this.name = name; } //에러가 남(똑같은 String을 받아오므로) public Foo(String address){ this.address = address; }
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 됨 : 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있음(인스턴스 통제 클래스), 인스턴스를 통제하면 클래스를 싱글턴으로 만들 수도, 인스턴스화 불가로 만들 수도 있고, 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있음(인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거 타입은 인스턴스가 하나만 만들어짐을 보장)
//java.math.BigInteger.valueOf메서드의 코드 public static final BigInteger ZERO = new BigInteger(new int[0], 0); private final static int MAX_CONSTANT = 16; private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1]; private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1]; static { /* posConst에 1 ~ 16까지의 BigInteger 값을 담는다. */ /* negConst에 -1 ~ -16까지의 BigInteger 값을 담는다. */ } public static BigInteger valueOf(long val) { // 미리 만들어둔 객체를 리턴한다 if (val == 0) return ZERO; if (val > 0 && val <= MAX_CONSTANT) return posConst[(int) val]; else if (val < 0 && val >= -MAX_CONSTANT) return negConst[(int) -val]; // 새로운 객체를 만들어 리턴한다 return new BigInteger(val); }
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있음 : 엄청난 유연성을 응용하면 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있음(인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용하는 인터페이스 기반 프레임 워크를 만드는 핵심 기술, 클라이언트는 알아야 할 개념의 개수와 난이도가 줄음), JAVA9는 public static 메소드뿐 아니라 private static 메소드 생성 가능(private static 메서드가 필요한 경우 : 클래스 안에서 사용 밖으로 노출할 필요가 없을때(static 메서드는 static만 호출가능), 메서드의 스코프 자체가 private임)
class OrderUtil { public static Discount createDiscountItem(String discountCode) throws Exception { if(!isValidCode(discountCode)) { throw new Exception("잘못된 할인 코드"); } // 쿠폰 코드인가? 포인트 코드인가? if(isUsableCoupon(discountCode)) { return new Coupon(1000); } else if(isUsablePoint(discountCode)) { return new Point(500); } throw new Exception("이미 사용한 코드"); } } //Coupon과 Point객체를 선택적으로 리턴하는데, 이를 위해서는 두 하위 클래스가 같은 인터페이스를 구현하거나, 같은 부모 클래스를 갖도록 하면 됨 class Coupon extends Discount { } class Point extends Discount { }
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있음 : EnumSet 클래스는 생성자 없이 public static 메소드, allOf(), of() 등을 제공하는데 enum 타입의 개수에 따라 RegularEnumSet 또는 JumboEnumSet으로 달라짐
enum Color{ RED, BLUE, WHITE } EnumSet<Color> colors = EnumSet.allOf(Color.class); EnumSet<Color> blueAndWhite = EnumSet.of(BLUE, WHITE);
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 됨 : JDBC의 getConnection() 경우처럼 연결하는 DB(Driver)에 따라 다른 값을 가져옴(의존성 주입과 살짝 비슷하지만 공급받는 거라고 생각해야함)
public static Foo getFoo(boolean flag){ Foo foo = new Foo(); //어떤 특정 약속 되어 있는 텍스트 파일에서 Foo의 구현체의 FQCN(Fully Qualified Class Name)을 읽어옴 //FQCN에 해당하는 인스턴스를 생성 //foo 변수를 해당 인스턴스를 가리키도록 수정 //이렇게 하면 호출하는 시점에 텍스트 파일에 모가 적혀 있느냐에 따라 다른 결과 값을 제공 return foo; }
단점 두가지
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들수 없음 : 불변 타입인 경우나 상속 대신 컴포지션을 권장함
2. 정적 팩터리 메서드는 프로그래머가 찾기 어려움 : 생성자처럼 API설명에 드러나지 않으므로 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야함, 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 like = StackWalker.getInstance(options); //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);
생성자에 매개변수가 많다면 빌더를 고려하라
//점층적 생성자 패턴 - 확장하기 어려움 public NutritionFacts(int servingsSize, int servings, int calories, int fat, int sodium, int carbohydrate){ this.servinsSize = servingSize; this.servings = servins; this.calories = calories; this.fat = fat; this.sodium = sodiu,; this.carbohydrate = carbohydrate; } //생성자 NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
생성할 파일이 늘어나면 복잡해지고, 순서를 바꾸면 엉뚱한 동작을 함
//자바빈즈 패턴- 일관성이 깨지고, 불변으로 만들 수 없음 NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100); cocaCola.setSodium(35);m cocaCola.setCarbohydrate(27);
메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태이므로 클래스를 불변으로 만들 수 없으며 스테드 안전성을 얻으려면 추가 작업이 필요
//빌더 패턴- 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비
public class NutritionFacts{ private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder{ //필수 매개변수 private final int servingSize; private final int servins; //선택 매개변수 - 기본값으로 초기화한다. private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings){ this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val){ calories = val; return this; } public Builder fat(int val){ fat= val; return this; } public Builder sodium(int val){ sodium= val; return this; } public Builder carbohydrate(int val){ carbohydrate= val; return this; } public NutritionFacts build(){ return new NutritionFacts(this); } } private NutritionFacts(Builder builder){ servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } } //클라이언트 코드 NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수를 흉내 낸 것
장점 - 빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있고, 객체마다 부여되는 일련번호와 같은 특정 필드는 빌더가 알아서 채우도록 할 수도 있음
단점 - 객체를 만들려면, 그에 앞서 빌더부터 만들어야 하는데 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제 될 수 있음, 점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 함(API는 시간이 지날수록 매개변수가 많아지는 경향이 있으므로 애초에 빌더로 개발 하는게 나을 때가 많음)
※불변(immutable 혹은 immutability)은 어떠한 변경도 허용하지 않는다는 뜻
※불변식(invariant)은 프로그램이 실행되는 동안, 혹은 정해진 기간 동안 반드시 만족해야 하는 조건
private 생성자나 열거 타입으로 싱글턴임을 보증하라
싱글턴 - 인스턴스를 오직 하나만 생성할 수 있는 클래스(무상태? 객체나 설계상 유일해야 하는 시스템 컴포넌트)
싱글턴을 만드는 방법 3가지
첫번째, public static멤버가 final 필드인 방식
//코드 3-1 public static final 필드 방식의 싱글턴 public class Elvis { public static final Elvis INSTANCE =new Elvis(); private Elvis(){ ... } public void leaveTheBuilding(){ ... } }
private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한 번만 호출되고, public이나 protected 생성자가 없으므로 Elvis 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임을 보장
장점 - 1. 해당 클래스가 싱글턴임이 API에 명백히 드러남(public static 필드가 final이니 절대로 다른 객체를 참조 X)
2. 간결함
두번째, 정적 팩터리 메서드를 public static 멤버로 제공
//코드 3-2 정적 팩터리 방식의 싱글턴 public class Elvis{ private static final Elvis INSTANCE = new Elvis(); private Elvis(){ ... } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding(){ ...} }
Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 제2의 Elvis 인스턴스는 생성되지 않음
장점 - 1. API를 바꾸지 않고도 싱글턴이 아니게 변경 가능함(유일한 인스턴스를 반환하던 팩터리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 함)
2. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있음
3. 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있음
예외 - 두가지 방법 다 권한이 있는 클라이언트는 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있음, 방어하려면 생성자를 수정해서 두 번째 객체가 생성되려 할 때 예외를 던지게 하면 됨
직렬화 - 둘 중 하나의 방식으로 만든 싱글턴 클래스를 직렬화하려면 단순히 Serializable을 구현하고 선언하는 것만으로는 부족하고, 모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야 함(안그러면 역직렬화할 때마다 새로운 인스턴스가 만들어짐)
//싱글턴임을 보장해주는 readResolve 메서드 private Object readResolve() { //'진짜' Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡김 return INSTANCE; }
세번째, 원소가 하나인 열거 타입을 선언
//코드 3-3 열거 타입 방식의 싱글턴 - 바람직한 방법 public enum Elvis{ INSTANCE; public void leaveTheBuilding() { ... } }
장점 - 1. 간결하고, 추가 노력 없이 직렬화할 수 있고, 아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 완벽히 막아줌
2. 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법
단점 - 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 사용불가(열거 타입이 다른 인터페이스를 구현하도록 선언은 가능)
인스턴스화를 막으려거든 private 생성자를 사용하라
정적 메서드와 정적 필드만 담은 클래스의 쓰임새 - java.lang.Math와 javaj.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아 놓을 때, java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아 놓을 수도 있음(java8부터 이런 메서드를 인터페이스에 넣을 수 있음), final클래스와 관련한 메서드들을 모아놓을 때도 사용(final 클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가능 하므로)
//코드 4-1 인스턴스를 만들 수 없는 유틸리티 클래스 public class UtilityClass{ //기본 생성자가 만들어지는 것을 막음(인스턴스화 방지용) private UtilityClass(){ throw new AssertionError(); } }
명시적 생성자가 private이니 클래스 바깥에서는 접근할 수 없고, 상속도 불가능하게 하는 효과도 있음(모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데, 이를 private으로 선언했으니 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막힘
자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
//코드 5-1 정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어려움 public class SpellChechcker{ private static final Lexicon dictionary = ...; private SpellChecker(){} // 객체 생성 방지 public static boolean isValid(String word) { ... } public static List<String> suggetions(String typo) { ... } } //코드 5-2 싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어려움 public class SpellChecher { private final Lexicon dictionary = ...; private SpellChechker(...) {} public static SpellChecker INSTANCE = new SpellChecker(...); public boolean isValid(String word) { ... } public List<String> suggestions(String typo) { ... } }
dictionary 필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가할 수 있지만, 이 방식은 어색하고 오류를 내기 쉬우며 멀티스레드 환경에서는 쓸 수 없음, 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않음
클래스가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 할때 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이 있음
//코드 5-3 의존 객체 주입은 유연성과 테스트 용이성을 높여줌 public class SpellChecker{ private final Lexicon dictionary; public SpleeChecker(Lexicon dictionary){ this.dictionary = Objects.requireNonNull(dictionary); } public boolean isValid(String word) { ... } public List<String> suggestions(String typo) { ... } }
자원이 몇 개든 의존 관계가 어떻든 상관없이 동작하고 불변을 보장하여 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있음, 의존 객체 주입은 생성자, 정적 팩터리, 빌더 모두에 응용 가능
쓸만한 변형은 생성자에 자원 팩터리를 넘겨주는 방식이 있는데 팩터리는 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체(JAVA8의 Supplier<T> 인터페이스 와일드카드 타입을 사용해 팩터리의 타입 매개 변수를 제한)
//클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크(Mosaic)를 만드는 메서드 Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
불필요한 객체 생성을 피하라
똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많음
//코드 6-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는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않음, 이 메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스는 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 생성 비용이 높은데 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 됨
//코드 6-2 값비싼 객체를 재사용해 성능을 개선 //Pattern(불변) 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 //캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 인스턴스를 재사용 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 isRomanNumeral(String s) { return ROMAN.matcher(s).matches(); } }
오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니므로, 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의 해야함
※ 요즘 jvm의 가비지 컬렉터는 상당히 빠르므로 프로그램의 명확성, 간결성, 기능을 위해 추가로 생성하는게 일반적으로 좋은 일임
다 쓴 객체 참조를 해제하라
//코드 7-1 메모리 누수가 일어나는 위치는 어디인가? public class Stack{ private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } /** * 원소를 위한 공간을 적어도 하나 이상 확보 * 배열 크기를 늘려야 할 때마다 대략 두배씩 늘림 */ private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size +1); } }
코드에서는 스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 더 이상 사용하지 않더라도 가비지 컬렉터가 회수하지 않음(다 쓴 참조(앞으로 다시 쓰지 않을 참조)를 여전히 가지고 있으므로), elements 배열의 '활성 영역'(인덱스가 size보다 작은 원소들로 구성) 밖의 참조들이 모두 여기에 해당
//코드 7-2 제대로 구현한 pop 메서드 public Object pop(){ if (size ==0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; }
객체 참조를 null 처리하는 일은 예외적인 경우여야 함(모든 객체에 할 필요 없음), 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효범위(scope) 밖으로 밀어내는 것, 다 쓴 참조를 null 처리하면 참조를 실수로 사용하려 하면 NullPointerException이 발생하는 장점도 있음
자기 메모리를 직접 관리하는 클래스(ex. Stack)라면 메모리 누수에 주의해야 하므로 원소를 다 사용한 즉시 그 원소가 참조한 객체들을 다 null 처리해줘야 하고 캐시 역시 메모리 누수를 일으키는 주범임(캐시 외부에서 키를 참조하는 동안만(값이 아님) 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들면 다 쓴 엔트리는 그 즉시 자동으로 제거됨), 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않으면 콜백은 계속 쌓임(콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거해감 예를 들어 WeakHashMap에 키로 저장하면 됨)
finalizer와 cleaner 사용을 피하라
finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하고 쓰지 말아야 하므로 JAVA9에서는 finalizer를 사용 자제 API로 지정하고 cleaner를 그 대안으로 소개함
cleaner는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요함
finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없음, 언제 요청 작업을 수행할지는 가비지 컬렉터 알고리즘에 달렸는데 이는 구현마다 천차만별이라서 테스트한 jvm에서는 동작하던 프로그램이 고객의 시스템에서는 문제가 될 수 있음
프로그램 생애주기와 상관없는, 상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존하면 안됨
finalizer의 예외는 무시되며, 처리할 작업이 남았더라도 바로 종료되므로 해당 객체는 마무리가 덜 된 상태로 남을 수 있고, 다른 스레드가 훼손된 객체를 사용하려 하면 어떻게 동작할지 예상을 못함(finalizer만), finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수 있음(생성자나 직렬화 과정에서 예외가 발생하면, 이 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 되면서 일그러진 객체를 활용해서 허용되지 않았을 작업을 수행할 수 있음
객체 생성을 막으려면 생성자에서 예외를 던지는 것만으로도 충분하지만, finalizer가 있다면 finalize 메서드를 만들고 final로 선언하면 됨
finalizer와 cleaner는 심각하게 성능도 느려짐
C++에서의 파괴자는 특정 객체와 관련된 비메모리 자원을 회수하는 보편적인 방법인데 자바에서는 가비지 컬렉터가 담당하지만 try-with-resources와 try-finally를 사용함
finalizer나 cleaner를 대신해줄 방법은 AutoCloseable을 구현해주고, 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출하면 됨(일반적으로 예외가 발생해도 제대로 종료되도록 try-with-resources 사용해야 함), close 메서드에서 이 객체는 더 이상 유효하지 않음을 필드에 기록하고, 다른 메서드는 이 필드를 검사해서 객체가 닫힌 후에 불렸다면 IllegalStateException을 던지는 방법
cleaner와 finalizer의 적절한 쓰임새 두가지 -
1. 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할
2. 네이티브 피어(native peer : 네이티브 피어란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체)와 연결된 객체에서 사용, 네이티브 피어는 자바 객체가 아니라서 가비지 컬렉터가 그존재를 알지 못하므로 네이티브 객체까지 회수하지 못함, cleaner나 finalizer가 처리하기 좋은 작업이나 성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때 사용하고 성능 저하를
try-finally보다는 try-with-resources를 사용하라
댓글
댓글 쓰기