싱글턴(Singleton) 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 함수와 같은 무상태(stateless) 객체 설계상 유일해야하는 시스템 컴포넌트 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다. ▷ 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이다. 싱글턴을 만드는 방식 생성자는 private로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 마련한다. 1) public static 멤버가 final 필드 public class Elvis { /** * 싱글톤 오브젝트 */ public static final ..
점층적 생성자 패턴(Telescoping Constructor Pattern) 선택적 매개변수가 많을때 적절히 대응하기가 어렵다. 점층적 생성자 패턴이란, 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자 등의 형태로 선택 매개변수를 전부 다 받는 생성자까지 늘려가는 방식이다. 점층적 생성자 패턴 - 확장하기 어렵다. public class NutritionFacts { private final int servingSize; // (mL, 1회 제공량) 필수 private final int servings; // (회, 총 n회 제공량) 필수 privat..
정적 팩터리 메서드 클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다. 추가로, 생성자와 별도로 정적 팩터리 메서드 (static factory method)를 제공할 수 있다. 이는 그 클래스의 인스턴스를 반환하는 단순한 정적 메서드다. 정적 팩토리 메서드 ... /** * 미리 만들어놓는다. * 정적 팩토리 메서드로 외부에서 사용할 수 있다. */ private static final Settings SETTINGS = new Settings(); /** * 정적 팩토리 사용 * newInstance 명칭으로 사용 * * [플라이웨이트 패턴]과 비슷하다. * 미리 자주 사용하는 객체들을 만들어놓고, 필요한 객체르 꺼내다 쓴다. * @return */ public static ..
직렬화 프록시 패턴(serialization proxy pattern) 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계하여 private static으로 선언한다. 이 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시다. 중첩 클래스의 생성자는 단 하나여야 하며, 바깥 클래스를 매개변수로 받아야한다. 이 생성자는 단순히 인수로 넘어온 인스턴스의 데이터를 복사한다. 일관성 검사나 방어적 복사도 필요없다. 설계상 직렬화 프록시의 기본 직렬화 형태는 바깥 클래스의 직렬화 형태로 쓰기에 이상적이다. 그리고 바깥 클래스와 직렬화 프록시 모두 Serializable을 구현한다고 선언해야한다. Period 클래스용 직렬화 프록시 package com.java.effective.item90; impor..
싱글턴 패턴의 직렬화 이 클래스는 바깥에서 생성자를 호출하지 못하게 막는 방식으로, 인스턴스가 오직 하나만 만들어짐을 보장했다. public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { } public void leaveTheBuilding() { ... } } 이 클래스는 선언에 implements Serializable을 추가하는 순간 더이상 싱글턴이 아니게된다. 기본 직렬화를 쓰지 않더라도, 그리고 명시적인 readObject를 제공하더라도 이 클래스가 초기화될 때 만들어진 인스턴스와는 별개인 인스턴스를 반환하게된다. readResolve 기능 readResolve 기능을 이용하면 readObjec..
방어적 복사를 사용하는 불변 클래스 아이템 50에서 불변인 날짜 범위 클래스를 만드는데 가변인 Date 필드를 사용했었다. 그래서 불벼식을 지키고 불변을 유지하기 위해 생성자와 접근자에서 Date 객체를 방어적으로 복사하느라 코드가 길어졌다. https://devfunny.tistory.com/613?category=895441 [교재 EffectiveJava] 아이템 50. 적시에 방어적 복사본을 만들라 불변식 깨뜨리기 자바는 메모리 충돌 오류에서 안전한 언어다. 자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 그 불변식이 지켜진다. 하지만 아무리 자바라고 해도 다른 클래 devfunny.tistory.com 방어적 복사를 사용하는 불변 클래스 package com.java.effecti..
커스텀 직렬화 형태를 고려 Serializable을 구현하고 기본 직렬화 형태를 사용한다면 다음 릴리스 때 버리려한 현재의 구현에 영원히 발이 묶이게된다. 기본 직렬화 형태를 버릴 수 없게된다. 실제로도 BigInteger 같은 일부 자바 클래스가 이 문제에 시달리고있다. 먼저 고민해보고 괜찮다고 판단될 때만 기본 직렬화 형태를 사용하라. 기본 직렬화 형태는 유연성, 성능, 정확성 측면에서 신중히 고민한 후 합당할 때만 사용해야한다. 일반적으로 직접 설계하더라도 기본 직렬화 형태와 거의 같은 결과가 나올 경우에만 기본 형태를 써야한다. 기본 직렬화 형태 객체의 물리적 표현과 논리적 내용이 같다면 기본 직렬화 형태라도 무방하다. 기본 직렬화 형태에 적합한 후보 public class Name impleme..
Serializable 어떤 클래스의 인스턴스를 직렬화할 수 있게 하려면 클래스 선언에 implements Serializable만 덧붙히면 된다. 너무 쉽게 적용할 수 있는것에 비해, 직렬화는 아주 값비싼 일이다. Serializable을 구현하면 릴리스한 뒤에는 수정하기 어렵다. 클래스가 Serializable을 구현하면 직렬화된 바이트 스트림 인코딩(직렬화 형태)도 하나의 공개 API가 된다. 그래서 이 클래스가 널리 퍼진다면 그 직렬화 형태도 영원히 지원해야한다. 커스텀 직렬화 형태를 설계하지 않고 자바의 기본 방식을 사용하면 직렬화 형태는 최소 적용 당시 클래스의 내부 구현 방식에 영원히 묶여버린다. 기본 직렬화 형태에서는 클래스의 private, package-private 인스턴스 필드들마저..
직렬화의 위험성 직렬화는 공격 범위가 너무 넓고, 지속적으로 넓어져 방어하기 어렵다. ObjectInputStream의 readObject 메서드를 호출하면서 객체 그래프가 역직렬화되기 때문이다. readObject 메서드는 (Serializable 인터페이스를 구현했다면) 클래스패스 안의 거의 모든 타입의 객체를 만들어낼 수 있다. 바이트 스트림을 역직렬화하는 과정에서 이 메서드는 그 타입들 안의 모든 코드를 수행할 수 있다. 이는 즉, 그 타입들의 코드 전체가 공격 범위에 들어간다는 뜻이다. 자바의 역직렬화는 명백하고 현존하는 위험이다. 신뢰할 수 없는 스트림을 역직렬화하면 원격 코드 실행, 서비스 거부 등의 공격으로 이어질 수 있다. 역직렬화 폭탄 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬..
스레드 스케줄러(Thread Scheduler) 여러 스레드가 실행 중이면 운영체제의 스레드 스케줄러가 어떤 스레드를 얼마나 오래 실행할지 정한다. 정상적인 운영체제라면 이 작업을 공정하게 수행하지만 구체적인 스케줄링 정책은 운영체제마다 다를 수 있다. 따라서 잘 작성된 프로그램이라면 이 정책에 좌지우지돼서는 안된다. 정확성이나 성능이 스레드 스케줄러에 따라 달라지는 프로그램이라면 다른 플랫폼에 이식하기 어렵다. 실행 가능한 스레드의 평균적인 수를 프로세서 수보다 지나치게 많아지지 않도록 하는것이 중요하다. 그래야 스레드 스케줄러가 고민할 거리가 줄어든다. 실행 준비가 된 스레드들은 맡은 작업을 완료할때까지 계속 실행되도록 만들자. 여기서 실행 가능한 스레드의 수와 전체 스레드 수는 구분해야한다. 전체 ..
지연 초기화(lazy initialization) 필드 초기화 시점을 그 값이 처음 필요할때까지 늦추는 방법이다. 그래서 값이 전혀 쓰이지 않으면 초기화도 결코 일어나지 않는다. 이 기법은 정적 필드와 인스턴스 필드 모두에 사용할 수 있다. 지연 초기화는 주로 최적화 용도로 쓰이지만, 클래스와 인스턴스 초기화 때 발생하는 위험한 순환 문제를 해결하는 효과도 있다. 다른 모든 최적화와 마찬가지로 지연 초기화는 "필요할 때까지는 하지말라". 지연초기화 단점 클래스 혹은 인스턴스 생성시의 초기화 비용은 줄지만 그 대신 지연 초기화하는 필드에 접근하는 비용이 커진다. 지연 초기화하려는 필드들 중 결국 초기화가 이뤄지는 비율에 따라, 실제 초기화에 드는 비용에 따라, 초기화된 각 필드를 얼마나 빈번히 호출하느냐에..
API 문서에 synchronized 한정자 한 메서드를 여러 스레드가 동시에 호출할때 그 메서드가 어떻게 동작하느냐는 해당 클래스와 이를 사용하는 클라이언트 사이의 중요한 계약과 같다. API 문서에서 아무런 언급이 없으면 그 클래스 사용자는 나름의 가정을 해야만한다. 그 가정이 틀리면 클라이언트 프로그램은 동기화를 충분히 하지 못하거나, 너무 지나치게 하게되어 심각한 오류가 발생할 수 있다. "API 문서에 synchronized 한정자가 보이는 메서드는 스레드 안전하다."라는 말은 몇가지 면에서 틀렸다. 자바독이 기본 옵션에서 생성한 API 문서에는 synchronized 한정자가 포함되지 않는다. 메서드 선언에 synchronized 한정자를 선언할지는 구현 이슈일뿐 API에 속하지 않는다. 따라..