- 아이템 1 - 생성자 대신 정적 팩토리 메서드를 고려해라
- 아이템 2 - 생성자에 매개변수가 많다면 빌더를 고려해라
- 아이템 3 - private 생성자나 열거 타입으로 싱글턴임을 보증하라
- 아이템 4 - 인스턴스화를 막으려거든 private 생성자를 사용하라
- 아이템 5 - 자원을 직접 명시하지 말고 의존 객체 주입(DI)을 사용하라
- 아이템 6 - 불필요한 객체 생성을 피하라
- 아이템 7 - 다 쓴 객체 참조를 해제하라
- 아이템 8 - finalizer와 cleaner 사용을 피하라
- 아이템 9 - try-finally보다 try-with-resources를 써라
정적 팩토리 메서드로 생성자(new) 대신 클래스의 인스턴스를 얻을 수 있음
Date d = Date.from(instance);
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object newArray = Array.create(classObject, arrayLen);
FileStore fs = Files.getFileStore(path); // Files 클래스 안에서 FileStore를 얻고 있음
BufferedReader br = Files.newBufferedReader(path); // Files 클래스 안에서 새로운 BufferedReader를 생성하고 있음
List<Complaint> litany = Collections.list(legacyListany);
NutritionFacts facts = new NutritionFacts(240, 8, 100, 0, 35, 5); // 숫자가 각각 뭐를 뜻하는거지?
public class User {
private final String name;
private final int age;
public static class Builder {
private String name = "";
private int age = 0;
public Builder name(String val) {
this.name = val;
return this;
}
public Builder age(int val) {
this.age = val;
return this;
}
public User build() {
return new User(this);
}
}
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
}
// 무슨 인자에 어떤 값이 들어가는지 명확함
User user = new User.Builder()
.name("홍길동")
.age(28)
.build();
private 생성자 + public static 필드/메서드enum 타입public class Elvis {
private static final Elvis INSTANCE = new Elvis(); // 클래스가 처음 로딩될 때 딱 한 번만 실행됨
private Elvis() {} // 외부에서 생성 불가, 초기화 할 때 한 번만 호출됨
public static Elvis getInstance() { return INSTANCE; }
public void eat() {
System.out.println("밥은 먹고 하자");
}
}
public enum Elvis {
INSTANCE;
public void eat() {
System.out.println("밥은 먹고 하자");
}
}
직렬화 (Serialization)
역직렬화 (Deserialization)
여기서 문제
Math, Collections, Arrays 같은 유틸리티 클래스 같은 녀석들 (굳이 생성할 필요 X)private으로 만들어 인스턴스화를 막아야 함new 키워드로 사용자가 인스턴스를 생성할 수 있게 됨 public class UtilityClass {
// 생성자를 private으로 막음
private UtilityClass() {
// 굳이 던질 필요는 없는데 클래스 내부에서 실수로 생성자 호출 못하게 개발자 실수 방지하는 역할
throw new RuntimeException("인스턴스화 금지!");
}
public static void doSomething() {
// ...
}
}
이것도 설계 핵심
new 하지 말고, 외부에서 주입받자public class SpellChecker {
private final Dictionary dictionary = new KoreanDictionary(); // 직접 생성
// private final Dictionary dictionary = new JapaneseDictionary(); // 바꾸고 싶으면 이런짓 해야함
public boolean isValid(String word) {
return dictionary.contains(word);
}
}
// 인터페이스
public interface Dictionary {
boolean contains(String word);
}
// 콘크리트 클래스 2개
public class KoreanDictionary implements Dictionary {
@Override
public boolean contains(String word) {
// 안녕하세요
}
}
public class JapaneseDictionary implements Dictionary {
@Override
public boolean contains(String word) {
// 콘니찌와
}
}
// Dictionary를 주입받을 사용 객체
public class SpellChecker {
private final Dictionary dictionary;
// 생성자 주입 방식 -> SpeelChecker가 생성될 때 dictionary가 정해짐
public SpellChecker(Dictionary dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) {
return dictionary.contains(word);
}
}
// main
public class Main {
public static void main(String[] args) {
// 한국어 사전
SpellChecker koreanChecker = new SpellChecker(new KoreanDictionary()); // KoreanDictionary를 주입
System.out.println(koreanChecker.isValid("안녕하세요"));
// 일본어 사전
SpellChecker japaneseChecker = new SpellChecker(new JapaneseDictionary()); // JapaneseDictionary를 주입
System.out.println(japaneseChecker.isValid("콘니찌와"));
}
}
String s = new String("bikini"); // 'new String()' is redundant
Boolean b = new Boolean(true); // 'Boolean(boolean)' is deprecated since version 9 and marked for removal
String s = "bikini"; // 리터럴을 직접 사용 (string pool에서 재사용)
String a = "bikini";
String b = "bikini";
System.out.println(a == b); // true
Boolean은 불변이고, 캐시된 상수 인스턴스를 쓰면 되기 때문
// Boolean.TRUE 반환
Boolean b = Boolean.valueOf(true); // Unnecessary boxing (내부에서 Boolean.TRUE 리턴)
Boolean b2 = Boolean.TRUE;
(item1)
Pattern p = Pattern.compile("[a-z]+"); // 컴파일 비용이 큼
// 매번 컴파일
boolean result = "[a-z]+".matches("hello");
// static final로 캐싱
private static final Pattern PATTERN = Pattern.compile("[a-z]+");
boolean result = PATTERN.matcher("hello").matches();
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
// sum += i는 내부적으로 sum = Long.valueOf(sum.longValue() + i); 로 변환됨;
sum += i; // 오토박싱이 돼서 Long 객체 수억 개가 생성됨 (성능 저하, GC 부하)
}
long sum = 0L; // primitive 타입으로 초기화
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
=> 재사용할 수 있는건 재사용하고 반복적이고 쓸데없는 객체 생성을 피하라는 뜻
객체 풀은 일반적으로 초기화 비용이 매우 높은 객체(Connection, Thread 등) 에만 유효
String, Point, UserDto 같은 가벼운 객체에 대해 객체 풀을 만들면
=> 오히려 성능 저하
public class Stack {
private Object[] elements = new Object[100]; // 객체 참조
private int size = 0;
public void push(Object e) {
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size]; // null 처리를 안해서 요소는 줄었지만 참조는 남아 있음
}
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 참조 해제 (GC 회수가 가능해짐)
return result;
}
void process() {
BigObject obj = new BigObject(); // 지역 변수로 객체 참조
obj.doSomething();
// obj = null; 이렇게 안 해도 scope 끝나면 GC 대상 됨
}
Map<String, Object> cache = new HashMap<>();
cache.put("user:1", new User("Soohyeok", 28, "+82")); // 사용 안 해도 계속 유지됨
Map<Object, String> cache = new WeakHashMap<>();
finalize()와 Cleaner는 예측 불가능하고, 느리고 위험하기 때문에 사용을 피해야 함finalizer보다 안전하지만 여전히 예측 불가능함try-finally로 처리했지만 try-with-resources가 더 안전함static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close(); // 예외가 나도 무조건 닫아야 하니까 finally에 둠
}
}
static String firstLineOfFile(String path) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}