조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.
1. 싱글턴
- 특정 클래스의 인스턴스를 오직 하나만 생성하는 디자인 패턴
항목 | 내용 |
주요 목적 | - 전역적으로 공유되는 인스턴스 제공 - 인스턴스 생성 비용 절감 |
주로 사용되는 곳 | - 시스템 설정 관리자 - 로깅 객체 - 설계상 유일해야 하는 객체 (DB 커넥션 풀 등) |
특징 |
- 일반적으로 무상태(stateless) 객체로 설계
- 함수적 역할 수행이 많음 (입력 → 출력만 있음) - 전역 접근 지점 제공 (Class.getInstance()) |
2. 생성 방식
예) 인스턴스 필드
더보기
public class Yundle {
public static final Yundle INSTANCE = new Yundle();
private Yundle() {}
public static void main(String[] args) {
Yundle yundle = Yundle.INSTANCE;
}
}
- static final 필드값이 초기화됨
예) 정적 팩토리
더보기
public class MetaElvis<T> {
private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();
private MetaElvis() {}
@SuppressWarnings("unchecked")
public static <T> MetaElvis<T> getInstance() { return (MetaElvis<T>) INSTANCE; }
public void say(T t ) { System.out.println(t); }
public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); }
public static void main(String[] args) {
MetaElvis<String> elvis1 = MetaElvis.getInstance();
MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
elvis1.say("hello");
elvis2.say(100);
// 인스턴스는 같으나 타입은 다름
System.out.println(elvis1.equals(elvis2));
}
}
- 제네릭 활용 가능
public class Concert {
private Singer singer;
public Concert() { }
public void start(Supplier<Singer> supplier) {
Singer singer = supplier.get();
this.singer = singer;
singer.sing();
}
public static void main(String[] args) {
Concert concert = new Concert();
concert.start(Elvis::getInstance);
}
}
- 메서드 참조 사용 가능
예) 열거 타입
더보기
새로운 인스턴스가 생기는 일을 완벽히 막아줌
public enum AppConfig {
INSTANCE;
public void doSomething() {
System.out.println("doing something");
}
}
AppConfig a1 = AppConfig.INSTANCE;
// 직렬화
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("conf.obj"));
oos.writeObject(a1);
oos.close();
// 역직렬화
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("conf.obj"));
AppConfig a2 = (AppConfig) ois.readObject();
ois.close();
System.out.println(a1 == a2); // ✅ true!
- 직렬화 시에도 싱글턴 보장 (JVM이 자동으로 readResolve()와 같은 효과를 보장해줌)
Constructor<AppConfig> cons = AppConfig.class.getDeclaredConstructor(); // ⚠️ 예외 발생
- 리플렉션 접근이 제한됨
인터페이스 구현 가능
public interface Logger {
void log(String message);
}
public enum RealLogger implements Logger {
INSTANCE;
public void log(String message) {
System.out.println("[REAL] " + message);
}
}
Logger mockLogger = Mockito.mock(Logger.class);
when(mockLogger.log("hello")).thenReturn(...);
- 테스트 시, mock으로 분리 가능
'Java' 카테고리의 다른 글
[Effective Java] 2-5. 객체 생성과 파괴: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (2) | 2024.12.27 |
---|---|
[Effective Java] 2-4. 객체 생성과 파괴: 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2024.12.27 |
[Effective Java] 2-2. 객체 생성과 파괴: 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2024.12.26 |
[Effective Java] 2-1. 객체 생성과 파괴: 생성자 대신 정적 팩토리 메서드를 고려하라 (0) | 2024.12.26 |
[JVM 밑바닥까지 파헤치기] 12-3. 자바 메모리 모델과 스레드: 스레드 (1) | 2024.12.26 |