diff --git a/ch10/item71.md b/ch10/item71.md new file mode 100644 index 0000000..764ec5d --- /dev/null +++ b/ch10/item71.md @@ -0,0 +1,51 @@ +# Item 71 + +## 필요 없는 검사 예외 사용은 피하라 + +### 검사 예외란? +> - 어플리케이션 수행 중에 일어날법한 예외를 검사하고 대비하라는 목적으로 사용.
+> - 반드시 예외 처리를 해야한다.
+> - 컴파일러가 발견해서 컴파일 오류를 발생시킨다. +> - try/catch, throws 방식이 존재 +> - 처리를 강요하는 것이기 때문에 과하게 사용한다면 API 사용자에게 부담을 준다. +
+ +### 검사 예외를 피하는 방법 +- 빈 Optional을 반환 +- 상태 검사 메서드와 Unchecked Exception을 추가 +
+ +#### 예시 1, 빈 Optional +```java +public static Optional create() { + + try { + database.create(); // create() 메서드 안에선 throws IOException으로 컴파일 에러 발생 가정. + } catch (IOException e) { + return Optional.empty(); // 빈 옵셔널 반환 + } + return Optional.of(Database); +} +``` +- 예외가 발생한 부가 정보를 담을 수 없으므로 좋은 방법은 아님 +
+ +#### 예시 2, 상태 검사 메서드와 Unchecked Exception +``` java +public static Optional create() { + + if (database.canCreateFile()) { // 예외가 던져질 지 여부를 boolean값으로 반환 + database.create(); + } else { + throw new CustomDatabaseIOException(); + } +} +``` +- 여러 스레드가 동시에 접근한다면 canCreateFile와 create 호출부 사이에 외부 요인이 접근 가능. +- 그로 인해 객체의 상태가 변할 수 있다. +
+ +### 결론 +1. API호출자가 예외 상황에서 복구할 방법이 없다 => 비검사 예외를 던진다. +2. API호출자가 예외 상황에서 복구할 방법이 있으며, 호출자가 처리하기를 원한다. => optional 반환 고려, 예외가 발생할 수 있음을 암시해야함. +3. API호출자가 예외 상황에서 복구할 방법이 있으며, 예외를 api에서 던진다. => 검사 예외를 던진다. diff --git a/ch10/item72.md b/ch10/item72.md new file mode 100644 index 0000000..1a5b136 --- /dev/null +++ b/ch10/item72.md @@ -0,0 +1,44 @@ +# Item 72 + +## 표준 예외를 사용하라 +
+ +### 표준 예외란? +> - 자바 API에서 제공하는 예외.
+> - 가독성이 높아지고, 사용하기 쉽다.
+> - 메모리 사용량도 줄어들고, 클래스를 메모리에 적재하는 시간도 적게 걸린다.
+
+ +### 대표적인 표준 예외 +- IllegalArgumentException + - 허용하지 않은 값이 인수로 전달되었을 때 + +- IllegalStateException + - 객체가 메서드를 사용하기에 적절하지 않은 상태일 때 + +- NullPointerException + - null을 허용하지 않는 메서드가 null을 인자로 받을 때 + +- IndexOutOfBoundsException + - 인덱스의 범위를 넘어설 때 + +- ConcurrentModificationException + - 허용되지 않은 동시 수정이 발견되었을 때 + +- UnsupportedOperationException + - 호출한 메서드가 지원하지 않을 때 + +
+ +### Exception, RuntimeException, Throwable, Error는 직접 재사용 하지 말 것 +- 위 클래스들은 예외 클래스들의 상위 클래스이다. 즉, 구체적인 예외 정보를 알 수 없으므로 안정적으로 테스트할 수 없다. + +
+ +### 주의사항 + +- 상황에 부합한다면 항상 표준 예외를 재사용하자. 단, API 문서를 참고하여 사용하고자 하는 Exception이 이름 뿐만아니라 맥락에도 부합하는지 확인해야한다. + +- 더 많은 정보를 제공하고자 한다면 표준 예외를 확장해도 된다. 단, 예외는 직렬화할 수 있다는 것을 감안해야한다. + + 직렬화에는 많은 부담이 따르므로 이 사실만으로도 커스텀한 예외를 사용해야할 이유가 사라진다. diff --git a/ch11/item83.md b/ch11/item83.md new file mode 100644 index 0000000..fe4b99d --- /dev/null +++ b/ch11/item83.md @@ -0,0 +1,108 @@ +# Item 83 + +## 지연 초기화는 신중히 사용하라 + +### 지연초기화란? +> - 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법.
+> - 주로 최적화 용도로 사용.
+> - 클래스와 인스턴스 초기화 때 발생 하는 위험한 순환문제를 해결하는 효과도 있음. +
+ +### 지연초기화는 양날의 검이다. +- 지연초기화를 사용한다면 인스턴스 생성 시의 초기화 비용은 줄지만 그 대신 필드에 접근하는 비용은 커진다. +- 지연 초기화가 실제로는 성능을 느려지게 할 수 있다. +> - 초기화가 이뤄지는 비율은 어떠한가.
+> - 실제 초기화에 비용이 얼마나 드는가.
+> - 초기화된 각 필드를 얼마나 빈번히 호출되는가. +
+ +따라서, 성능을 생각한다면 지연초기화는. +- 해당 클래스의 인스터스 중 그 필드를 사용하는 인스턴스의 비율이 낮고 +- 그 필드를 초기화하는 비용이 크다면 사용하자. +- 즉. 초기화 시점 해당 필드 or 클래스가 필요할 때 시키는 것이다! +- 그러나, 성능을 측정해보기 전까지는 어떤 것이 더 나은지 확신하지 못함. + +또한, 멀티스레드 환경에서는 지연초기화 하는 필드를 둘 이상의 스레드가 공유한다면 +어떠한 형태로든 반드시 동기화를 해야함! + +#### 예시 1, 일반적인 초기화 방법 +```java +public class TestClass { + private final Member member = createMember(); + + private Member createMember() { + return new Member("KJJ"); + } +} +``` +- 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다. +
+ +#### 예시 2, 인스턴스 필드의 지연초기화 +``` java +public class TestClass { + private Member member; + + public synchronized Member getMember(){ + if(member==null){ + member = createMember(); + } + return member; + } + private Member createMember() { + return new Member("KJJ"); + } +} +``` +- 지연 초기화가 초기화 순환성(initialization circularity) 을 깨뜨릴 것 같으면 synchronized 를 단 접근자를 사용하자. +
+ +#### 예시 3, 정적 필드용 지연 초기화 홀더 클래스 관용구 +``` java +public class TestClass { + + static final private Member member = createMember(); + + private static Member createMember() { + System.out.println("init"); + return new Member("KJJ"); + } + + public static Member getMember(){ + return TestClass.member; + } +} +``` +- 해당 방식은 클래스는 클래스가 처음 쓰일 때 비로소 초기화 된다는 특성을 이용한 관용구다. +- 이러한 방식은 getMember 메서드가 필드에 접근하면서 동기화를 전혀 하지않으니 성능이 느려질 거리가 전혀 없다. +
+ +#### 예시 4, 인스턴스 필드 지연 초기화용 이중검사 관용구 +``` java +public class TestClass { + + private volatile Member member; + + private Member createMember() { + return new Member("KJJ"); + } + + public Member getMember() { + Member result = member; + if(result!=null){ + return result; + } + synchronized (this){ + if(member == null){ + member = createMember(); + } + return member; + } + } +} +``` +- 성능 때문에 인스턴스 필드를 지연 초기화해야 한다면 이중검사 관용구를 사용하라. +- 해당 관용구는 초기화된 필드에 접근할 때의 동기화 비용을 없애준다. +- 필드가 초기화 된 후로는 동기화 하지 않으므로 해당 필드는 반드시 volatile로 선언한다. +- result 지역변수는 필드가 이미 초기화된 상황에서는 이 필드를 딱 한번만 읽도록 보장하는 역할을 한다 +
diff --git a/ch11/item84.md b/ch11/item84.md new file mode 100644 index 0000000..4dfcbb9 --- /dev/null +++ b/ch11/item84.md @@ -0,0 +1,26 @@ +# Item 84 + +## 프로그램의 동작을 스레드 스케줄러에 기대하지 말라 + +### 스레드 스케줄러란? +> - 스레드의 실행 시점을 관리하는 역할을 한다.
+> - 사용자가 제어할 수 없다.
+> - 제한된 자원을 여러 프로세스가 효율적으로 사용하도록 다양한 정책(Policy)을 가지고 할당한다. +
+ +### 당장 처리해야할 작업이 없다면 실행하지 마라 +- 스레드 풀 크기를 적절히 설정하고, 작업은 짧게 유지해야 한다. + - 단, 너무 짧으면 오히려 작업 분배에 부담이가서 성능을 떨어뜨릴 수 있다. + - 바쁜 대기(busy waiting) 상태가 되면 안된다 +
+ +### Tread.yield에 의존하지 마라 +- Tread.yield 사용하여 성능을 개선시킬 수 있다. +- 하지만, 어느정도 개선이 될 수 있지만, 효과가 없을수도 있고 오히려 느려질 수 있다. +- Tread.yield는 테스트할 수단도 없다. +- 애플리케이션 구조를 바꿔 동시에 실행가능한 스레드 수가 적어지도록 조치하는 것이 올바른 방법이다. +
+ +### 스레드 우선 순위에 의존하지 마라 +- 스레드 우선순위는 자바에서 이식성이 가장 나쁜 특성에 속한다 +- 심각한 응답 불가 문제를 스레드 우선순위로 해결하려는 시도는 진짜 원인을 찾아 수정하기 전까지 같은 무넺가 반복해서 터져 나올 것이다. diff --git a/ch12/item89.md b/ch12/item89.md new file mode 100644 index 0000000..4665635 --- /dev/null +++ b/ch12/item89.md @@ -0,0 +1,69 @@ +# Item 89 + +## 인스턴스 수를 통제해야한다면 readResolve보다는 열거 타입을 사용하라 + +### readResolve 란? +> - 인스턴스 통제 목적으로 사용함 .
+> - 단 객체 참조 타입 인스턴스 필드는 모두 transient로 선언해야한다.
+> - 그렇지 않으면 readResolve 메서드가 수행되기 전에 역직렬화된 객체의 참조를 공격할 여지가 남는다. +
+ +### transient 란? +- +- +
+ +### 싱글톤을 직렬화하는 경우 문제점 +- 클래스에 implements Serializable 을 추가하는 순간 더 이상 싱글턴이 아니게 된다. +- 기본 직렬화를 쓰지 않고, 명시적인 readObject를 제공하더라도 소용없다. +- 어떤 readObject를 사용하든 이 클래스가 초기화될 때 만들어진 인스턴스와는 별개인 인스턴스를 반환하게 된다. + +### + +#### 예시 1, 싱글톤 객체 +```java +public final class Elvis implements Serializable { + private static final Elvis INSTANCE = new Elvis(); + + private static Elvis() { + } + + public static Elvis getINSTANCE() { + return INSTANCE; + } + + private String[] favoriteSongs = {"Sandman", "aquaman"} + + public void printFavorites() { + System.out.println(Arrays.toString(favoriteSongs); + } + + private Object readResolve() { + return INSTANCE; + } +} +``` +- 위 예시에서는 favoriteSongs를 transient로 선언할 것이 아니라면 enum타입으로 만드는 것이 좋다. +- 싱글턴이 non-transient 참조필드를 가지고 있으면 이 내용은 readResolve가 실행되기 전에 역직렬화 된다. +- 그렇게 되면 조작된 스트림을 사용하여 해당 참조 필드의 내용이 역직렬화되는 시점에 참조를 훔쳐올 수 있다. +
+ +``` java +public enum Elvis { + INSTANCE; + private String[] favoriteSongs = {"sandman", "aquaman"}; + + public void printFavorites() { + System.out.println(Arrays.toString(favoriteSongs); + } +} +``` +- 이렇게 enum 타입으로 하면 된다. +
+ +##결론 +- 직렬화가 필요하며 인스턴스 수가 하나임을 보장하고 싶을 때, enum 타입을 가장 먼저 고려해라. +- 만약 enum 타입을 사용하는 것이 불가능하며, 인스턴스 수를 제한해야 한다면, readResolve를 구현해라. +- 또한, 해당 클래스의 모든 필드를 원시타입 또는 임시타입으로 정의되어야 한다. + + diff --git a/ch12/item90.md b/ch12/item90.md new file mode 100644 index 0000000..d60192f --- /dev/null +++ b/ch12/item90.md @@ -0,0 +1,78 @@ +# Item 90 + +## 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 +- Serializable을 구현하기로 결정한 순간, 언어의 생성자 이외의 방법으로도 인스턴스를 생성할 수 있게 된다. +- 버그 및 보안 문제가 일어날 가능성이 커진다. +- 직렬화 프록시 패턴 (Serialization Proxy Pattern)을 이용하면 이러한 위험을 크게 줄여줄 수 있다. +-
+ +### 직렬화 프록시 패턴이란? +> - 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해준다.
+> - 필드들을 final로 선언해도 되므로 Period 클래스를 진정한 불변으로 만들 수 있다.
+> - 역직렬화때 유효성 검사를 하지 않아도 된다. +> - 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 작동한다. +
+ +#### 예시 1, 직렬화 프록시 패턴 +```java + public final class Period implements Serializable { + + private final Date start; + private final Date end; + + public Period(Date start, Date end) { + if (this.start.compareTo(this.end) > 0) { + throw new IllegalArgumentException(start + "가 " + end + "보다 늦다."); + } + this.start = new Date(start.getTime()); + this.end = new Date(end.getTime()); + } + + //자바의 직렬화 시스템이 바깥 클래스의 인스턴스 말고 SerializationProxy의 인스턴스를 반환하게 하는 역할 + //이 메서드 덕분에 직렬화 시스템은 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없다 + //"프록시야 너가 대신 직렬화되라" + private Object writeReplace() { + return new SerializationProxy(this); + } + + //불변식을 훼손하고자 하는 시도를 막을 수 있는 메서드 + //"Period 인스턴스로 역직렬화를 하려고해? 안돼" + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("프록시가 필요합니다"); + } + + //바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스(Period의 직렬화 프록시) + private static class SerializationProxy implements Serializable { + + private static final long serialVersionUID = 234098243823485285L; + + private final Date start; + private final Date end; + + //생성자는 단 하나여야 하고, 바깥 클래스의 인스턴스를 매개변수로 받고 데이터를 복사해야 함 + SerializationProxy(Period p) { + this.start = p.start; + this.end = p.end; + } + + //역직렬화시 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해줌. + //역직렬화는 불변식을 깨뜨릴 수 있다는 불안함이 있는데, + //이 메서드가 불변식을 깨뜨릴 위험이 적은 정상적인 방법(생성자, 정적 팩터리, 다른 메서드를 사용)으로 역직렬화된 인스턴스를 얻게 한다. + //"역직렬화 하려면 이거로 대신해" + private Object readResolve() { + return new Period(start, end); + } + } + } + ``` + +### 직렬화 프록시 패턴의 한계? +- 클라이언트가 조건없이 확장할 수 있는 클래스에는 적용할 수 없다.
+- 객체그래프 순환이 있는 클래스에는 적용할 수 없다.
+- 방어적 복사보다 느리다.
+ + +## 결론 +- 확장할 수 없는 클래스라면 가능한 직렬화 프록시 패턴을 사용하자. + +