01. 생성자 대신 정적 팩터리 메서드를 고려하라
public static Boolean valuOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
- 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 litany = Collections.list(legacyLitany);
02. 생성자에 매개변수가 많다면 빌더를 고려하라
// 2-1 점층적 생성자 패턴 - 확장하기 어렵다!
public class NutritionFacts {
private final int servingSize; // (ml, 1회 제공량) 필수
private final int servings; // (회, 총 n회 제공량) 필수
private final int calories; // (1회 제공량당) 선택
private final int fat; // (g/1회 제공량) 선택
private final int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate;// (g/1회 제공량) 선택
public NutritionFacts(int servingSize, int servings){
this(servingSize,servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories){
this(servingSize,servings,calories,0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat){
this(servingSize,servings,calories,fat,0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium){
this(servingSize,servings,calories,fat,sodium,0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate){
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts (240,8,100,0,35,27);
// 2-2 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다.
public class NutritionFacts {
// 매개변수들은 (기본값이 있다면) 기본값으로 초기화된다.
private int servingSize = -1; // 필수; 기본값 없음
private int servings = -1; // 필수; 기본값 없음
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
// 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽)
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 servings;
// 선택 매개변수 - 기본값으로 초기화한다.
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;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}
// 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴
public abstract class Pizza{
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// 하위 클래스는 이 메서드를 재정의(overriding)하여
// "this"를 반환하도록 해야 한다.
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // 아이템 50 참조
}
}
// 2-5 뉴욕 피자
public class NyPizza extends Pizza {
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
// 2-6 칼조네 피자
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; // 기본값
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override public Calzone build() {
return new Calzone(this);
}
@Override protected Builder self() { return this; }
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceInside().build();
03. private 생성자나 열거 타입으로 싱글턴임을 보증하라
// 3-1 public static final 필드 방식의 싱글턴
public class Elvis {
**public static final Elvis INSTANCE = new Elvis();**
private Elvis() {...}
public void leaveTheBuilding() {...}
}
// 3-2 정적 팩터리 방식의 싱글턴
public class Elvis {
**private** static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
**public static Elvis getInstance()** { return INSTANCE; }
public void leaveTheBuilding(){...}
}
// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve(){
// '진짜' Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다.
return INSTANCE;
}
// 3-3 열거 타입 방식의 싱글턴 - 바람직한 방법
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){...}
}
04. 인스턴스화를 막으려거든 private 생성자를 사용하라
// 4-1 인스턴스를 만들 수 없는 유틸리티 클래스
public class UtillityClass{
// 기본 생성자가 만들어지는 것을 막는다 (인스턴스 방지용)
private UtilityClass(){
throw new AssertionError();
}
... // 나머지 코드는 생략
}
05. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
// 5-1 정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 객체 생성 방지
public static boolean isValid(String word) {...}
public List<String> suggestions(String typo){...}
}
// 5-2 싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker(...) {}
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isValid(String word) {...}
public List<String> suggestions(String typo){...}
}
// 5-3 의존 객체 주입은 유연성과 테스트 용이성을 높여준다.
public class SpellChecker {
private final Lexicon dictionary;
**public SpellChecker(Lexicon dictionary) {**
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {...}
public List<String> suggestions(String typo){...}
}
Mosaic create(Supplier<? extneds Tile> tileFactory {...}
06. 불필요한 객체 생성을 피하라
String s = new String("bikini"); // 따라 하지 말 것!
String s = "bikini"; // 하나의 인스턴스를 사용하는 방법
Boolean b = new Boolean("true"); // 비권장 API
Boolean b = Boolean.valueOf("true"); // 권장 API
// 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|)$");
}
// 6-2 값비싼 객체를 재사용해 성능을 개선한다.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compole(
"^(?-.)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-3 끔찍이 느리다! 객체가 만들어지는 위치를 찾았는가?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
07. 다 쓴 객체 참조를 해제하라
// 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);
}
}
// 7-2 제대로 구현한 pop 메서드
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] == null; // 다 쓴 참조 해제
return result;
}
08. finalizer와 cleaner 사용을 피하라
// 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다!
private static class State implements Runnable {
int numJunkPiles; // 방(Room) 안의 쓰레기 수
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// close 메서드나 cleaner가 호출한다.
@Override public void run() {
System.out.println("방 청소");
numJunkPiles = 0;
}
}
// 방의 상태. cleanable과 공유한다.
private final State state;
// cleanable 객체. 수거 대상이 되면 방을 청소한다.
private final Cleaner.Cleanable cleanable;
public Room (int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override
public void close() {
cleanable.clean();
}
}
public class Adult{
public static void main(String[] args) {
try (Room myRoom = new Room(7)) {
System.out.println("안녕~");
}
}
}
public class Teenager{
public static void main(String[] args){
new Room(99);
System.out.println("아무렴");
}
}
09. try-finally보다는 try-with-resources를 사용하라
// 9-1 try-finally 더 이상 자원을 회수하는 최선의 방법이 아니다!
****static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
// 9-2 자원이 둘 이상이면 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();
}
}
// 9-3 try-with-resources 자원을 회수하는 최선책
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
// 9-4 복수의 자원을 처리하는 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);
}
}
// 9-5 try-with-resources를 catch절과 함께 쓰는 모습
static String firstLineOfFile(String path, String defaultVal) {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}