1. 생성자 대신 정적 팩터리 메서드를 고려하라.
- 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사 할 수 있다.
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다. 생성 비용이 큰 객체가 자주 요청되는 상황에서 성능을 상당히 끌어올려 준다. 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 통제가 가능하다.(인스턴스 통제 클래스)
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. API를 만들 때 구현 클래스를 공개하지 않고도 그 객체를 반환 가능하다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 정적 팩터리 메서드에 흔히 사용하는 명명 방식 들
- from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드.
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드.
- valueOf : from 과 of의 더 자세한 버전.
- instance 또는 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음.
- create 또는 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환 보장.
- getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의 할 때 쓴다.
- newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의 할 때 쓴다.
2. 생성자에 매개변수가 많다면 빌더를 고려하라.
- 점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
( 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇 개 인지도 주의해서 세어 보아야 함 ) - 자바빈즈 패턴(JavaBeans pattern)인 경우, 매개변수가 없는 생성자로 객체를 만든 후 세터(setter) 메서드들을
호출하여 원하는 매개변수의 값을 설정하는 방식인데, 객체 하나를 만들려면 여러 개 호출해야 하고, 객체가
완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게된다.(생성자에서 유효성 검사 후 일관성 유지X) - 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 것이 빌더 패턴(Builder pattern)이다.
3. private 생성자나 열거 타입으로 싱글턴임을 보증하라.
- 싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스.
- 생성자는 private으로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 생성.
- public static 멤버가 final 필드인 방식
( private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번 호출 )
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {};
}
- 예외가 있다면, 리플렉션 API를 이용하여 private 생성자 호출 가능.
- 정적 팩터리 방식의 싱글턴 방식
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {};
public static final getInstance() { return INSTANCE; }
}
- 인스턴스 필드를 일시적(transient) 이라고 선언하고 readResolve 메서드를 제공해야한다.
- 위의 방식대로 하지 않을 경우, 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어 진다.
- 열거 타입 방식의 싱글턴
public enum Elvis {
INSTANCE;
// enum 클래스도 멤버 변수 또는 메서드를 가질 수 있다.
}
- 위 방식의 경우 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이방법은 부적절 하다.
( 인터페이스는 가능 )
4. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.
- 어떤 클래스가 사용하는 리소스에 따라 행동을 달리 해야 하는 경우에는 스태틱 유틸리티 클래스와 싱글톤을 사용하는 것은 부적절하다.
- 인스턴스를 생성하는 시점에 필요한 자원을 넘겨주는 방식.
- 필요한 자원을 생성자 또는 정적 팩터리나 빌더에 넘겨주어서 클래스의 유연성, 재사용성, 테스트 용이성을 개선.
5. 불필요한 객체 생성을 피하라.
- 똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
- Boolean(String) 생성자 대신 Boolean.valueOf(String) 팩터리 메서드를 사용하는 것이 좋다.
- 생성 비용이 아주 비싼 객체가 있을 경우, 비싼 객체가 반복해서 필요하다면 캐싱하여 재사용한다.
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 인스턴스는 한번 쓰고 버려져서 곧바로
// 가비지컬렉션 대상이 된다.
}
// 성능 개선 코드
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.complie(
"^(?=.)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();
}
}
- 오토박싱된 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
6. 다 쓴 객체 참조를 해제하라.
- 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다.
다 사용한 즉시 그 원소가 참조한 객체들을 다 null 처리 해줘야 한다. - 맴버변수가 아닌 지역변수는 굳이 null 처리 할 필요는 없다. scope에서 나올 경우 바로 GC 대상이 된다.
- cache 또한 메모리 누수를 일으키는 주범이 될 수 있다. 이럴 경우 weakHashMap을 사용하여 키가 참조하는 동안만 엔트리가 유효하고 키가 유효하지않은(gc 또는 null) 상황이 되면 엔트리에서 그 즉시 자동으로 삭제될 것이다.
- weakReference 참고 링크 ( https://d2.naver.com/helloworld/329631 )
7. finalizer 와 cleaner 사용을 피하라.
- Java에서는 두가지 객체 소멸자를 제공한다. 하나는 finalizer (실행을 예측할 수 없고, 상황에 따라 위험) 또 하나는 cleaner ( 마찬가지로 예측이 불가능하며 느리고 일반적으로 불필요 )
- finalizer 메서드안의 로직을 실행 하면서 GC의 수행시간이 지연될 수 있다.
- Java 9에서는 finalizer를 사용자제(deprecated) API로 지정 그러나 자바 라이브러리에서는 여전히 사용중.
- AutoCloseable 인터페이스의 @overrid close() 메서드를 활용하여 안정망으로 사용하자.
- cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수 용으로만 사용을 권고.
// Cleaner 객체 생성
private static final Cleaner cleaner = Cleaner.create();
// cleanable 객체 생성. 수거 대상이 되면 실행
private final Cleaner.Cleanalbe cleanable;
// 수거 쓰레드 등록
cleanable = cleaner.register( this, new Runnable {...} );
8. try-finally 보다는 try-with-resources를 사용하라.
- 자바 라이브러리에는 close 메서드를 사용하여 직접 닫아줘야 하는 API들이 있다.(InputStream, OutputStream, java.sql.Connection 등)
- JDK 7 에서 부터 나온 try-with-resources 사용한다면, 굳이 try-finally 구문을 사용하지 않고 자원이 회수가 가능하며, 에러 스텍 추적 내역에 supprssed 라는 문구와 함께 예외가 누적되어 표현가능하여 에러 추적에 효율적이다.
- AutoClosable 인터페이스를 상속 받아 구현된 객체일 경우, try-with-resources를 사용 할 수 있다.
- Closable 인터페이스에도 JDK6에서는 아무것도 상속받지 않았지만 7버전에서 부터는 AutoClosable 인터페이스를 상속 받도록 변경되었다.
- 그로 인해 기존의 Closable 인터페이스를 상속 받고 있는 객체 였다면 try-with-resources를 사용 가능하다.
'Java > Effective Java 3' 카테고리의 다른 글
[이팩티브 자바3] 제 5장. 제네릭 (0) | 2020.11.08 |
---|---|
[이팩티브 자바3] 제 4장. 클래스와 인터페이스 (0) | 2020.11.03 |
[이팩티브 자바3] 제 3장. 모든 객체의 공통 메서드 (0) | 2020.10.05 |