1️⃣ 정적 팩터리 메서드란? 클라이언트가 클래스의 인스턴스를 얻는 수단은 다음의 두가지가 존재한다. > **1) public 생성자 2) 정적 팩터리 메서드(static factory method)** 정적 팩터리 메서드는 🌟클래스의 인스턴스를 단순히 반환하며
🙏선택 매개변수가 많을 경우 활용 가능한 여러 패턴들 중에, 빌더 패턴에 대해 알아본다. 1️⃣ 점층적 생성자 패턴 정적 팩터리와 생성자에는 제약이 하나 존재한다. 바로 필수가 아닌 선택적 매개변수가 많을수록 대응이 힘들다는 점인데, 보통 점층적 생성자 패턴을 즐겨
싱글턴(singleton)이란, 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 전형적인 예로는, 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다. 하지만, 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려
단순히 정적 메서드와 정적 필드만을 담은 클래스들은, 객체 지향적이지 않은 것처럼 보이지만 나름의 쓰임새가 존재한다. 다음의 예시들을 봐보자. > 💡 정적 메서드와 정적 필드만을 담은 클래스 예시 1) java.lang.Math, java.util.Arrays 2)
1️⃣ 들어가기 많은 클래스가 하나 이상의 자원에 의존하는 경우가 많다. 이럴때 우리는 주로 정적 유틸리티 클래스(아이템 4) 혹은 싱글턴(아이템 3)으로 구현하고는 한다. 맞춤법 검사기가 사전에 의존하는 예시를 봐보자. ▶️ 정적 유틸리티 잘못 사용한 예 ▶️
1️⃣ 들어가기 > 불필요한 객체 생성을 피하는 방법들에 대해 알아보자. 똑같은 기능의 객체를 매번 생성하기 보다는, 객체 하나를 재사용하는 편이 좋을 때가 많다. 예를 들어, 불변 객체(아이템 17)은 언제든지 재사용 가능하다. 해당 코드는 실행 시 마다 Stri
메모리를 직접 관리해야 하는 C, C++와 달리, 자바는 다 쓴 객체를 알아서 회수해가는 가비지 컬렉터를 가지고 있다. 그렇다고 해서, 메모리 관리에 더 이상 신경 쓰지 않아도 된다는 말은 아니다. 1️⃣ 메모리 누수 주범(1):배열 ▶️ 메모리 누수 예제 겉으로
dd
자바 라이브러리에는 close() 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. ex) InputStream, OutputStream, java.sql.Connection이런 자원 중 상당수가 안전망으로 finalizer를 활용하고 있지만, 믿음직스러지 못하다.👉
🙏 들어가기 _equals 메서드는 다음에 경우에 해당한다면, 재정의 하지 않는 것이 가장 좋다. 1️⃣ equals를 재정의 하지 말아야 하는 경우 1) 각 인스턴스가 본질적으로 고유한 경우 값이 아닌 동작하는 개체를 표현하는 클래스 ex) Thread 2)
dd
Object의 기본 toString 메서드는, 우리가 작성한 클래스에 적합한 문자열을 거의 반환해주지 못하고 단지 클래스\_이름@16진수로\_표시한\_해시코드 만을 반환 한다. 예를 들어, PhoneNumber@abbd 를 반환해준다.하지만 이 형태보다는, 전화번호를
👉 Cloneble은 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(아이템 20)이다. 하지만, 1)clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이며 2)protected 메서드이기 때문에 단순 Cloneable을 구현하는 것만으로
compareTo는, 🌟1)단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 2)제네릭(Generic)하다는 두 가지를 제외하면 equals와 동일하다._ (단, Object의 메서드가 아니다)_ Comparable을 구현했다는 것은, 클래스의 인스턴스들에 자연
잘 설계된 컴포넌트는, 🌟모든 내부 구현화 데이터 정보를 외부로부터 완벽히 숨겨, 구현과 API를 깔끔하게 분리🌟한다. 오직 API를 통해서만 다른 컴포넌트와 소통하며 서로의 내부 동작 방식에 개의치 않는 이 개념을, 바로 정보 은닉(캡슐화) 라고 한다. 🔗 정
불변 클래스란, 🌟인스턴스의 내부 값을 수정할 수 없는 클래스이며 불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순가까지 절대 달라지지 않는다. ex) 자바 플랫폼 라이브러리의 String, BigInteger, BigDeciaml, 기본 타입의 박싱된 클
1️⃣ 상속의 위험성 👉상속(클래스가 다른 클래스를 확장) 은 코드를 재사용하는 강력한 수단이지만, 다른 패키지의 구체 클래스를 상속하는 일은 위험하다. 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다. 즉, 릴리스마다 내부 구현이 달라질 수 있는 상위 클래스로 인해
상속을 고려한 설계와 문서화란 정확히 무엇을 뜻하는지 알아보자. 상속을 위한 좋은 설계란? 1️⃣ 먼저, 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남거야 한다. 즉, 공개된 메서드에서 호출되는 자신의 또 다른 메서드가
🔗 자바는 1)인터페이스와 2)추상 클래스 두가지를 통해 다중 구현 메커니즘 을 제공하고 있다.둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현한 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 점이다.(단일 상속으로 인해 새로운 타입 정의하는데 제약
1) Default Method 자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었지만, 자바 8부터는 디폴트 메소드 를 통해 인터페이스에 메서드를 추가하는 것이 가능해졌다. 🔗 Default 메서드 문제점 1️⃣하지만 디폴트
인터페이스는 자신을 구현할 클래스의 인스턴스를 참조할 수 있는 을 한다. 즉, 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해주는 것이다. 하지만 위의 용도로만 사용해야 한다는 지침에 맞지 않는 예로, 상수 인터
1️⃣ 태그 달린 클래스 태그 달린 클래스란, 두 가지 이상의 의미를 표현 가능할 때, 그중 현재 표현하는 의미를 태그 값으로 알려주는 클래스이다. 아래 예시를 보자. ▶️* 태그 달린 클래스 - 클래스 계층구조보다 훨씬 나쁨* 🔗 태그 달린 클래스 단점 우선,
1️⃣ 중첩 클래스 중첩 클래스(nested class)란, 다른 클래스 안에 정의되어 자신을 감싼 바깥 클래스에서만 쓰여야 하는 클래스를 의미한다. > 📚* 중첩 클래스 종류* 정적 멤버 클래스, (비정적)멤버 클래스, 익명 클래스, 지역 클래스 👉 이번 아이
소스 파일 하나에 톱 레벨 클래스를 여러개 선언하는 것은, 심각한 위험을 감수해야 하는 행위이다. 한 클래스를 여러 가지로 정의할 수 있으며, 그 중 어느 것을 사용할지는 어느 소스를 먼저 컴파일하느냐에 따라 달라지기 때문이다. 구체적인 예시를 들어보자. ▶️ 두
제네릭 클래스 혹은 인터페이스란, 클래스와 인터페이스 선언에 타입 매개변수가 쓰인 것을 말한다. 각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다. 📚 제네릭 형식클래스/인터페이스 이름<실제 타입 매개변수>제네릭 타입을 하나 정의하면, 그에 딸린 로 타입(
제네릭을 사용하면서 마주칠 수 있는 컴파일러 경고들은 다음과 같다. 이 중 비검사 경고는 제일 쉽게 제거 가능하기 때문에, 🌟할 수 있는 한 모든 비검사 경고를 제거해야 한다.비검사 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환
📚 공변(covariant)함께 변한다는 것배열은 공변이여서 Sub가 Super의 하위 타입이라면 Sub\[] 또한 Super\[]의 하위 타입이 된다.반면, 제네릭은 불공변(invariant)이기 때문에 서로 다른 타입 Type1과 Type2가 있을 때 List&l
일반 클래스를 제네릭 클래스로 만드는 방법은 다음과 같다. > 1) 클래스 선언에 타입 매개변수를 추가한다 ( 타입 이름 : E ) 2) 코드에 쓰인 Object를 적절한 타입 매개변수로 바꾼다 예시를 통해 제네릭 타입으로 변환하는 방법을 알아보자. ▶️ Obje
클래스와 마찬가지로, 메서드도 제네릭으로 생성할 수 있다. 대체로 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. ex)Collections의 알고리즘 메서드 🔗 제네릭 메서드 작성법 제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다. 다음은 두
매개변수화 타입은, 서로 다른 타입 Type1과 Type2가 있을 때 List이 List의 하위 타입도 상위 타입도 아닌 "불공변(invariant)" 이다. List은 문자열만, List는 어떤 객체도 넣을 수 있으니 후자가 하는 일을 제대로 수행하지 못해 당연히
가변인수(varargs) 메서드(아이템 53)란, 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 하는 것이다. 제네릭과 같이 자바 5때 함께 추가되었다. 하지만, 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 만들어지는데 이 때 vara
dd
열거 타입이란, 일정 개수의 상수 값을 정의한 다음 그 외의 값은 허용하지 않는 타입이다.열거 타입이 생기기 이전에는, 다음 코드처럼 상수를 한 묶음 선언해서 사용하고는 했다.▶️ 정수 열거 패턴1) 타입 안전을 보장할 방법이 없으며 표현력도 좋지 않다.오렌지를 건네야
열거 타입 상수는 하나의 정수값에 대응되며, 모든 열거 타입은 해당 상수가 몇 번째 위치인지 반환하는 ordinal() 메서드를 제공한다. ▶️ ordinal을 잘못 사용한 예 하지만 해당 코드는 상수 선언 순서를 바꾸는 순간 오작동하며, 이미 사용 중인 정수와 값
열거한 값들이 주로 단독이 아닌 집합으로 사용될 경우, EnumSet이 나오기 전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴(아이템 34)를 사용했다. 🔗 비트 필드 열거 상수 ▶️ 비트 필드 열거 상수 비트별 OR를 사용해 여러 상수를
1️⃣ Ordinal() 배열이나 리스트에서 원소를 꺼낼 때와 같이 인덱싱이 필요할때는, ordinal() 메서드(아이템 35)로 인덱스를 얻을 수 있다. 🔗 Ordinal 메서드 사용 다음의 식물을 나타낸 Plant 클래스를 예로 들어보자. 생애주기별로 총 3
열거 타입을 확장하는 것은, 대부분 상황에서 좋지 않기 때문에 타입 안전 열거 패턴과 달리 불가능하다. 그런데 확장 가능한 열거 타입이, "연산 코드(operation code)"에는 쓰임새가 존재한다. 이따금 API가 제공하는 기본 연산 외에 사용자 확장 연산을 추가
1️⃣ 명명 패턴 전통적으로, 도구나 프레임워크가 특별이 다뤄야 할 프로그램 요소에는 구분되는 명명 패턴을 적용해왔다. 예를 들어, JUnit은 버전 3까지 테스트 메서드 이름을 test로 시작하게끔 하였다. 하지만 이러한 명명 패턴 방식은 여러 단점을 지닌다. 🔗
자바가 기본으로 제공하는 애너테이션 중, 프로그래머에게 가장 중요한 것은 @Override 애너테이션이다. @Override는 메서드 선언에만 달 수 있으며, 이 애너테이션이 달렸다는 것은 상위 타입의 메서드를 재정의 했음을 뜻한다. 해당 애너테이션을 일관되게 사용한
마커 인터페이스란, 아무 메서드도 담고 있지 않고 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 말한다.대표적인 예로, Serializable 인터페이스가 존재하는데 단순히 자신을 구현한 클래스의 인스턴스는 ObjectOutputstream
🔗 함수 객체 생성 수단의 역사 주로 자바에서 함수 타입을 표현할 때, 추상 메서드를 하나만 담은 인터페이스(드물게 추상 클래스)를 사용했다. 이러한 인터페이스의 인스턴스를 함수 객체라고 하며, 특정 함수나 동작을 나타내는데 썼다. 이후 JDK 1.1부터는 함수 객
🔗 메서드 참조 메서드 참조란, 함수 객체를 람다보다 간결하게 만드는 방법이다. ▶️ 람다 map의 merge() 메서드는 키, 값, 함수를 인자로 받으며 주어진 키가 맵 안에 아직 없다면 주어진 {키,값} 쌍을 그대로 저장하는 함수이다. 반대로 키가 이미 있다면
1️⃣ 개요 자바8 부터 람다를 지원하면서, API를 작성하는 모범 사례들도 크게 바뀌었다. 예를 들어, 상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 템플릿 메서드 패턴 대신 함수 객체를 매개변수로 받는 정적 팩터리나 생성자를 제공하고는 한다. 많이
스트림이란 자바 8부터 다량의 데이터 처리 작업(순차/병렬)을 돕고자 나온 API이며, 두 가지 핵심적인 추상 개념을 제공한다. 스트림 API는 메서드 연쇄를 지원하는 프루언트 API(fluent API)이다. > 🔖 스트림의 추상 개념 1) 스트림(stream)
스트림은 단순한 또 하나의 API가 아닌, 함수형 프로그램에 기초하고 있다. 1️⃣ 스트림 패러다임 스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분이다. 이때 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다. > 🔖
1) 스트림 도입 이전 스트림이 도입되기 이전에 원소 시퀀스, 즉 일련의 원소를 반환하는 메서드의 반환 타입으로 Collection, Set, List과 같은 컬렉션 인터페이스나 Iterable 또는 배열을 사용했다. 2) 스트림 도입 이후 스트림은 반복(iterat
1️⃣ 동시성 프로그래밍의 역사 > 1️⃣ 릴리스 스레드, 동기화, wait/notify 지원했다. > 2️⃣ 자바 5 이후 동시성 컬렉션인 java.util.concurrent 라이브러리와 실행자(Executor) 프레임워크 지원했다. > 3️⃣ 자바 7 이후 고성
메서드와 생성자는 입력 매개변수의 값이 특정 조건을 만족해야 하는 경우가 많은데, 이러한 경우 오류는 가능한 한 빨리 검사해야 하는 것이 좋으므로 메서드 몸체가 시작되기 전에 매개변수를 검사하는 것이 좋다. 만약 매개변수 검사를 제대로 하지 못하면, 아래와 같이 실패
1️⃣ 방어적 복사(Defensive Copy) 자바는 C/C++와 같이 안전하지 않은 언어에서 자주 보는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류가 일어나지 않는다는 점에서 안전한 언어이다. 즉 자바로 작성한 클래스는, 무조건 그 불변식이
API 핵심 설계 요령들을 몇가지 알아보도록 하자. 🔗 메서드 이름은 신중히 짓자 표준 명명 규칙(아이템 68)을 따라야 한다. 먼저 이해할 수 있고 같은 패키지에 속한 다른 이름들과 일관되게 지어야 하며, 그 다음으로 개발자 커뮤니티에서 널리 받아들여지는 이름을
재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택된다. 따라서 다중 정의는 신중하게 사용해야 하는데, 각각 개념과 올바르게 다중 정의를 사용하는 방법에 대해 알아보자.메서드 재정의란, 상위 클래스가 정의한 것과 같은 시그니처의 메서드를 하위 클래스
가변인수(vargs) 메서드란, 명시한 타입의 인수를 0개 이상 받을 있는 메서드를 의미한다. 가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 배열에 저장하여 가변인수 메서드에 건네준다.▶️ 가변인수 활용 예시▶️ 인수가 1개
컬렉션이나 배열같은 컨테이너(Container)가 비었으면 null 을 반환하는 메서드는, 항상 방어 코드를 넣어줘야 한다.즉, 다음과 같이 클라이언트가 null 상황을 처리하는 코드를 추가적으로 작성해야 하는 것이다.null 대신 빈 컨테이너를 할당한다면, 방어 코드
메서드가 "특정 조건에서 값을 반환할 수 없을 때" 취할 수 있는 선택지는 다음과 같이 존재한다.예외를 던진다.반환 타입이 객체 참조라면, Null을 반환한다. 하지만 예외는 정말 예외인 상황에서만 사용해야 하며(아이템 69) 생성 시 스택 추적 전체를 갭처하므로 비용
쓸모 있는 API에는 잘 작성된 문서도 필요하다. 자바에서는 자바독(Javadoc)이라는 유틸리티를 지원하고 있는데, 소스코드 파일에서 문서화 주석이라는 특수한 형태로 기술된 설명을 추려 API 문서로 변환해준다. 🔗 문서화 주석을 다는 방법 1) 공개된 모든 클래
지역변수의 유효 범위를 최소로 줄이면, 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다. C 언어 지역변수를 코드 블럭의 첫 머리에 선언해야 한다. JAVA 문장을 선언할 수 있는 곳이면 어디서든 변수를 선언 가능하다. 🔗 지역변수의 범위를 줄이는
🔗 For-Each문을 사용하기 이전 전통적 for문을 사용해서 반복하기 ▶️ 컬렉션 순회 ▶️ 배열 순회 while문 보다는 낫지만, 가장 좋은 방법은 아니다. (아이템 57) 다음의 이유를 보자. 반복자와 인덱스 변수는 코드만 지저분하게 할 뿐 꼭 필요한 것
표준 라이브러리를 유용하게 쓰면, 다음과 같은 장점들이 존재한다.핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.노력하지 않아도 성능이 지속해서 개선된다.기능이 점점 많아진다.다른 개발자들이 읽고 유지보수, 재활용 하기 좋은 코드가 된다.해
float 과 double 타입은, 이진 부동소수점 연산에 쓰이며 넓은 범위의 수를 빠르게 정밀한 근사치로 계산하도록 설계되었다.따라서, 정확한 결과가 필요할때는 부적합하다. 특히나, 금융 관련 계산에는 사용하면 안된다. 0.1 혹은 10의 음의 거듭제곱 수를 표현할
자바의 데이터 타입은 크게 두가지로 나눠진다.기본 타입 : int, double, boolean..참조 타입 : Integer, Double, Boolean오토박싱과 오토언박싱 때문에 두 타입이 자동으로 형변환 되고, 크게 구분해서 사용하지 않을 수 있지만 둘은 큰 차
s
객체의 실제 클래스를 사용할 상황은, 오직 생성자로 생성할 때 뿐이다.따라서 적합한 인터페이스만 있다면 매개변수 뿐 아니라 반환값, 변수, 필드 전부 인터페이스 타입으로 선언해야 한다. 인터페이스를 타입으로 사용하는 프로그램은 훨씬 유연해지기 때문이다.만약 나중에 구현
ㄴㄴㅇ
자바 네이티브 인터페이스란, 자바 프로그램이 네이티브 메서드를 호출하는 기술이다. > 🔖 네이티브 메서드 C/C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드 일반적인 네이티브 메서드의 주요 쓰임은 다음과 같이 세가지이다. 주요 쓰임새 레지스트리 같은 플랫폼
최적화는 섣불리 진행하면 좋은 결과보다 해로운 결과로 이어지기 쉽다. 따라서, 프로그램이 우선 좋은 아키텍처를 가질 수 있도록 한 이후에나 최적화를 고려해야 한다. ☁️ 좋은 설계 성능을 제한하는 설계를 피하라 구현상의 문제가 아닌 아키텍처 결함이 성능을 제한하는
🌱 철자 규칙 ☁️ 패키지와 모듈 이름 각 요소를 . 으로 구분하여 계층적으로 짓는다. 만약 조직 바깥에서도 사용될 패키지라면, 조직의 인터넷 도메인 이름을 역순으로 사용하면 된다. ex) com.google 각 패키지 이름은 소문자를 사용한 8자 이하의 짧은 단어
예외는 예외 상황에서만 써야 하지, 절대로 일상적인 제어 흐름용으로 쓰여서는 안된다. 또한, 이를 프로그래머에게 강요하는 API를 만들어서도 안된다. 제어 흐름용으로써의 사용 배열의 원소를 순회하는데, 무한루프를 돌다가 배열의 끝에 도달해 예외가 발생하면 끝을 내는
자바는 문제 상황을 알리는 타입(throwable)으로 다음의 세가지를 제공한다.검사 예외 : Exception 하위 클래스들런타임 예외 : RuntimeException 하위 클래스들에러 : Error 하위 클래스들해당 세가지 중에 언제 어떠한 타입을 사용하면 좋을지
검사 예외는 제대로 활용한다면, API와 프로그램의 질을 높일 수 있다.
예외를 사용할때는, 이미 자바 라이브러리에서 제공하고 있는 예외들, 즉 표준 예외를 사용하는 것이 좋다. 또한, 이름뿐 아니라 예외가 던져지는 맥락도 부합할 때만 재사용해야 한다.표준 예외를 재사용한다면 아래와 같은 장점이 존재한다.나의 API가 다른 사람이 익히고 사
메서드가 저수준 예외를 처리하지 않고 바깥으로 전파( throws )해버릴 때, 수행하려는 일과 관련 없어 보이는 예외가 튀어나오게 된다. 이는 내부 구현 방식을 드러내어 윗 레벨의 API를 오염시킬 수 있다는 단점이 있다.이 문제를 피하려면, 상위 계층에서는 저수준
메서드가 던지는 예외는, 그 메서드를 올바로 사용하는 데 아주 중요한 정보이다. 발생 가능한 예외를 문서로 남기지 않으면 그 클래스나 인터페이스를 효과적으로 사용하기 어렵거나 불가능할 수도 있기 때문이다. 따라서, 예외를 문서화하는 것은 매우 중요하다.검사 예외는 항상
Q. 동기화란 무엇일까?다른 스레드가 변경중이여서 일관되지 않은 상태를, 락을 검으로써 다른 스레드가 보지 못하게 막는다. 즉 일관성이 깨진 상태를 볼 수 없게 한다. 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 볼 수 있게 해준다.우리는 자바의 synchron
과도한 동기화는 오히려 성능을 떨어트리고, 교착상태에 빠뜨린다. 동기화된 영역 안에서 재정의할 수 있는 메서드 호출하는 경우 재정의 가능한 메서드는, 자식에서 어떤 짓을 하는지 통제할 수 없기 때문에(ex) 부모 메서드를 다시 호출) 예외를 일으키거나 교착상태에 빠
스케줄링 정책은 운영체제마다 다를 수 있으므로, 프로그램의 성능이 스케줄러에 따라 달라지지 않도록 해야 한다. 아래 세 가지 방안을 지킨다면, 스케줄러에 독립적인 이식성이 높은 프로그램을 만들 수 있다. ☁️ 실행 가능한 스레드 작게 유지하기 실행 가능한 스레드
싱글톤고 같이 불변식을 지키려고 한다면, 열거 타입을 사용하자. readResolve 를 사용한다면 반드시 모든 참조 타입 인스턴스 필드를 transient 로 선언해야 한다. 싱글턴 클래스 직렬화 문제 싱글턴 클래스를 직렬화 하려고 Seriablizable 를 구현