├── .gitignore ├── 10장_예외 ├── Item72.md ├── README.md ├── item70.md ├── item73.md ├── item74.md ├── item75.md ├── item76.md └── item77.md ├── 11장_동시성 ├── README.md ├── item78.md ├── item81.md └── item82.md ├── 12장_직렬화 ├── README.md ├── item86.md └── item89.md ├── 2장_객체_생성과_파괴 ├── README.md ├── img │ ├── item7_1.png │ ├── item7_2.png │ └── item7_3.png ├── item1.md ├── item2.md ├── item3.md ├── item4.md ├── item5.md ├── item6.md ├── item7.md ├── item8.md └── item9.md ├── 3장_모든_객체의_공통_메서드 ├── README.md ├── item11.md ├── item12.md ├── item13.md └── item14.md ├── 4장_클래스와_인터페이스 ├── README.md ├── item16.md ├── item17.md ├── item18.md ├── item19.md ├── item20.md ├── item21.md ├── item22.md ├── item24.md └── item25.md ├── 5장_제네릭 ├── Item28.md ├── README.md ├── item26.md ├── item27.md ├── item29.md ├── item30.md ├── item31.md └── item33.md ├── 6장_열거_타입과_애너테이션 ├── Item34.md ├── Item40.md ├── README.md ├── item35.md ├── item36.md ├── item37.md ├── item38.md └── item41.md ├── 7장_람다와_스트림 ├── Item42.md ├── Item48.md ├── README.md ├── img_1.png ├── img_2.png ├── item43.md ├── item45.md └── item46.md ├── 8장_메서드 ├── Item56.md ├── README.md ├── item49.md ├── item50.md ├── item51.md ├── item52.md ├── item53.md └── item54.md ├── 9장_일반적인_프로그래밍_원칙 ├── README.md ├── item57.md ├── item58.md ├── item59.md ├── item60.md ├── item61.md ├── item62.md ├── item64.md ├── item65.md ├── item66.md ├── item67.md └── item68.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/intellij+all 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### Intellij+all Patch ### 84 | # Ignore everything but code style settings and run configurations 85 | # that are supposed to be shared within teams. 86 | 87 | .idea/* 88 | 89 | !.idea/codeStyles 90 | !.idea/runConfigurations 91 | 92 | .DS_Store 93 | 94 | # End of https://www.toptal.com/developers/gitignore/api/intellij+all -------------------------------------------------------------------------------- /10장_예외/Item72.md: -------------------------------------------------------------------------------- 1 | # Item 72 표준예외를 사용하라 2 | 3 | ### 표준예외의 장점 4 | 5 | - 많은 프로그래머에게 익숙해진 규약을 그대로 따르게한다 6 | - 예외 클래스 수가 적어진다 7 | - 클래스 수가 적어지니 메모리 사용량도 줄고 클래스를 적재하는 시간도 적게 걸린다 8 | 9 | 10 | ### 결론 11 | - 상황에 부합한다면 '항상' 표준예외를 재사용하자. 12 | 13 | - 적재적소에 적절한 표준예외를 사용하자 14 | - Exception, RuntimeException, Throwable, Error는 여러 성격의 예외들을 포괄하는 클래스이므로 직접 재사용하지말자 15 | -------------------------------------------------------------------------------- /10장_예외/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |--------------------------|-----|-----| 3 | | 아이템 | 발표자 | 질문자 | 4 | -------------------------------------------------------------------------------- /10장_예외/item70.md: -------------------------------------------------------------------------------- 1 | ## 아이템 70 복구 할 수 있는 상황에서는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용해라 2 | 3 | 자바는 문제 상황을 알리는 타입으로 아래와 같이 3가지를 제공한다. 4 | + 검사 예외 (Checked Exception) 5 | + 런타임 예외 (Runtime Exception) 6 | + 에러 (Error) 7 | 8 | 9 | ## 검사 예외와 비검사 예외를 구분하는 규칙 10 | > 호출 하는 쪽에서 복구하리라 여겨 지는 상황이라면 검사 예외를 사용해 11 | ### 검사 예외 (Checked Exception) 12 | 검사 예외를 던지면 호출자가 그 예외를 catch 로 잡아 처리하거나 더 바깥으로 전파하도록 강제하게 된다. 13 | 14 | 따라서 메서드 선언에 포함된 검사 예외 각각은 그 메서드를 호출 했을때 발생할 수 있는 유력한 결과임을 API 사용자에게 알려주는 것이다. 15 | 16 | ```java 17 | // 메서드 선언에 포함된 검사 예외 예제 18 | public void copy() throws IOException { 19 | } 20 | ``` 21 | 22 | ### 비검사 예외 (UnChecked Exception) 23 | 비검사 예외는 아래 두가지로 나눠지게 된다. 24 | + 런타임 예외 (Runtime Exception) 25 | + 에러 (Error) 26 | 27 | 이 둘은 통상적으로 프로그램에서 잡을 필요가 없거나 잡지 말아야한다. 28 | 29 | 프로그램에서 비검사 예외를 던졌다는 것은 복구가 불가능하니 더이상 실행해봐야 득보다는 실이 많다는 뜻이다. 30 | 31 | #### 런타임 예외 (Runtime Exception) 32 | > 프로그래밍 오류를 나타낼때는 런타임 예외를 사용하자 33 | 34 | + 런타임 예외는 대부분 전제조건을 만족하지 못했을 경우 발생한다. 35 | 36 | + 예를들어 `ArrayIndexOutOfBoundsExceptin`은 배열의 전재 조건이 만족하지 않았을 경우 발생한다 37 | + 배열의 인덱스는 0에서 `배열크기 -1` 이여야한다는 전제조건 38 | 39 | + 복구 할 수 있는 상황인지 프로그래밍 오류인지 명확히 구분 하기는 힘들다. 40 | 41 | + 그래서 API 설계자의 판단에 따라서 복구 가능하다면 검사 예외를, 복구 불가능 하다면 런타임 예외를 사용하자 42 | 43 | 44 | #### 에러 (ERROR) 45 | + 에러는 보통 JVM 자원 부족, 불변식 깨짐 등 더이상 수행을 계속할 수 없는 상황에서 사용한다. 46 | + 구현하는 비검사는 모두 RuntimeException 의 하위 클래스여야한다. 47 | + Error 클래스를 만드는 일은 자제해라 (자바 언어 명세 X, 업계에 퍼진 규약) 48 | + throw 문으로 던지는 일도 없어야한다. 49 | + Exception, RuntimeException, Error 를 상속하지 않고, Throwable 을 만들 수 도 있다. 절대 사용하지마라 검사 예외보다 좋을게 하나도 없고 사람들만 헷갈리게한다 50 | 51 | 52 | #### 예외 역시 객체 53 | + 예외의 메소드는 주로 그 예외를 일으킨 상황에 관한 정보를 코드 형태로 전달하는데 쓰인다. 54 | + 그러나 throwable 클래스들은 대부분 오류 메시지 포맷을 상세히 기술하지 않는다. 55 | + 따라서 파싱 해서 사용해야하는데 이건 JVM이나 릴리스에 따라 포맷이 달라질 수 있기 때문에 하지 않는것이 좋다. 56 | 57 | ?.. 에외의 메소드 ..? 58 | 59 | 60 | #### 검사 예외는 호출자가 예외상황을 벗어나는데 필요한 정보를 알려주는 메서드를 제공하자 61 | + 검사 예외는 일반적으로 복구할 수 있는 조건일 때 발생한다. 62 | + 따라서 호출자가 예외에서 상황을 벗아나는데 필요한 정보를 알려주는 메서드를 제공해줘야한다. 63 | + ex) 카드잔고가 부족해서 검사 예외가 발생했다고 하면, 잔고가 얼마나 부족한지를 알려주는 접근자 메서드를 제공해야한다. 64 | 65 | 66 | 67 | ## 핵심정리 68 | > 1. 복구 할 수 있는 상황이면 검사 예외(Checked Exception)을, 프로그래밍 오류라면 비검사 예외(UnChecked Exception)를 던지자. 69 | > 2. 확실하지 않다면 비검사 예외를 던지자 (UnChecked Exception) 70 | > 3. 검사 예외라면 필요한 정보를 알려주는 메서드도 함께 제공하자 71 | -------------------------------------------------------------------------------- /10장_예외/item73.md: -------------------------------------------------------------------------------- 1 | # 아이템73. 추상화 수준에 맞는 예외를 던지라 2 | 3 | 메서드가 저수준 예외를 처리하지 않고 바깥으로 전파하면 수행하려는 일과 관련 없어 보이는 예외가 발생한다. 4 | 5 | **사실 이는 단순이 개발자를 당황시키는 데 그치지 않고, 내부 구현 방식을 드러내어 윗 레벨 API를 오염시킨다.** 6 | 7 | - 다음 릴리즈에서 구현 방식을 바꾸면 다른 예외가 튀어나와 기존 클라이언트 프로그램을 깨지게 할 수 있다. 8 | - **이 문제를 피하려면 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다.** 9 | - 이를 `예외 번역(exception translation)`이라고 함 10 | 11 | **예외 번역** 12 | 13 | ```java 14 | try { 15 | ... // 저수준 추상화를 이용한다 16 | } catch (LowerLevelException e) { 17 | // 추상화 수준에 맞게 번역한다. 18 | throw new HigherLevelException(...); 19 | } 20 | ``` 21 | 22 | ## 1. API 사용 사례 23 | 24 | 다음은 `AbstractSequentialList` 에서 수행하는 예외 번역의 예다. 25 | 26 | AbstractSequentialList는 List 인터페이스의 골격 구현 이다. 27 | 28 | 이 사례에서 수행한 예외 번역은 `List` 인터페이스의 get 메서드 명세에 명시된 필수사항임을 기억해두자. 29 | 30 | ```java 31 | /** 32 | * 이 리스트 안의 지정한 위치의 원소를 반환한다. 33 | * @throws IndexOutOfBoundsException index가 범위 밖이라면, 34 | * 즉 ({@code index < 0 || index >= size()})이면 발생한다. 35 | */ 36 | public E get(int index) { 37 | try { 38 | return listIterator(index).next(); 39 | } catch (NoSuchElementException exc) { 40 | throw new IndexOutOfBoundsException("Index: "+index); 41 | } 42 | } 43 | ``` 44 | 45 | **IndexOutOfBoundsException.java** 46 | 47 | ```java 48 | public class IndexOutOfBoundsException extends RuntimeException { 49 | ... 50 | } 51 | ``` 52 | 53 | 여기에서 `NoSuchElementException` 예외를 클라이언트에게는 `IndexOutOfBoundsException` 로 보여주게 된다. 54 | 55 | ## 2. 예외 연쇄 56 | 57 | 예외를 번역할 때, 저수준 예외가 디버깅에 도움이 된다면 `예외 연쇄(exception chaining)`를 사용하는 게 좋다. 58 | 59 | **예외 연쇄?** 60 | 61 | - 문제의 근본 원인인 저수준 예외를 고수준 예외에 실어 보내는 방식 62 | - 접근자 메서드(Throwable의 getCause 메서드)를 통해 필요하면 언제든 저수준 예외를 꺼내볼 수 있음 63 | 64 | **예외 연쇄 예시** 65 | 66 | ```java 67 | try { 68 | ... // 저수준 추상화를 이용한다. 69 | } catch (LowerLevelException cause){ 70 | // 저수준 예외를 고수준 예외에 실어 보낸다. 71 | throw new HigherLevelException(cause); 72 | } 73 | ``` 74 | 75 | 고수준 예외의 생성자는 예외 연쇄용으로 설계된 상위 클래스의 생성자에 이 원인을 건네주어 최종적으로 Throwable 생성자까지 건네지게 한다. 76 | 77 | **예외 연쇄용 생성자** 78 | 79 | ```java 80 | class HigherLevelException extends Exception { 81 | HigherLevelException(Throwable cause) { 82 | super(cause); 83 | } 84 | } 85 | ``` 86 | 87 | 대부분의 표준 예외는 **예외 연쇄용 생성자를** 갖추고 있다. 88 | 89 | - 그렇지 않은 예외라도 Throwable의 initCause 메서드를 이용해 원인을 직접 전달할 수 있음 90 | - 예외 연쇄는 문제의 원인을 `getCause` 메서드로 프로그램에서 접근할 수 있게 해주며 원인과 고수준 예외의 스택 추적 정보를 잘 통합해준다. 91 | 92 | **무턱대고 예외를 전파하는 것보다 예외 번역이 우수한 방법이지만, 그렇다고 남용해서는 안 된다.** 93 | 94 | - 가능하다면 저수준 메서드가 반드시 성공하도록하여 아래 계층에서 예외가 발생하지 않도록 하는 것이 **최선** 95 | - 때로는 상위 계층 메서드의 매개변수 값을 아래 계층 메서드로 건네기 전에 미리 검사하는 방법으로 이 목적을 달성할 수 있다. (호출하기 전 매개변수 검사 등) 96 | 97 | ## 3. 차선책 98 | 99 | 아래 계층에서의 예외를 피할 수 없다면, 상위 계층에서 그 예외를 조용히 처리하여 문제를 API 호출자에게 전파하지 않는 방법이 있다. 100 | 101 | 이 경우 발생한 예외는 `java.util.logging` 같은 적절한 로깅 기능을 활용하여 기록해두면 좋다. 102 | 103 | ```java 104 | public E get(int index) { 105 | try { 106 | return listIterator(index).next(); 107 | } catch (NoSuchElementException exc) { 108 | log.info("주어진 인덱스를 넘어서는 예외가 발생했음 Index : {}" , index); 109 | } 110 | } 111 | ``` 112 | 113 | 이렇게 해두면 클라이언트 코드와 사용자에게 문제를 전파하지 않으면서도 개발자가 로그를 분석해 추가 조치를 취할 수 있게 해준다. 114 | 115 | ## 4. 정리 116 | 117 | ***예외 번역과 예외 연쇄를 적절하게 이용하자.*** 118 | 119 | **Reference** 120 | 121 | - [이펙티브자바 서적](http://www.yes24.com/Product/Goods/65551284) -------------------------------------------------------------------------------- /10장_예외/item74.md: -------------------------------------------------------------------------------- 1 | # [Item74] 메서드가 던지는 모든 예외를 문서화하라 2 | 3 | 메서드가 던지는 예외는 그 메서드를 올바르게 사용하는데 아주 중요한 정보다. 4 | 5 | 따라서 각 메서드가 던지는 예외 하나하나를 문서화하는데 충분한 시간을 쏟아야 한다. 6 | 7 | ### 검사 예외(Checked Exception)는 항상 따로따로 선언하고, 각 예외가 발생하는 상황을 자바독의 @throws 태그를 사용하여 정확히 문서화하자 8 | 9 | - 공통 상위 클래스 하나로 뭉뚱그려 선언하는 일은 삼가하자 10 | - 같은 맥락에서 발생할 여지가 있는 다른 예외들까지 삼켜버릴 수 있다 11 | 12 | ### 메서드가 던질 수 있는 예외를 각각 @throws 태그로 문서화하되, 비검사 예외는 메서드 선언의 throws 목록에 넣지 말자 13 | 14 | - 비검사 예외(Unchecked Exception)도 정성껏 문서화 해두면 그 자체만으로도 메서드를 잘 수행하기 위한 전제조건이 되어 좋다 15 | - 특히 인터페이스 메서드에서 구현한 모든 구현체가 일관되게 동작하도록 해주기 때문에 특히 중요하다 16 | - ***하지만 검사냐, 비검사냐에 따라 API 사용자가 해야할 일이 달라지므로 둘을 확실히 구분해주는게 좋다*** 17 | 18 | ### 비검사 예외도 모두 문서화하라고는 했지만 현실적으로 불가능할 때도 있다 19 | 20 | - 누군가가 만들어놓은 클래스와 발생 가능한 모든 예외를 공들여 문서가 있다고 가정했을 때 21 | - 새로운 개발자가 해당 클래스를 사용하는 메서드를 작성하고 새로운 비검사 예외를 던지게 된다면, 예상치 못한 비검사 예외를 전파하게 된다.. 22 | 23 | ### 한 클래스에 정의된 많은 메서드가 같은 이유로 같은 예외를 던진다면, 클래스 설명에 추가하는 것도 방법이다 24 | 25 | - 대표적으로 NullPointerException이 가장 흔한 사례다 26 | 27 | ### 핵심 정리 28 | 29 | - 메서드가 던질 가능성이 있는 모든 예외를 문서화 하자 (검사 예외든, 비검사 예외든, 추상 메서드든, 구체 메서드든 모두) 30 | - 자바독의 @throws 태그를 사용하여 ‘검사 예외’만 메서드 선언의 throws문에 일일이 선언하자 31 | - 발생 가능한 예외를 문서로 남기지 않으면 다른 사람이 효과적으로 해당 클래스나 인터페이스를 사용하기 어렵다, 아니 불가능할 수도 있다 -------------------------------------------------------------------------------- /10장_예외/item75.md: -------------------------------------------------------------------------------- 1 | # item75 예외의 상세 메시지에 실패 관련 정보를 담으라 2 | 3 | 예외를 잡지 못해 프로그램이 실패하면 스택 추적을 출력한다. 스택 추적은 예외 객체의 toString 메서드를 호출해 얻는 문자열로, 보통은 예외의 클래스 이름 뒤에 상세 메시지가 붙는 형태다. 4 | 5 | 사후 분석을 위해 실패 순간의 상황을 정확히 포착해 예외의 상세 메시지에 담아야 한다. 6 | 실패 순간을 포착하려면 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메시지에 담아야 한다. 7 | > 보안과 관련한 정보는 주의해서 다뤄야 한다. 문제를 진단하고 해결하는 과정에서 스택 추적 정보를 많은 사람이 볼 수 있으므로 상세 메시지에 비밀번호나 암호 키 같은 정보까지 담아서는 안된다. 8 | 9 | 스택 추적에는 예외가 발생한 파일 이름과 줄번호는 물론 스택에서 호출한 다른 메서드들의 파일 이름과 줄번호까지 정확히 기록되어 있는게 보통이다. 문서와 소스코드에서 얻을 수 있는 정보는 남길 필요가 없다. 10 | 11 | 예외의 상세 메시지와 최종 사용자에게 보여줄 오류 메시지를 혼동해서는 안 된다. 최종 사용자에게는 친절한 안내 메시지를 보여줘야 하는 반면, 예외 메시지는 가독성보다는 담긴 내용이 훨씬 중요하다. 예외 메시지의 주 소비층은 문제를 분석해야 할 프로그래와 SRE 엔지니어이기 때문이다. 12 | 13 | 실패를 적절히 포착하려면 필요한 정보를 예외 생성자에서 모두 받아서 상세 메시지까지 미리 생성해놓는 방법도 괜찮다. 상세 메시지를 만들어내는 코드를 예외클래스 안으로 모아주는 효과도 있어, 클래스 사용자가 메시지를 만드는 작업을 중복하지 않아도 된다. 14 | ```java 15 | /** 16 | * IndexOutOfBoundsException을 생성한다. 17 | * @param lowerBound 인덱스의 최솟값 18 | * @param upperBound 인덱스의 최댓값 + 1 19 | * @param index 인덱스의 실젯값 20 | */ 21 | public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) { 22 | // 실패를 포착하는 상세 메시지를 생성한다. 23 | super(String.format("최솟값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index)); 24 | // 프로그램에서 이용할 수 있도록 실패 정보를 저장해둔다. 25 | this.lowerBound = lowerBound; 26 | this.upperBound = upperBound; 27 | this.index = index; 28 | } 29 | ``` 30 | 자바9에서는 IndexOutOfBoundsException에 정수 인덱스 값을 받는 생성자가 추가되었다. 최솟값과 최댓값까지 받지는 않는다. 31 | 32 | 예외는 실패와 관련한 정보를 얻을 수 있는 접근자 메서드를 적절히 제공하는 것이 좋다. 포착한 실패 정보는 예외 상황을 복구하는데 유용할 수 있으므로 접근자 메서드는 비검사 예외보다 검사 예외에서 더 빛을 발한다. 비검사 예외의 상세 정보를 접근하길 바라는 프로그래머는 드물 것이다. 하지만 'toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자'는 원칙을 따른다는 관점에서, 비검사 예외라도 상세 정보를 알려주는 접근자 메서드를 제공하길 권한다. 33 | -------------------------------------------------------------------------------- /10장_예외/item76.md: -------------------------------------------------------------------------------- 1 | # Item 76. 가능한 한 실패 원자적으로 만들라 2 | 3 | 여기서 하는 얘기는 예외가 발생해도 해당하는 객체는 그 호출 전 상태를 유지해야 한다. 4 | 5 | 요것이 바로 실패 원자적이어야 한다는 것이다. 6 | 7 | ## 메소드를 실패 원자적으로 만드는 방법 8 | 9 | ### 불변객체로 설계하자 10 | 11 | 불변객체는 태생이 실패 원자적이다. 12 | 13 | 메소드가 실패하게 되면 원하는대로 동작할 수는 없으나, 그 객체 자체가 불안정해지는 일은 없다. 14 | 15 | 변하는게 없기 때문이다. 16 | 17 | ### 가변 객체의 메소드를 실패 원자적으로 만드는 방법 18 | 19 | 매개변수의 유효성을 전부 검사하는 것이다. 20 | 21 | ```java 22 | public class Wallet { 23 | private int money; // 편의상 int로 설정, 가변불변 둘다 포함이라 final은 안넣음. 24 | private final List cards; 25 | public Wallet(final int money) { 26 | this.money = validMoney(money); 27 | this.cards = new ArrayList<>(); 28 | } 29 | 30 | public Wallet addMoney(final int money) { 31 | validMoney(money); 32 | return new Wallet(this.money + money); 33 | } 34 | 35 | public void addMoney(final int money) { 36 | validMoney(money); 37 | this.money += money; 38 | } 39 | 40 | private void validMoney(final int money) { 41 | if (money < 0) { 42 | throw new IllegalArgumentException("돈이 음수값 일 수 없습니다."); 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### 실패할 가능성이 있다면 최전방에 배치하자 49 | 50 | 계산이 들어가는 로직보다 무조건적으로 앞에 배치하게 되면, 계산과정에서 예외가 나지 않을 것이다. 51 | 52 | > 개인적 의견이지만, 여기에 테스트 코드를 넣는게 가장 바람직 할것 같다. 53 | 54 | 되는 상황부터가 아닌 예외 상황부터 정의하면 코드가 단순해지는걸 많이 느꼈음. 55 | 56 | ### 임시 복사본에서 작업을 수행 후 원래 객체와 교체 57 | 58 | 데이터를 임시 자료구조에 저장해 작업하는게 더 빠른경우 적용. 59 | 60 | ex ) 리스트에 정렬이 필요하면, 정렬에선 배열이 훨씬 좋기에 배열로 복사해서 값을 정렬하는 경우 리스트에는 변화가 없다는 것. 61 | 62 | ### 작업도중 발생하는 실패를 가로채는 복구코드 작성 63 | 64 | 별도의 temp 변수들에 저장해두고 catch시 되돌리는 방법을 생각했다. 65 | 66 | 근데 책에서 설명하길 `디스크 기반의 내구성을 보장해야 하는 자료구조` 라고 나오기에 DB의 트랜잭션을 생각했다. 67 | 68 | ## 동시성에서의 실패 원자성 69 | 70 | 실패 원자성은 권장되지만 동시성에선 실패 원자성이 보장되지 못한다. 71 | 72 | 그래서 동시성의 예외인 `Concurrent` 종류의 예외를 만나도 제대로 잡았다고 보장할 수 없다. 73 | 74 | ## 무조건 실패 원자적으로 만들어야 하는 것은 아니다. 75 | 76 | 실패 원자성만을 위해 달리면 이를 위한 비용이나 복잡도가 아주 큰 연산도 존재한다. 77 | 78 | 그래도 원초적으로 문제가 무엇인지를 파악한다면 실패 원자성은 공짜로 얻을 수 있게된다. 79 | 80 | 메소드 명세에 기술했다면 예외가 발생해도 메소드 호출 전과 같은 상태로 있어야한다. 81 | 82 | 이 규칙을 지키지 못한다면 실패한 경우의 객체 상태를 자바독에 명시해주자. 83 | -------------------------------------------------------------------------------- /10장_예외/item77.md: -------------------------------------------------------------------------------- 1 | API 설계자가 메서드 선언에 예외를 선언하는 까닭은 **적절한 조치를 취해달라고 말하는 것**이다. 안타깝게도 예외를 무시하기란 아주 쉽다. 해당 메서드 호출을 try문으로 감싼 후 catch 블록에서 아무일도 하지 않으면 끝이다. 2 | 3 | ``` 4 | // catch를 비워두면 예외는 무시된다. 5 | try { 6 | ...... 7 | } catch(SomeException e) { 8 | // 아무일도 하지 않는다. 9 | } 10 | ``` 11 | 12 | **catch 블록을 비워두지 말자** 13 | 14 | catch 블록을 비워두면 예외가 존재할 이유가 사라진다. 비유하자면 화재경보를 무시하는 수준이 아니라 아예 꺼버려, 다른 누구도 화재가 발생했는지 모르게하는 것과 같다. 15 | 16 | 물론 예외를 무시해야 될 때도 있다. 대표적으로 FileInputStream을 닫을 때가 그렇다. 17 | FileInputStream은 입력 전용 스트림이므로 파일의 상태를 변경하지 않았으니 복구할 것이 없고, 스트림을 닫는다는 건 필요한 정보를 다 읽었다는 뜻이니 남은 작업을 중단할 이유도 없다. 18 | 혹시나 같은 예외가 자주 발생한다면 파일을 닫지 못했다는 사실을 로그로 남기는 것도 좋은 방법이다. 19 | 20 | 어쨌든 **예외를 무시하기로 했다면 catch 블록 안에 이유를 주석으로 남기고 예외 변수의 이름도 ignored로 변경**하도록 하자. 21 | 22 | ``` 23 | Future f = exec.submit(planarMap::chromaticNumber); 24 | int numColors = 4; // 기본값 25 | 26 | try { 27 | numColors = f.get(1L, TimeUnit.SECOND); 28 | } catch (TimeoutException | ExcutionException ignored) { 29 | // 기본값을 사용한다. 30 | } 31 | ``` 32 | 33 | 이번 절의 내용은 검사와 비검사 예외에 똑같이 적용된다. 34 | 빈 catch 블록을 그냥 지나치면 그 프로그램은 오류를 내재한 채 동작하게 된다. 35 | 36 | 예외를 적절히 처리하면 오류를 완전히 피할 수도 있다. 또, 무시하지 않고 바깥으로 전파되게만 놔둬도 최소한 디버깅 정보를 남긴 채 프로그램이 중단되게 할 수 있다. 37 | -------------------------------------------------------------------------------- /11장_동시성/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |--------------------------|-----|-----| 3 | | 아이템 | 발표자 | 질문자 | 4 | -------------------------------------------------------------------------------- /11장_동시성/item78.md: -------------------------------------------------------------------------------- 1 | ## 아이템 78 공유 중인 가변 데이터는 동기화해 사용해라 2 | 3 | synchronized 키워드는 해당 메서드나 블록을 한 번에 한 스레드씩 수행하도록 보장한다. 4 | 5 | 동기화는 일관된 상태를 가진 객체에 접근하는 메서드가 그 객체에 락을 걸고, 6 | 7 | 락을 건 메서드는 객체의 상태를 확인하고 필요하면 수정하여 객체를 하나의 일관된 상태에서 다른 일관된 상태로 변화시킨다. 8 | 9 | 즉, 동기화를 제대로 사용한다면 어떤 객체도 일관되지 않는 상황을 볼 수 없을 것이다. 10 | 11 | ```java 12 | public class StopThread { 13 | 14 | private static boolean stopRequested; 15 | 16 | public static void main(String[] args) { 17 | Thread backgroundThread = new Thread(() -> { 18 | int i = 0; 19 | while (!stopRequested) { 20 | i++; 21 | } 22 | }); 23 | backgroukndTrehad.start(); 24 | 25 | TimeUnit.SECONDS.sleep(1); 26 | stopRequested = true; 27 | } 28 | } 29 | ``` 30 | 31 | 위 코드는 `stopRequested` 를 `false`로 시작 하고 다른 스레드가 `true` 로 변경 하면 반복문을 빠져나오는 코드이다. 32 | 33 | 하지만 슬프게도 위 코드는 여원히 수행 되게 된다. 34 | 35 | 그 이유는 동기화 때문이다. 36 | 37 | 메인스레드가 수정한 값을 `backgroukndTrehad` 가 언제쯤 보게 될지 보증 할 수 없다. 38 | 39 | 동기화에 빠지면 가상 머신은 다음과 같이 끌어올리기 (hoisting)라는 최적화 기법을 수행하게 된다. 40 | 41 | ```java 42 | //원래 코드 43 | while (!stopRequested) i++; 44 | ``` 45 | 46 | 47 | ```java 48 | // 최적화한 코드 49 | if (!stopRequested) { 50 | while(true) i++; 51 | } 52 | ``` 53 | 54 | 이 결과 프로그램은 응답 불가 상태가 되어 더이상 진전이 없다. 이 문제를 해결 하기 위해서는 55 | 56 | `stopRequested` 필드를 동기화 해 접근하면 해결이 가능하다 57 | 58 | ```java 59 | public class StopThread{ 60 | private static boolean stopRequested; 61 | 62 | private static synchronized void requestedStop(){ 63 | stopRequested = true; 64 | } 65 | 66 | private static synchronized boolean stopRequested(){ 67 | return stopRequested; 68 | } 69 | 70 | public static void main(String[] args) throws InterruptedException{ 71 | Thread backgroundThread = new Thread(() -> { 72 | int i=0; 73 | while(!stopRequested()){ 74 | i++; 75 | } 76 | }) 77 | backgroundThread.start(); 78 | 79 | TimeUnit.SECONDS.sleep(1); 80 | requestedStop(); 81 | } 82 | } 83 | ``` 84 | 85 | 쓰기 메서드와 읽기 메서드 모두 동기화 했음을 주목 해야한다. 86 | 87 | 쓰기만 동기화 하면 안되고, 쓰기와 읽기 모두 동기화하지 않으면 동작을 보장하지 않는다. 88 | 89 | 90 | ## volatile 91 | `volatile` 한정자는 배타적 수행과는 상관없지만 항상 최근에 기록된 값을 읽는 것을 보장해주는 역할을 한다. 92 | 93 | 따라서 위와 같은 상황에서 사용할 수 있는 한정자다. 94 | 95 | `volatile`은 매번 동기화하는 비용이 그렇게 크지 않고, 속도가 더 빠른 대안이 된다. 96 | 97 | 98 | ```java 99 | // volatile를 적용한 예시 100 | public class StopThread{ 101 | private static volatile boolean stopRequested; 102 | 103 | public static void main(String[] args) { 104 | Thread backgroundThread = new Thread(() -> { 105 | int i=0; 106 | while(!stopThread){ 107 | i++; 108 | } 109 | }) 110 | backgroundThread.start(); 111 | 112 | TimeUnit.SECONDS.sleep(1); 113 | stopRequested = true; 114 | } 115 | } 116 | ``` 117 | 118 | 하지만 이런 `volatile`는 조심해서 사용해야한다 119 | #### volatile 주의사항 120 | ```java 121 | private static volatile int nextSerialNumber = 0; 122 | public static int generateSerialNumber(){ 123 | return nextSerialNumber++; 124 | } 125 | ``` 126 | 이 메서드는 매번 고유한 값을 반환할 의도로 만들어졌다. 127 | 128 | nextSerialNumber 라는 값은 원자적으로 접근할 수 있고, 129 | 130 | 어떤 값이든 허용하기 때문에 동기화없이도 불변식을 보호할 수 있어 보인다. 131 | 132 | 하지만 동기화 없이 올바로 작동하지 않는다. 133 | 134 | 증가 연산자(++)는 코드상으로 하나지만 실제로는 nextSerialNumber 135 | 136 | 1) 필드에 값을 읽을 때 한번 137 | 2) 그 다음 새로운 값을 저장할 때 한번 138 | 139 | 총 두번 접근하게 된다. 140 | 141 | 만약 다른 스레드가 이 두 접근 사이를 비집고 들어와서 값을 읽어간다면 두 스레드는 동일한 값을 읽어가게 된다. 142 | 143 | 즉 오류가 발생하게 되고 이런 문제를 안전 실패(safety failure)라고 한다. 144 | 145 | 이는 `generateSerialNumber()` 메서드에 `synchronized`를 붙임으로써 해결할 수 있다. 146 | 147 | 동시에 호출해도 서로 간섭하지 않아 이전 호출이 변경한 값을 읽게 된다. 148 | 149 | 다만 메서드에 `synchronized`를 붙였다면 `volatile` 한정자는 제거해야 한다. 150 | 151 | 152 | 153 | #### AtomicLong 154 | 155 | `volatile`는 동기화의 두 효과 중 통신만 지원하지만 이 패키지는 배타적 실행 가지 지원을 한다. 156 | 그뿐만 아니라 성능도 더 우수하다. 157 | 158 | java.util.concurrent.atomic 을 이용한 락-프리 동기화 159 | ```java 160 | private static final AtomicLong nextSerialNum = new AtomicLong(); 161 | 162 | public static long generateSerialNumber() { 163 | return nextSerialNum.getAndIncrement(); 164 | } 165 | ``` 166 | 동기화로 인한 문제를 피하는 최고의 방법은 가변 데이터를 공유하지 않는 것이다. 167 | 168 | 가변 데이터는 단일 스레드에서만 쓰는것이 좋다. 169 | 170 | 한 스레드가 데이터를 다 수정하고 다른 스레드에 공유할 때는 해당 객체에서 공유하는 부분만 동기화해도 된다. 171 | 172 | 그러면 그 객체를 다시 수정할 일이 생기기 전까지는 173 | 174 | 다른 스레드들은 동기화없이 자유롭게 값을 읽어갈 수 있기 때문이다. 175 | 176 | 이런 객체를 불변 이라 하고 다른 스레드에 이런 객체를 건네는 행위를 안전 발행(safe publication)이라고 한다. 177 | -------------------------------------------------------------------------------- /11장_동시성/item81.md: -------------------------------------------------------------------------------- 1 | # 아이템81. wait와 notify보다는 동시성 유틸리티를 애용하라 2 | 3 | wait과 notify를 올바르게 사용하는것은 아주 까다롭다. 4 | 5 | 고수준 동시성 유틸리티를 사용하자 (ex: `java.util.concurrent`) 6 | 7 | ## 1. java.util.concurrent 패키지 8 | 9 | `java.util.concurrent`의 고수준 유틸리티는 세 범주로 나눌 수 있다. 10 | 11 | 1. 실행자 프레임워크(executor framework) - *아이템80* 12 | 2. 동시성 컬렉션(concurrent collection) 13 | 3. 동기화 장치(synchronizer) 14 | 15 | ## 2. 동시성 컬렉션 16 | 17 | - List, Queue, Map과 같은 표준 컬렉션 인터페이스에 동시성을 가미한 고성능 컬렉션이다. 18 | - 내부적으로 동기화를 각자의 내부에서 수행한다. - *아이템79* 19 | 20 | **따라서, 동시성 컬렉션에서 동시성을 무력화하는건 불가능하며, 외부에서 락을 추가로 사용하면 오히려 속도가 느려진다.** 21 | 22 | - 동시성을 무력화하지 못하기에 여러 메서드가 원자적으로 묶여 호출하지는 못하고, 여러 기본 동작을 하나의 원자적 동작으로 묶는 ‘**상태 의존적 수정**’ 메서드들이 추가되었다. 23 | - 예시) `Map`의 `putIfAbsent(key, value)` 메서드 24 | 25 | ```java 26 | // Map의 putIfAbsent 메서드 27 | default V putIfAbsent(K key, V value) { 28 | V v = get(key); 29 | if (v == null) { // key에 매핑된 값이 없을 때만 put 30 | v = put(key, value); 31 | } 32 | 33 | return v; 34 | } 35 | ``` 36 | 37 | - *이 메서드 덕에 스레드 안전한 정규화 맵(canonicalization map)을 쉽게 구현* 38 | - 자바 8에서는 일반 컬렉션 인터페이스에도 디폴트 메서드 형태로 추가 됨 39 | 40 | ### 2-1. String.intern() 구현하기 41 | 42 | intern() 메서드는 String 비교에서 == 연산자의 사용이 필요한 경우 활용할 수 있다. 43 | 44 | ```java 45 | String a = "apple"; 46 | String b = new String("apple"); 47 | String c = b.intern() 48 | 49 | System.out.println(a == b); // false 50 | System.out.prrintln(a == c); // true 51 | ``` 52 | 53 | - a와 b의 비교는 앞서 예시처럼 참조하는 위치가 다르므로 false를 반환 54 | - b와 c는 intern()을 통해 생성하여 ==을 true로 반환 55 | - **intern() 메서드는 String pool에서 리터럴 문자열이 이미 존재하는지 체크하고 존재하면 해당 문자열을 반환하고, 아니면 리터럴을 String pool에 넣어줌** 56 | 57 | **2-1-1. ConcurrentMap으로 구현한 동시성 정규화 맵 - 최적은 아니다** 58 | 59 | ```java 60 | private static final ConcurrentMap map = new ConcurrentHashMap<>(); 61 | 62 | public static String intern(String s) { 63 | String previousValue = map.putIfAbsent(s, s); 64 | return previousValue == null ? s : previousValue; 65 | } 66 | ``` 67 | 68 | - `ConcurrentHashMap` 은 get 같은 검색 기능에 최적화되었다. 69 | - 따라서 get을 먼저 호출하여 필요할 때만 `putIfAbsent`를 호출하면 더 빠르다. 70 | 71 | **2-1-2. ConcurrentMap으로 구현한 동시성 정규화 맵 - 더 빠르다** 72 | 73 | ```java 74 | public static String intern(String s) { 75 | String result = map.get(s); 76 | if (result == null) { 77 | result = map.putIfAbsent(s, s); 78 | if (result == null) 79 | result = s; 80 | } 81 | return result; 82 | } 83 | ``` 84 | 85 | - `ConcurrentHashMap`은 동시성이 뛰어나며 속도도 무척 빠르다. 86 | - ~~저자의 로컬에서 String.intern보다 6배 빠르다고 함~~ 87 | - `String.intern()`에는 오래 실행되는 프로그램에서 메모리 누수를 방지하는 기술도 들어가 있음을 감안하자. 88 | 89 | **2-1-3. 동시성 컬렉션 vs 동기화한 컬렉션** 90 | 91 | - 동시성 컬렉션은 동기화한 컬렉션을 옛날 레거시로 만들어 버렸다. 92 | - 대표적으로 이제는 `Collections.synchronizedMap` 보다는 `ConcurrentHashMap` 을 사용하자 93 | - 동기화된 Map을 동시성 Map으로 교체하는 것만으로 동시성 애플리케이션의 성능은 극적으로 개선 된다. 94 | 95 | > 동기화 컬렉션보다 안전과 속도 모두 개선되었기에 동시성 컬렉션을 사용하자 96 | 97 | ## 3. 동기화 장치 98 | 99 | > 스레드가 다른 스레드를 기다릴 수 있게 함 100 | 101 | - 컬렉션 인터페이스 중 일부는 작업이 성공적으로 완료될 때까지 기다리도록 확장 됨 102 | - 예를들어, Queue를 확장한 `BlockingQueue`에 추가된 `take` 메서드가 있음 103 | - 큐의 첫 번째 원소를 꺼냄 104 | - 만약 큐가 비었다면 새로운 원소가 추가될 때까지 기다린다. 105 | - 이런 특성 덕에 `BlockingQueue`는 작업 큐(생산자-소비자 큐)로 쓰기 적당하고 `ThreadPoolExecutor`나 실행자 서비스*(아이템 80)* 구현체에서 `BlockingQueue`를 사용한다. 106 | 107 | 그 밖에 또 자주 사용되는 동기화 장치로 다음과 같은 장치들이 있다. 108 | 109 | ### 3-1. 동기화 장치의 종류 110 | 111 | 동기화 장치는 스레드가 다른 스레드를 기다릴 수 있게 하여, 서로 작업을 조율할 수 있게 해줌 112 | 113 | - CountDownLatch 114 | - Semaphore 115 | - CyclicBarrier 116 | - Exchanger 117 | - Phaser 118 | 119 | **자주 쓰이는 CountDownLatch** 120 | 121 | - 일회성 장벽으로, 하나 이상의 스레드가 또 다른 하나 이상의 스레드 작업이 끝날 때까지 기다리게 한다. 122 | - CountDownLatch의 유일한 생성자는 int 값을 받으며, 이 값이 래치의 countDown 메서드를 몇 번 호출해야 대기 중인 스레드를 깨우는지 결정한다. 123 | 124 | ```java 125 | // 동시 실행 시간을 재는 간단한 프레임워크 126 | public static long time(Executor executor, int concurrency, Runnable action) 127 | throws InterruptedException { 128 | 129 | CountDownLatch ready = new CountDownLatch(concurrency); 130 | CountDownLatch start = new CountDownLatch(1); 131 | CountDownLatch done = new CountDownLatch(concurrency); 132 | 133 | for (int i = 0; i < concurrency; i++) { 134 | executor.execute(() -> { 135 | ready.countDown(); // 준비 완료 136 | try { 137 | start.await(); // 모든 작업자 스레드가 준비되길 기다림 138 | action.run(); 139 | } catch (InterruptedException e) { 140 | Thread.currentThread().interrupt(); 141 | } finally { 142 | done.countDown(); // 타이머에게 작업 마침을 알림 143 | } 144 | }); 145 | } 146 | 147 | ready.await(); // 모든 작업자가 준비되길 기다림 148 | long startNanos = System.nanoTime(); 149 | start.countDown(); // 작업자 깨움 150 | done.await(); // 모든 작업자가 일을 마치기를 기다림 151 | return System.nanoTime() - startNanos; 152 | } 153 | ``` 154 | 155 | **코드 동작 설명** 156 | 157 | - 총 3개의 래치로 구성되어있다. 158 | - ready : 작업자 스레드들이 준비가 완료됐음을 타이머 스레드에 통지할 때 사용 159 | - start : 통지를 끝낸 작업자 스레드들이 start가 열리기를 기다림 160 | - done : 타이머에게 작업을 마쳤음을 알릴 때 사용 161 | 162 | 1. `ready.countDown`으로 각 작업자는 준비 완료를 알림 163 | 2. 모든 작업자가 준비 완료되면 `ready.await` 대기 종료 164 | 3. 타이머 스레드가 시작 시각을 기록(`startNanos 변수`) 165 | 4. `start.countDown`을 호출하여 기다리던 작업자 스레드들을 깨움 166 | 5. 마지막 남은 작업자 스레드가 종료 `start.await` 호출 - `start.countDown` 대기 종료 167 | 6. `done.countDown`호출하여 `done.await()`대기 종료 168 | 7. 타이머 스레드는 `done` 래치 깨어난 직후 종료 시각을 기록 169 | 170 | **스레드 기아 교착상태(Thread starvation deadlock) 발생 가능** 171 | 172 | - 위와 같은 메서드에 넘겨진 실행자는 concurrency 매개변수로 동시성 수준 만큼의 스레드를 생성할 수 있어야 한다. 173 | - 그렇지 못하면 스레드 생성을 하지 못하고 이 메서드는 끝나지 않는다. 174 | - 이런 상태를`스레드 기아 교착상태`라 한다. 175 | 176 | > **참고** 177 | > 시간 간격을 잴 때는 System.nanoTime을 사용하자. 178 | > System.currentTimeMillis보다 더 정확하고 정밀하며, 시스템의 실시간 시계의 시간 보정에 영향을 받지 않는다. 179 | 180 | ## 4. 정리 181 | 182 | - 코드를 새로 작성한다면 `wait`과 `notify`를 쓸 이유가 거의(어쩌면 전혀) 없다. 183 | - 하지만 어쩔 수 없이 레거시 코드를 다뤄야 할 때도 있을 것이다. 184 | - `wait`을 사용하는 표준 방법은 아래와 같다. 185 | - **`wait` 메서드를 사용할 때는 반드시 반복문 안에서 사용하도록 해야한다. 반복문으로 실행가능 조건을 검사함으로써 불필요한 `wait` 호출을 막을 수 있다.** 186 | - 만약 반복문 밖에 `wait`이 존재한다면 `wait`한 스레드를 언제 다시 `notify` 할지 보장할 수 없다. 187 | - 대기 후에 조건을 검사하여 조건이 충족되지 않았다면 다시 대기하게 하는 것은 안전실패를 방지하는 조치다. 만약 조건이 충족되지 않았는데 스레드가 동작을 이어가면 락이 보호하는 불변식을 깨뜨릴 위험이 있다. 188 | 1. `wait` 이후에 깨어나는 사이 다른 스레드가 락을 얻고 동기화 블럭 안의 상태를 변경할 수도 있음 189 | 2. 조건이 만족되지 않았는데 악의적인 스레드가 `notifyAll`을 호출할 수도 있음 190 | 3. 대기중인 스레드가 `notify` 없이도 깨어나는 경우가 드물게 있을 수 있음 191 | 192 | ```java 193 | synchronized (obj) { 194 | while(조건이 충족되지 않았다) { 195 | obj.wait(); // (락을 놓고, 깨어나면 다시 잡는다.) 196 | } 197 | ... // 조건이 충족됐을 때의 동작을 수행한다. 198 | } 199 | ``` 200 | 201 | - `notify`와 `notifyAll` 두개 중 어떤 것을 사용할지에 대한 문제도 존재하지만 `notifyAll`을 사용하는 것이 더 합리적이고 안전하다. 202 | - 깨어나야 하는 모든 스레드가 깨어남을 보장하기 때문에 항상 정확한 결과를 얻을 것이다. 203 | - 다른 스레드까지 깨어날 수 있지만 우리의 프로그램 정확성에는 영향을 주지 않을 것이다. 깨어난 스레드들은 기다리던 조건이 충족되었는지 확인하여 충족되지 않았다면 다시 대기상태로 돌아갈 것이기 때문이다. 204 | 205 | - 만약 이들을 사용하는 레거시 코드를 유지보수를 해야한다면 `wait`은 항상 표준 관용구에 따라 `while`(반복문)안에서 호출하도록 하자. 206 | - 일반적으로 `notify`보다는 `notifyAll`을 사용해야 한다. 혹시라도 `notify`를 사용한다면 응답 불가 상태에 빠지지 않도록 각별히 주의하자. 207 | 208 | > 새로운 코드라면 언제나 wait와 notify가 아닌 동시성 유틸리티를 사용하자 -------------------------------------------------------------------------------- /11장_동시성/item82.md: -------------------------------------------------------------------------------- 1 | # [Item82] 스레드 안전성 수준을 문서화하라 2 | 3 | 4 | 스레드 안전성(Thread-safe) 수준을 문서화 하지 않으면 클래스 사용자는 나름의 가정을 해야만 한다. 5 | 6 | 만약 그 가정이 틀리면 클라이언트 프로그램은 동기화를 충분히 하지 못하거나 지나치게 한 상태일 것이며 이 경우 모두 심각한 오류로 이어질 수 있다. 7 | 8 | ### synchronized 한정자를 선언했는데? 9 | 10 | 소스 코드에 synchronized 한정자를 사용했다고 하더라도 이는 구현상에서만 쓰일 뿐 JavaDoc 기본 옵션에서 생성한 API 문서에는 드러나지 않는다 11 | 12 | **즉, synchronized 한정자를 선언하였다고 해서 Thread-safe함을 문서화 한 것이 아니다!** 13 | 14 | ### 스레드 안전성에도 수준이 나뉜다 15 | 16 | **멀티스레드 환경에서도 API를 안전하게 사용하게 하려면 클래스가 지원하는 스레드 안전성 수준을 전확히 명시해야 한다.** 17 | 18 | 다음은 스레드 안전성이 높은 순으로 나열한것이다. 19 | 20 | (**스레드 안전성 어노테이션과 유사한 것을 `≈` 통해 표현)** 21 | 22 | - **불변(immutable) ≈** @Immutable 23 | : 이 클래스의 인스턴스는 마치 상수와 같아서 외부 동기화가 필요 없다 (ex. String, Long, BigInteger) 24 | - **무조건적 스레드 안전(unconditionally thread-safe) ≈** @ThreadSafe 25 | : 우리가 흔히 얘기하는 thread-safe한 클래스로 별도의 동기화 없이 동시에 사용해도 안전하다 (ex. AtomicLong, ConcurrentHashMap) 26 | 27 | ![ConcurrentHashMap의 클래스 상단 주석](https://user-images.githubusercontent.com/68587990/209437057-af00f800-8ed4-4841-a858-93a2fcee2d10.png) 28 | 29 | ConcurrentHashMap의 클래스 상단 주석 30 | 31 | ![ConcurrentLinkedHashMap의 @ThreadSafe 어노테이션](https://user-images.githubusercontent.com/68587990/209437071-c1e854c8-f0c8-4720-9f6c-78d61d79ea59.png) 32 | 33 | ConcurrentLinkedHashMap의 @ThreadSafe 어노테이션 34 | 35 | - **조건부 스레드 안전(conditionally thread-safe) ≈** @ThreadSafe 36 | : 전반적으로는 thread-safe 하지만 일부 메서드가 동기화가 필요한 경우 (ex. Collections.synchronized 래퍼 메서드가 반환한 컬렉션들) 37 | 38 | ![Collections.java의 synchronizedSet을 반환하는 synchronizedSet 메서드의 경우 외부 동기화의 예시를 주석으로 남기고 있다](https://user-images.githubusercontent.com/68587990/209437403-0c454647-e050-4f8f-99e3-5a56ba47e642.png) 39 | 40 | Collections.java의 synchronizedSet을 반환하는 synchronizedSet 메서드의 경우 외부 동기화의 예시를 주석으로 남기고 있다 41 | 42 | - **스레드 안전하지 않음(not thread-safe) ≈** @NotThreadSafe 43 | : 동시에 사용하려면 각각의 메서드 호출을 클라이언트가 외부 동기화 메커니즘으로 감싸야 하는 경우 (ex. ArrayList, HashMap 등) 44 | 45 | ![클래스 상단 주석으로 not synchronized라고 명시하고 있는 ArrayList 클래스](https://user-images.githubusercontent.com/68587990/209437472-769c858a-b65b-43d3-a6b9-e43a00dc2010.png) 46 | 47 | 클래스 상단 주석으로 not synchronized라고 명시하고 있는 ArrayList 클래스 48 | 49 | ![@NotThreadSafe 어노테이션이 명시된 LinkedDeque 클래스](https://user-images.githubusercontent.com/68587990/209437474-00ac7acc-16e6-4090-a6e5-f08ce917e96f.png) 50 | 51 | @NotThreadSafe 어노테이션이 명시된 LinkedDeque 클래스 52 | 53 | - **스레드 적대적(thread-hostile) ≈** @NotThreadSafe (@NotThreadSafe으로 명시해도 되지않을까?) 54 | : 외부 동기화 매커니즘을 사용하더라도 안전하지 않은 경우. 이 경우 문제를 고쳐 재배포하거나 deprecated API로 지정할 필요가 있다 55 | 56 | 클래스의 스레드 안전성은 57 | 58 | - 보통 클래스의 문서화 주석에 기재한다 59 | - 독특한 특성의 메서드라면 해당 메서드 주석으로 기재한다 60 | - 열거 타입은 굳이 불변(혹은 @Immutable)이라고 쓰지 않아도 된다 61 | - 반환 타입만으로는 명확히 알 수 없는 정적 팩터리라면 자신이 반환하는 객체의 스레드 안정성을 반드시 문서화 해야한다(Collections.synchronized 래퍼 메서드 처럼) 62 | 63 | ### 비공개 락 객체 관용구를 사용하자 64 | 65 | 클래스가 외부에서 사용할 수 있는 락(public lock instance)을 제공하면 클라이언트 측에서 유연하게 클래스 메서드를 사용할 순 있지만, 66 | 67 | ConcurrentHashMap과 같이 내부에서 복잡하게 고성능 동시성 제어 메커니즘을 사용하고 있는 경우 혼용해서 쓸 수 없다. (가능하더라도 매우매우 복잡하고 번거로울듯 하다) 68 | 69 | 또한, 공개된 락을 오래 쥐고 놓지 않는 서비스 거부 공격(deial-of-service attack)을 수행할 수도 있다 70 | 71 | 따라서 synchronized 메서드 대신, 그리고 공개 락 객체를 사용하는 대신 비공개 락 객체를 사용하자 72 | 73 | ```java 74 | // 비공개 락 객체 관용구 75 | private final Object lock = new Object(); 76 | 77 | public void foo() { 78 | synchronized(lock) { 79 | // ... 80 | } 81 | } 82 | ``` 83 | 84 | > **우연히라도 락 객체가 교체되는 일을 예방하기 위해서 항상 lock 필드는 final로 선언하자** 85 | > 86 | 87 | ### 비공개 락 객체는 상속용으로 설계한 클래스에 적합하다 88 | 89 | 상속용 클래스에서 자신의 인스턴스를 락으로 사용한다면, ‘서로가 서로를 훼방놓는’ 상태에 빠져 의도치 않은 결과를 낳게 된다 90 | 91 | ```java 92 | /** 93 | * Bloch05, puzzle 77 94 | */ 95 | @Slf4j 96 | public class Worker extends Thread { 97 | private volatile boolean quittingTime = false; 98 | 99 | public void run() { 100 | while (!quittingTime) { 101 | pretendToWork(); 102 | } 103 | log.info("end to work!"); 104 | } 105 | 106 | private void pretendToWork() { 107 | try { 108 | log.info("working!"); 109 | Thread.sleep(300); 110 | } catch (InterruptedException e) { 111 | } 112 | } 113 | 114 | synchronized void quit() throws InterruptedException { 115 | log.info("start quit()"); 116 | quittingTime = true; 117 | join(); 118 | log.info("end quit()"); 119 | } 120 | 121 | synchronized void keepWorking() { 122 | log.info("start keepWorking()"); 123 | quittingTime = false; 124 | log.info("end keepWorking()"); 125 | } 126 | 127 | public static void main(String[] args) throws InterruptedException { 128 | final Worker worker = new Worker(); 129 | worker.start(); 130 | 131 | Timer t = new Timer(true); // Daemon thread 132 | t.schedule(new TimerTask() { 133 | public void run() { 134 | worker.keepWorking(); 135 | } 136 | }, 500); 137 | 138 | Thread.sleep(400); 139 | worker.quit(); 140 | } 141 | } 142 | ``` 143 | 144 | - **예상 실행 결과** 145 | 146 | ![https://user-images.githubusercontent.com/68587990/209667128-2b1f844c-012b-4af4-92d6-1710ef7e890c.png](https://user-images.githubusercontent.com/68587990/209667128-2b1f844c-012b-4af4-92d6-1710ef7e890c.png) 147 | 148 | - **실제 실행 결과** 149 | 150 | ![https://user-images.githubusercontent.com/68587990/209667137-19778867-b926-4c6f-b1a1-299b8c3329a4.png](https://user-images.githubusercontent.com/68587990/209667137-19778867-b926-4c6f-b1a1-299b8c3329a4.png) 151 | 152 | ```java 153 | 17:30:46.777 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 154 | 17:30:47.084 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 155 | 17:30:47.181 [main] INFO me.whiteship.chapter11.item82.Worker - start quit() 156 | 17:30:47.278 [Timer-0] INFO me.whiteship.chapter11.item82.Worker - start keepWorking() 157 | 17:30:47.278 [Timer-0] INFO me.whiteship.chapter11.item82.Worker - end keepWorking() 158 | 17:30:47.389 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 159 | 17:30:47.694 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 160 | 17:30:48.000 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 161 | 17:30:48.301 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 162 | 17:30:48.604 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 163 | 17:30:48.910 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 164 | 17:30:49.215 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 165 | 17:30:49.521 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 166 | 17:30:49.826 [Thread-0] INFO me.whiteship.chapter11.item82.Worker - working! 167 | ... 168 | ``` 169 | 170 | **따라서 이러한 경우를 막기 위해서 별도의 비공개 락 인스턴스를 만들어, 명시적 잠금을 사용해야한다** 171 | 172 | > 명시적 잠금을 사용하는 BetterWorker에 대해서 알고 싶으면 [Bloach05 Puzzle 77 에 대해서 설명한 블로그 글](https://www.javaspecialists.eu/archive/Issue144-Book-Review-Java-Puzzlers.html) 을 참고 하면 된다. 위 Worker 예시도 해당 블로그를 참고하여 조금 변형하였다 173 | > 174 | 175 | ### 핵심 정리 176 | 177 | - 모든 클래스가 자신의 스레드 안전성 정보를 명확히 문서화 해야한다 178 | - 문서화를 할 땐 명확한 언어로 주석 설명을 하거나, 어노테이션을 사용하자 179 | - synchronized 한정자로는 스레드 안전성 문서화와는 관련이 없다 180 | - 조건부 스레드 안전 클래스는 메소드 주석을 통해 어떤 순서로 호출해야하는지, 어떻게 락을 얻어야하는지 명확히 설명하자 181 | - 무조건적 스레드 안전 클래스를 작성할 때는 synchronized 한정자가 아닌 비공개 락 객체를 사용하자(명시적 잠금) 182 | -------------------------------------------------------------------------------- /12장_직렬화/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |--------------------------|-----|-----| 3 | | 아이템 | 발표자 | 질문자 | 4 | -------------------------------------------------------------------------------- /12장_직렬화/item86.md: -------------------------------------------------------------------------------- 1 | ## 아이템86 Serializable을 구현할지는 신중히 결정하라 2 | 3 | ### Serializable 4 | 5 | 클래스의 인스턴스를 직렬화 할 수 있게 하려면 클래스 선언에 `implements Serializable` 를 추가하면된다. 6 | 7 | ```java 8 | // 직렬화 선언 예시 9 | public class User implements Serializable { 10 | private final String userId; 11 | 12 | public User(String userId) { 13 | this.userId = userId; 14 | } 15 | } 16 | ``` 17 | 18 | 정말 쉽게 직렬화 적용이 가능하다. 19 | 20 | 하지만 알고보면 이는 매우 신중하게 선택해서 Serializable 를 구현할지 생각해야한다. 21 | 22 | ### Serializable을 구현하면 릴리스한 뒤에는 수정하기 어렵다. 23 | 24 | 클래스가 Serializable을 구현하면 직렬화된 바이트 스트림 인코딩(직렬화형태) 도 하나의 공개 API가 된다. 25 | 26 | 그래서 이 클래스가 널리 퍼진다면 그 직렬화 형태도 계속해서 지원해야한다. 27 | 28 | 즉 클래스의 private 과 package-private 인스턴스 필드마저 API로 공개하는 꼴이 된다. 29 | 30 | 뒤늦게 클래스 내부 구현을 손보면 원래의 직렬화 형태와 달라지게 된다. 31 | 32 | 한쪽은 구버전 인스턴스를 직렬화하고 다른 쪽은 신버전 클래스로 역질렬화한다면 오류가 발생하게 된다. 33 | 34 | 직렬화 예제 35 | 36 | ```java 37 | public class SerialTest { 38 | void serializable() { 39 | User user = new User("sawhong"); 40 | byte[] serializedMember; 41 | 42 | try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 43 | try (ObjectOutputStream outPutStream = new ObjectOutputStream(stream)) { 44 | outPutStream.writeObject(user); 45 | serializedMember = stream.toByteArray(); 46 | } 47 | } 48 | System.out.println(Base64.getEncoder().encodeToString(serializedMember)); 49 | } 50 | } 51 | ``` 52 | 위 코드를 돌리게 되면 `rO0ABXNyAARVc2Vy38q4XzPAJHQCAAFMAAZ1c2VySWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAB3Nhd2hvbmc=` 다음과 같은 코드를 얻게 된다 53 | 54 | 그리고 이를 역직렬화 하게 된다면 55 | 56 | ```java 57 | public class SerialTest { 58 | void deserializable() throws IOException, ClassNotFoundException { 59 | String serialize = "rO0ABXNyAARVc2Vy51MQda07LpMCAAFMAAZ1c2VySWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAB3Nhd2hvbmc="; 60 | byte[] decode = Base64.getDecoder().decode(serialize); 61 | User user; 62 | try (ByteArrayInputStream bais = new ByteArrayInputStream(decode)) { 63 | try (ObjectInputStream ois = new ObjectInputStream(bais)) { 64 | Object objectMember = ois.readObject(); 65 | user = (User) objectMember; 66 | } 67 | } 68 | 69 | // sawhong 이 나오게됨 70 | System.out.println(user.getUserId()); 71 | } 72 | } 73 | ``` 74 | 75 | 기존 코드를 수정하기 전까지는 직렬화한 데이터를 역직렬화하면 기존 원하던 데이터를 그대로 받을 수 있다. 76 | 77 | 하지만! 78 | 79 | user 클래스에 새로운 값을 추가하거나 하면 바로 역직렬화 할 수 없다. 80 | 81 | 82 | 스크린샷 2023-01-03 오후 8 34 09 83 | 84 | 85 | 해당 오류가 발생하는 이유는 직렬화된 클래스는 serialVersionUID 를 통해서 고유 식별 번호를 부여 받게된다. 86 | 87 | 하지만 serialVersionUID 를 클래스내에 static fianl long 필드로 명시하지 않으면 시스템이 런타임에 암호해시 함수(SHA-1) 을 적용해 자동으로 클래스 안에 생성한다. 88 | 89 | (우리의 예제가 바로 적용하지 않은 상황) 90 | 91 | 그래서 나중에 클래스를 수정하게 된다면 serialVersionUID 값도 변하게된다. 그래서 오류가 발생하게 된다. 92 | 93 | 해결을 위해서는 serialVersionUID 을 갖도록 하면된다. 94 | 95 | 96 | ### 버그와 보안 구멍이 생길 위험이 높아진다. 97 | 98 | 객체는 생성자를 사용해 만드는것이 기본이다. 하지만 기본 역직렬화를 사용하면 불변식 깨짐과 허가되지 않는 접근에 쉽게 노출된다. 99 | 100 | why? 역직렬화는 숨은 생성자인데 생성자가 만족해야 하는 불변식이나 객체 생성중 객체 내부에 공격자가 접근 할 수 없도록 해야하는것을 잊기 쉽다. 101 | 102 | ```java 103 | public class User implements Serializable { 104 | 105 | private final String userId; 106 | 107 | public User(String userId) { 108 | this.userId = userId; 109 | 110 | if (userId.equals("TEST")) { 111 | throw new IllegalArgumentException("테스트 계정은 회원가입 할 수 없습니다."); 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | userId에 TEST라는 값을 넣게 되면 오류가 발생하는것이 당연하지만, 역직렬화를 통한 객체 생성은 해당 생성자를 무시하고, 생성하게 된다. 118 | 119 | ### 해당 클래스의 신버전을 릴리스할 때 테스트 할 것이 늘어난다. 120 | 121 | 직렬화 가능 클래스가 수정되면 신버전 인스턴스를 직렬화 하여 구버전으로 역직렬화 가능한지 테스트 해봐야한다. 122 | 123 | 따라서 매번 직렬화 가능 클래스가 수정되면 테스트를 진행해야하기 때문에 매우 귀찮고 힘들게 된다. 124 | 125 | 126 | ### Serializable 구현 여부는 가볍게 결정할 사안이 아니다. 127 | 128 | 객체를 전송하거나 저장할때 자바 직렬화를 이용하는 프레임 워크용으로 만든 클래스라면 선택의 여지는 없다. 129 | 130 | Serializable 을 반드시 구현해야 하는 다른 클래스의 컴포넌트로 쓰일 클래스도 마찬가지다. 131 | 132 | 하지만 이를 구현하는데 따르는 비용이 적지 않으니, 클래스를 설계할 때마다 득과실을 잘 비교해서 사용해야한다. 133 | 134 | ### 상속용으로 설계된 클래스는 대부분 Serializable을 구현하면 안되며, 인터페이스도 대부분 Serializable을 확장해서는 안된다. 135 | 136 | 상속용으로 설계된 클래스나 인터페이스가 Serializable를 확장해서 안되는 이유는, 구현체 들에게 위의 문제점을 모두 주기 때문이다. 137 | 138 | 그러나 Serializable 을 구현한 클래스만 지원하는 프레임워크를 사용하는 경우에는 위 규칙을 지키지 못하는 경우도 있다 139 | 140 | ex) throwable, component 141 | 142 | 143 | ### Serializable을 구현하지 않기로 결정했다면? 144 | 145 | Serializable을 구현하지 않을 때는 한 가지만 주의하면 된다. 146 | 147 | 상속용 클래스에서 직렬화를 지원하지 않지만, 하위 클래스에서 직렬화를 지원하려 한다면 부담이 늘어난다. 148 | 149 | 이런 클래스를 역직렬화하려면 상위 클래스는 매개변수가 없는 생성자를 제공해야 한다. 왜냐하면 자식 클래스 인스턴스를 직렬화 할때, 부모 클래스의 기본 생성자가 150 | 자동으로 호출 되기 때문이다. 151 | 152 | 153 | 이런생성자가 제공되지 않으면 하위 클래스는 어쩔수 없이 직렬화 프록시 패턴을 사용해야한다. 154 | 155 | ### 내부 클래스 는 직렬화를 구현하지 말아야 한다. 156 | 157 | 내부 클래스에는 바깥 인스턴스의 참조와 유효 범위 안의 지역변수 값들을 저장하기 위해 컴파일러가 생성한 필드들이 자동으로 추가된다. 158 | 159 | 다시말해 내부 클래스의 기본 직렬화 형태가 분명하지 않음을 말한다. 160 | 161 | 해당 필드들이 클래스 정의에 어떻게 추가되는지 정의되지 않은 만큼 내부 클래스에서는 직렬화를 지원하면 안된다. 162 | 163 | 그러나 정적 멤버 클래스는 Serializable을 구현해도 괜찮다. 164 | -------------------------------------------------------------------------------- /12장_직렬화/item89.md: -------------------------------------------------------------------------------- 1 | # 아이템89. 인스턴스 수를 통제해야 한다면 readResolve 보다는 열거 타입을 사용하라 2 | 3 | ## 1. 싱글턴과 직렬화 4 | 5 | > **싱글턴 패턴의 클래스가 직렬화를 구현하면 싱글턴을 보장하지 못한다** 6 | 7 | 아래에 작성한 싱글턴 클래스는 객체를 메모리에 한번만 올리는 것을 보장한다. 8 | 9 | ```java 10 | public class Elvis { 11 | public static final Elvis INSTANCE = new Elvis(); 12 | 13 | private Elvis() { 14 | } 15 | 16 | public static Elvis getINSTANCE() { 17 | return INSTANCE; 18 | } 19 | } 20 | ``` 21 | 22 | 하지만 싱글턴 클래스가 직렬화 가능한 클래스가 되기 위해 `Serializable` 인터페이스를 구현하면 싱글턴을 보장하지 못한다. 23 | 24 | 이유는 직렬화를 통해 초기화해둔 인스턴스가 아닌 다른 인스턴스가 반환되기 때문이다. 25 | 26 | `readObject`를 제공하는 방식도 해결할 수 없다. 어떻게 직렬화할 수 있을까? 27 | 28 | ## 2. 싱글턴 직렬화하기 : readResolve 29 | 30 | `readResolve` 기능을 이용해 `readObject`가 만들어낸 인스턴스를 대체할 수 있다. 31 | 32 | 1. 역직렬화 된 후 새로 생성된 객체를 인수로 readResolve 메서드가 호출 33 | 2. 메서드가 반환한 객체 참조가 새로 생성된 객체를 대신해 반환 34 | 3. 새로 생성된 객체는 참조되는 곳이 없어 가비지 컬렉션 35 | 36 | ```java 37 | private Object readResolve() { 38 | return INSTANCE; 39 | } 40 | ``` 41 | 42 | - 이렇게 되면 직렬화 형태가 어떠한 데이터도 가질 필요가 없다 (인스턴스 개수 통제 목적) 43 | - 모든 인스턴스 필드는 `transient` 한정자를 붙여주자 44 | - 만약 그렇지 않으면 역직렬화(Deserialization) 과정에서 역직렬화된 인스턴스를 가져올 수 있다. → **싱글턴이 깨지게 된다.** 45 | 46 | ## 3. 어떻게 공격당할까 47 | 48 | 싱글턴이 `transient`가 아닌 참조 필드를 가지고 있다면, 그 필드의 내용은 readResolve 메서드가 실행되기 전에 역직렬화된다. 49 | 50 | 1. readResolve 메서드와 인스턴스 필드 하나를 포함한 도둑 클래스를 만든다. 51 | 2. 도둑 클래스의 인스턴스 필드는 직렬화된 싱글턴을 참조하는 역할을 한다. 52 | 3. **직렬화된 스트림에서 싱글턴의 비휘발성 필드를 도둑의 인스턴스 필드로 교체**한다. 53 | 4. 싱글턴이 도둑을 포함하므로 역직렬화시 도둑 클래스의 readResolve가 먼저 호출된다. 54 | 5. 도둑 클래스의 인스턴스 필드에는 역직렬화 도중의 싱글턴의 참조가 담겨있게 된다. 55 | 6. 도둑 클래스의 readResolve 메서드는 인스턴스 필드가 참조한 값을 정적 필드로 복사한다. 56 | 7. 싱글턴은 도둑이 숨긴 transient가 아닌 필드의 원래 타입에 맞는 값을 반환한다. 57 | 8. 이 과정을 생략하면 직렬화 시스템이 도둑의 참조를 이 필드에 저장하려 할 때 VM에서 `ClassCastException` 을 발생시킨다. 58 | 59 | ```java 60 | // transient가 아닌 참조 필드를 가지는 싱글턴 61 | public class Elvis implements Serializable { 62 | public static final Elvis INSTANCE = new Elvis(); 63 | private Elvis() { } 64 | // transient 아님 65 | private String[] favoriteSongs = {"Hound Dog", "Heartbreak Hotel"}; 66 | 67 | public void printFavorites() { 68 | System.out.println(Arrays.toString(favoriteSongs)); 69 | } 70 | 71 | private Object readResolve() { 72 | return INSTANCE; 73 | } 74 | } 75 | ``` 76 | 77 | ```java 78 | // 싱글턴의 비휘발성 인스턴스 필드를 훔쳐러는 도둑 클래스 79 | public class ElvisStealer implements Serializable { 80 | private static final long serialVersionUID = 0; 81 | static Elvis impersonator; 82 | private Elvis payload; 83 | 84 | private Object readResolve() { 85 | // resolve되기 전의 Elvis 인스턴스의 참조를 저장 86 | impersonator = payload; 87 | 88 | // favoriteSongs 필드에 맞는 타입의 객체를 반환 89 | return new String[] {"A Fool Such as I"}; 90 | } 91 | } 92 | ``` 93 | 94 | 임시로 만든 스트림을 이용해 2개의 싱글턴 인스턴스를 만들어낸다. 95 | 96 | ```java 97 | // 직렬화의 약점을 이용해 싱글턴 객체를 2개 생성한다. 98 | public class ElvisImpersonator { 99 | private static final byte[] serializedForm = new byte[]{ 100 | (byte) 0xac, (byte) 0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x05, 101 | // 코드 생략 102 | }; 103 | 104 | private static Object deserialize(byte[] sf) { 105 | try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(sf)) { 106 | try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) { 107 | return objectInputStream.readObject(); 108 | } catch (IOException | ClassNotFoundException e) { 109 | throw new IllegalArgumentException(e); 110 | } 111 | } 112 | } 113 | 114 | public static void main(String[] args) { 115 | // ElvisStealer.impersonator 를 초기화한 다음, 116 | // 진짜 Elvis(즉, Elvis.INSTANCE)를 반환 117 | Elvis elvis = (Elvis) deserialize(serializedForm); 118 | Elvis impersonator = ElvisStealer.impersonator; 119 | // GC 처리 됐어야하는 Elvis를 갖고 있음 120 | elvis.printFavorites(); // [Hound Dog, Heartbreak Hotel] 121 | impersonator.printFavorites(); // [A Fool Such as I] 122 | } 123 | } 124 | ``` 125 | 126 | 서로 다른 2개의 Elvis 인스턴스가 생성된다. 127 | 128 | ``` 129 | [Hound Dog, HeartBreak Hotel] 130 | [A Fool Such as I] 131 | ``` 132 | 133 | ## 4. 해결 방법 : enum 134 | 135 | - `enum` 을 사용하면 모든 것이 해결된다! 136 | - `enum` 클래스는 자바가 선언한 상수 외에 다른 객체가 없음을 보장해준다. 137 | - 단, `AccessibleObject.setAccessible` 메서드와 같은 리플렉션을 사용했을 때는 예외다. 138 | 139 | ```java 140 | public enum Elvis { 141 | INSTANCE; 142 | private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; 143 | 144 | public void printFavorites() { 145 | System.out.println(Arrays.toString(favoriteSongs)); 146 | } 147 | } 148 | ``` 149 | 150 | ### 4-1. readResolve 메서드가 필요할 때 151 | 152 | - 인스턴스 통제를 위해 `readResolve` 메서드를 사용하는 것이 중요할 때도 있다. 153 | - 직렬화 가능 인스턴스 통제 클래스를 작성해야 할 때, 컴파일 타임에는 어떤 인스턴스들이 있는지 모를 수 있는 상황이라면 열거 타입으로 표현하는 것이 불가능하기 때문에 `readResolve` 메서드를 사용할 수 밖에 없다. 154 | 155 | ## 5. readResolve 메서드의 접근성 156 | 157 | - `final` 클래스라면 private 접근 제어자를 사용해야 한다. 158 | - `final` 이 아닌 경우 클래스일 때 주의할 점 159 | - `private` 선언시 하위 클래스에서 사용할 수 없다. 160 | - `package-private` 으로 선언시 같은 패키지에 속한 하위 클래스에서만 사용할 수 있다. 161 | - `protected`, `public` 은 재정의하지 않은 모든 하위 클래스에서 사용할 수 있다. 162 | - `protected`, `public` 이면서 하위클래스에서 재정의 하지 않으면, 하위 클래스의 인스턴스를 역직렬화하면 상위 클래스의 인스턴스를 생성하여 `ClassCastException` 을 일으킬 수 있다. 163 | 164 | ## 6. 정리 165 | 166 | - 불변식을 지키기 위해 인스턴스를 통제해야 한다면, enum 클래스가 가장 좋다. 167 | - enum 클래스를 사용할 수 없는 상황에서 직렬화와 인스턴스 통제가 필요하다면, `readResolve` 메서드를 작성하고, 모든 필드를 `transient` 한정자로 선언해야 한다. 168 | - 이 때, `readResolve` 메서드의 접근성은 매우 중요하다. 169 | - final 클래스에선 private 접근 제어자를 사용해야 한다. 170 | - final 이 아닌 클래스에서는 하위 클래스를 고려해서 작성할 필요가 있다. -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/README.md: -------------------------------------------------------------------------------- 1 | ## 2장 객체 생성과 파괴 2 | 3 | > 발표자 + 질문자 테이블을 추가할 예정입니다. 4 | 5 | - Item1 : 생성자 대신 정적 팩터리 메서드를 고려하라 6 | - Item2 : 생성자에 매개변수가 많다면 빌더를 고려하라 7 | - Item3 : private 생성자나 열거 타입으로 싱글터임을 보증하라 8 | - Item4 : 인스턴스화를 막으려거든 private 생성자를 사용하라 9 | - Item5 : 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 10 | - Item6 : 불필요한 객체 생성을 피하라 11 | - Item7 : 다 쓴 객체 참조를 해제하라 12 | - Item8 : finalizer와 cleaner 사용을 피하라 13 | - Item9 : try-finally보다는 try-with-resources를 사용하라 -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/img/item7_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/back-end-study/effective-java/6cdb10f6bbcf323f8e9576d3813517db4100d2db/2장_객체_생성과_파괴/img/item7_1.png -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/img/item7_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/back-end-study/effective-java/6cdb10f6bbcf323f8e9576d3813517db4100d2db/2장_객체_생성과_파괴/img/item7_2.png -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/img/item7_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/back-end-study/effective-java/6cdb10f6bbcf323f8e9576d3813517db4100d2db/2장_객체_생성과_파괴/img/item7_3.png -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item3.md: -------------------------------------------------------------------------------- 1 | # Item.03 private 생성자나 열거 타입으로 싱글턴임을 보증하라 2 | --- 3 | 4 | ### 싱글턴(Singleton)이란? 5 | 인스턴스를 오직 하나만 생성할 수 있는 클래스. 즉, 딱 하나의 동일한 인스턴스임을 보장하는 클래스를 말한다 6 | 7 | 싱글턴 패턴을 사용하면 다음과 같은 장점이 있다 8 | - 동일한 인스턴스를 얻기위해 무분별한 new 연산을 하지 않으므로 메모리 낭비를 방지할 수 있다 9 | - 인스턴스 생성 비용이 큰 경우 딱 한번만 생성하여 재사용할 수 있다 10 | - ~~global access 를 통해 데이터 공유가 가능하다~~ 11 | - (싱글톤 == global accessible instanace) -> false 아닌가? 12 | - 폐쇄적으로 접근가능하지만 딱 1개만 만들어지는 클래스도 싱글턴아닌가? 13 | - ==**싱글턴**이라는 개념은 **전역/비전역**이라는 개념과 분리해서 봐야한다== 14 | 15 | 단점으로는 16 | - 싱글톤 인스턴스가 너무 많은 책임을 갖게되면, 이를 사용하는 클래스들 간의 결합도가 높아지게 되고 유지보수가 어렵다 17 | - 개발자의 실수로 '상태'를 가지게 된다면 동시성 문제가 발생할 수 있다(무상태를 보장해야함) 18 | - 테스트를 하기 위해서는 반드시 인터페이스를 구현해야 한다 19 | - 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워 질 수 있다. 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면, 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이다. (책 23p) 20 | 21 | 22 | ### 싱글턴을 만드는 첫번째 방법(private 생성자 + public static final) 23 | 생성자는 private으로 감춰두고, public static 멤버를 통해서만 인스턴스에 접근 가능하게 한다 24 | ```java 25 | public class Elvis { 26 | public static final Elvis INSTANCE = new Elvis(); 27 | 28 | private Elvis() { 29 | // ... 30 | } 31 | 32 | public void leaveTheBuilding(){ 33 | //... 34 | } 35 | } 36 | ``` 37 | public static final 필드인 INSTANCE는 초기화될 때 딱 한번만 호출되고, 클래스 외부에서 접근 가능한 생성자가 없기 때문에 싱글턴을 보장한다 38 | 39 | 이 방법의 장점으로는 Elvis.INSTANCE 로 접근할 수 있어 명확하고, 소스가 간결하다 40 | 41 | 42 | ### 싱글턴을 만드는 두번째 방법(private 생성자 + 정적 팩터리 메서드) 43 | 정적 팩터리 메서드를 public static 멤버로 제공한다 44 | ```java 45 | public class Elvis { 46 | private static final Elvis INSTANCE = new Elvis(); 47 | 48 | private Elvis() { 49 | // ... 50 | } 51 | 52 | public static Elvis getInstance() { 53 | return INSTANCE; 54 | } 55 | 56 | public void leaveTheBuilding() { 57 | //... 58 | } 59 | } 60 | ``` 61 | 첫번째 방법과 동일하게 static final 필드를 초기화 한 후, Elvis.getInstance()로 접근하여 항상 같은 객체의 참조를 반환함으로써 싱글턴임을 보장한다 62 | 63 | 두번째 방법의 장점은 다음과 같다 64 | 1) 클라이언트 코드의 변경 없이 동작 방식을 변경할 수 있다 65 | 2) ==정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다(Item.30)== 66 | 3) 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다([Java에서 제공하는 함수형 인터페이스](https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html)) 67 | 68 | 69 | ### 첫번째, 두번째 방식의 문제점 70 | 71 | #### 리플렉션(Reflection) API 에 취약하다 72 | 리플렉션 API를 사용하면 ==AccessibleObject.setAccessible== 을 통해 private 생성자의 접근 제어 keyword에 대한 제어를 변경하면 private 생성자를 호출할 수 있다 73 | 74 | ```java 75 | public static void main(String[] args) { 76 | try { 77 | // getDeclaredConstructor는 getConstuctor와 달리 78 | // private 생성자 까지 호출 가능하다 79 | Constructor defaultConstructor 80 | = Elvis.class.getDeclaredConstructor(); 81 | 82 | // 생성자 접근제어 허용 83 | defaultConstructor.setAccessible(true); 84 | 85 | // 객체 생성 가능 86 | Elvis elvis1 = defaultConstructor.newInstance(); 87 | Elvis elvis2 = defaultConstructor.newInstance(); 88 | // Elvis.INSTANCE와 elvis1, elvis2는 모두 다른 hashCode 89 | 90 | } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | ``` 95 | 96 | 이러한 공격을 방어하려면 생성자를 수정하여 두 번째 객체가 생성되려 할 때 예외를 던지게 하면된다 97 | ```java 98 | public class Elvis { 99 | public static final Elvis INSTANCE = new Elvis(); 100 | private static boolean created; 101 | 102 | private Elvis() { 103 | if (created) { 104 | throw new UnsupportedOperationException("can't be created by constructor."); 105 | } 106 | created = true; 107 | } 108 | 109 | public void leaveTheBuilding(){ 110 | //... 111 | } 112 | } 113 | ``` 114 | 115 | #### 역직렬화(Deserialize) 시 싱글턴을 보장하지 않는다 116 | ```java 117 | public static void main(String[] args) { 118 | // 직렬화 119 | try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) { 120 | out.writeObject(Elvis.INSTANCE); 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | 125 | // 역직렬화 126 | try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) { 127 | Elvis elvis3 = (Elvis) in.readObject(); 128 | System.out.println(elvis3 == Elvis.INSTANCE); 129 | // false 출력 130 | } catch (IOException | ClassNotFoundException e) { 131 | e.printStackTrace(); 132 | } 133 | } 134 | ``` 135 | 이러한 경우 싱글턴 클래스에 Serializable 인터페이스를 구현하고, readResolve() 메소드를 구현해야 한다 136 | ```java 137 | public class Elvis implements Serializable { 138 | public static final Elvis INSTANCE = new Elvis(); 139 | private static boolean created; 140 | 141 | private Elvis() { 142 | if (created) { 143 | throw new UnsupportedOperationException("can't be created by constructor."); 144 | } 145 | created = true; 146 | } 147 | 148 | public void leaveTheBuilding(){ 149 | //... 150 | } 151 | 152 | // 문법적으로는 Override는 아니지만 Deserialize 할 때 이 메소드가 사용된다 153 | private Object readResolve() { 154 | return INSTANCE; 155 | } 156 | } 157 | ``` 158 | 위 두가지 문제점을 극복하고 테스트도 가능한 싱글턴 클래스를 생성하기 위해선 아래와 같이 클래스를 만들어 주어야한다 159 | ```java 160 | public class Elvis implements IElvis, Serializable { 161 | 162 | public static final Elvis INSTANCE = new Elvis(); 163 | private static boolean created; 164 | 165 | private Elvis() { 166 | if (created) { 167 | throw new UnsupportedOperationException("can't be created by constructor."); 168 | } 169 | 170 | created = true; 171 | } 172 | 173 | public void leaveTheBuilding() {...} 174 | 175 | // 문법적으로는 Override는 아니지만 Deserialize 할 때 이 메소드가 사용된다 176 | private Object readResolve() { 177 | return INSTANCE; 178 | } 179 | } 180 | ``` 181 | 첫번째와 두번째 방식에서 장점으로 꼽았던 간결하다는 장점이 무너짐과 동시에, 위와 같은 배경 지식이 없다면 이해하기 난해한 코드가 되어버렸다 182 | 183 | 184 | ### 싱글턴을 만드는 세번째 방법 185 | 원소가 하나인 열거(enum) 타입을 선언한다 186 | ```java 187 | public enum Elvis { 188 | INSTANCE; 189 | public void leaveTheBuilding(){ 190 | //... 191 | } 192 | } 193 | ``` 194 | Enum 타입으로 만들어 주게되면 `더 간결`하고 `추가적인 노력 없이 직렬화` 할 수 있고, `리플렉션 공격에서도 방어가능`하다 195 | 196 | > Enum 타입의 경우도 private 생성자가 있지만 리플렉션 API로도 접근할 수 없기 때문에 리플렉션 공격에 대해서 방어가 가능하다 197 | 198 | 199 | 단, 싱글턴이 enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다 200 | (인터페이스를 구현하도록 선언할 수는 있다) 201 | 202 | --- 203 | --- 204 | 205 | 순수 java 소스로 완벽한 싱글턴을 보장하는 클래스를 만들기 위해서는 복잡한 소스와 깊이 있는 배경 지식이 필요하다 206 | 또한 위에서 언급하지 않은 Thread-safe에 대해서도 고민을 해야한다([Thread-safe한 싱글턴 패턴](https://github.com/jeff-seyong/Design-Pattern/tree/master/singleton-pattern)) 207 | 208 | 하지만 우리 대부분(?)은 Spring을 사용하고 있고 Spring에서 Bean은 별도의 선언하지 않으면 기본적으로 싱글턴을 보장하는 것을 알고 있기 때문에 큰 고민하지 않고 쉽게 사용하고 있다 209 | 210 | 211 | ### 그렇다면 Spring에서는 어떻게 Bean들이 싱글턴임을 보장할까? 212 | Spring에서는 Spring Container를 통해 싱글턴임을 보장한다 213 | Spring Container에 Bean 저장소에다가 Bean 이름과 인스턴스를 매핑하여 저장해둔다 214 | 이후 의존성 주입이 필요한곳에 참조 하고자하는 인스턴스를 주입시켜준다 215 | 216 | 단, new 연산자를 통해서 인스턴스를 생성하는 경우에는 Spring에서 관리하고 있는 Bean이 아닌 클래스로부터 새로운 인스턴스를 생성하므로 주의해야 한다 -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item4.md: -------------------------------------------------------------------------------- 1 | # item04 인스턴스화를 막으려거든 private 생성자를 사용하라 2 | ### 정적 메서드와 정적 필드만을 담은 클래스를 만드는 이유 3 | - java.lang.Math, java.util.Arrays 처럼 기본 타입 값이나 배열 과련 메서드를 모아놓을 수 있다. 4 | - java.util.Collections 처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아놓을 수도 있다. 5 | - final클래스와 관련한 메서드들을 모아놓을 때도 사용한다. 6 | 7 | 8 | ### 정적 메서드만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다. 9 | ```java 10 | public class UtilityClass { 11 | // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용) 12 | private UtilityClass() { 13 | throw new AssertionError(); 14 | } 15 | } 16 | ``` 17 | - 클래스 안에서 생성자를 호출하지 않도록 Exception을 던져준다. 18 | - 직관적으로 주석을 이용해 생성자가 private인 의도를 전달한다. 19 | - 상속을 불가능하게 하는 효과도 있다. -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item5.md: -------------------------------------------------------------------------------- 1 | ## item5 자원을 직접 명시하지말고 의존 객체 주입을 사용하라 2 | 3 | 4 | ![](https://velog.velcdn.com/images/rodlsdyd/post/6ef0c9d8-7edf-4c41-bb66-a5cea81d5b17/image.png) 5 | 6 | ### 자원을 직접명시하는 방법의 문제점 7 | 8 | 9 | ```java 10 | public class SpellChecker { 11 | private static final Dictionary dictionary = new ADictionary(); // 사전에 따라 변경될 수 있다 12 | 13 | private SpellChecker() {} // Noninstantiable 14 | 15 | public static boolean isValid(String word){...} 16 | public static List suggestions(String typo) { ... } 17 | } 18 | ``` 19 | - ADictionary 가 아닌 다른 자원을 사용하려면 코드를 계속 변경해야한다 20 | 21 | - SpellChecker의 동작이 주입 자원에 따라 변할수 있으며 재사용이 힘들어진다 22 | (한국사전,독일사전 등에 따라 SpellChecker의 동작이 변경되어 KoreanSpellChecker, 등을 따로 만들어야한다) 23 | 24 | - 테스트에 효율적이지 않다 25 | 26 | ```java 27 | class SpellCheckerTest{ 28 | 29 | @Test 30 | void isValid() { 31 | assertTrue(SpellChecker.isValid("test")); 32 | } // ADictionary를 항상 만들게 된다 33 | } 34 | ``` 35 | 36 | - SpellChcker를 만들때마다 ADictionary를 생성하게된다 37 | - ADictionary를 생성하는 비용이 큰경우엔 더욱 효율적이지 않다 38 | 39 | 40 | 위방식에서 final을 없애고 다른사전으로 교체하는 메서드 (setter메서드)생성? 41 | => 오류내기쉬우며, 멀티스레드 환경에서 쓸수없다 42 | 43 | 오류 = setter주입으로 생길수있는 Null예외? 44 | 45 | 46 | 47 | ### 유연한 방식 48 | 49 | ```java 50 | public class SpellChecker { 51 | private final Dictionary dictionary; 52 | 53 | public SpellChecker(Dictionary dictionary) { 54 | this.dictionary = Objects.requireNonNull(dictionary); 55 | } 56 | //function... 57 | } 58 | ``` 59 | 60 | - Dictionary가 변경되어도 SpellChecker의 코드를 재사용할수있다. ( 단, Dictionary가 인터페이스라는 가정하에) 61 | 62 | - 필요할때마다 여러 인스턴스를 사용할수있고 불변 63 | 64 | - 테스트용 주입도 가능해진다 65 | 66 | 67 | ```java 68 | class SpellCheckerTest{ 69 | 70 | @Test 71 | void isValid() { 72 | SpellChecker spellChecker = new SpellChecker(new MockDictionary()); 73 | assertTrue(SpellChecker.isValid("test")); 74 | } // 테스트용 Mock객체로 변경이 가능해진다 75 | } 76 | ``` 77 | 78 | #### 정적팩토리를 넘겨주는 방식 79 | ```java 80 | public static SpellChecker staticFactory(Dictionary dictionary) { 81 | return new SpellChecker(dictionary); // 유연하게 주입 82 | } 83 | ``` 84 | 85 | #### 빌더에 적용 86 | 87 | ```java 88 | public static Builder builder(Dictionary dictionary) { 89 | return new Builder(dictionary); // 유연하게 주입 90 | } 91 | 92 | public static class Builder{ 93 | private Dictionary dictionary; 94 | 95 | public Builder(Dictionary dictionary) { 96 | this.dictionary = dictionary; 97 | } 98 | // 빌더... 99 | } 100 | ``` 101 | 102 | #### 생성자에 자원팩토리를 넘겨주는 방식 103 | 104 | ```java 105 | public class SpellChecker { 106 | 107 | private final Dictionary dictionary; 108 | 109 | public SpellChecker(Supplier dictionarySupplier) { 110 | this.dictionary = dictionarySupplier.get(); 111 | } 112 | } 113 | 114 | // Main 115 | SpellChecker spellCheckerBySupplier = new SpellChecker(() -> new ADictionary()); 116 | ``` 117 | 118 | 119 | 120 | 121 | - `Supplier`를 입력받는 메서드는 한정적 와일드카드타입을 사용해 팩터리의 타입매개변수를 제한해야한다 122 | 123 | => 타입에 구체적인 Supllier가 온다고 가정했나? 타입한정자를 사용해서 범위를 제한해라 124 | ex) `Supplier` 대신 `Supplier` 125 | 126 | 127 | 128 | ### 이야기 해볼점 129 | 130 | - 생성자 주입방식의 단점은 하나도 없는지? 131 | 132 | - (스프링) 세터주입이나 다른주입 방식이 생성자 주입보다 나은경우가 있는지? 133 | -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item6.md: -------------------------------------------------------------------------------- 1 | # item06 불필요한 객체 생성을 피하라 2 | ### 똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을때가 많다. 재사용은 빠르고 세련되다. 특히 불변 객체(아이템 17)는 언제든 재사용할 수 있다. 3 | 4 | 잘 못된 객체 생성 예 5 | ```java 6 | // 따라 하지 말것!! - 실행될 때마다 String 인스턴스를 생성 7 | String s = new String("bikini"); 8 | // 개선된 버전 - 실행될 때마다 하나의 String 인스턴스를 사용 9 | String s = "bikini"; 10 | ``` 11 | > [String Literals jls-3.10.5](https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.10.5) 12 | 13 | ### 생성자 대신 정적 팩터리 메서드(item01)를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다. 14 | ex) Boolean(String) 생성자 대신 Boolean.valueOf(String) 팩터리 메서드 사용, 자바 9에서 deprecated API로 지정되었다. [deprecated list](https://docs.oracle.com/en/java/javase/17/docs/api/deprecated-list.html) 15 | ```java 16 | // public static final Boolean TRUE = new Boolean(true); 17 | // public static final Boolean FALSE = new Boolean(false); 18 | 19 | // Boolean.valueOf(String) 20 | public static Boolean valueOf(String s) { 21 | return parseBoolean(s) ? TRUE : FALSE; 22 | } 23 | 24 | // Boolean.valueOf(boolean) 25 | public static Boolean valueOf(boolean b) { 26 | return (b ? TRUE : FALSE); 27 | } 28 | 29 | 30 | // Long.valueOf(long) 31 | public static Long valueOf(long l) { 32 | final int offset = 128; 33 | if (l >= -128 && l <= 127) { // will cache 34 | return LongCache.cache[(int)l + offset]; 35 | } 36 | return new Long(l); 37 | } 38 | ``` 39 | 40 | ### 생성 비용이 아주 비싼 객체가 반복해서 필요하다면 캐싱하여 재사용하길 권한다. 41 | ```java 42 | static boolean isRomanNumeral(String s) { 43 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]}L?X{0,3})(I[XV]|V?I{0,3})$"); 44 | } 45 | ``` 46 | String.matches는 정규식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다. String.matches내부에서 사용하는 Pattern 인스턴스는 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다. Pattern은 입력받은 정규표현식에 해당하는 [유한 상태 머신](https://ko.wikipedia.org/wiki/%EC%9C%A0%ED%95%9C_%EC%83%81%ED%83%9C_%EA%B8%B0%EA%B3%84)을 만들기 때문에 인스턴스 생성 비용이 높다. 47 | 성능 개선을 위해 필요한 정규표현식을 표현하는 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두고 재사용한다. 48 | ```java 49 | public class RomanNumerals { 50 | private static final Pattern ROMAN = Patterns.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]}L?X{0,3})(I[XV]|V?I{0,3})$"); 51 | static boolean isRomanNumeral(String s) { 52 | return ROMAN.matcher(s).matches(); 53 | } 54 | } 55 | ``` 56 | 개선된 방식의 클래스가 초기화된 후 이 메서드를 한 번도 호출하지 않는다면 ROMAN 필드는 쓸데없이 초기화된 꼴이다. 메서드가 처음 호출될 때 필드를 초기화 하는 지연 초기화(item83)로 불필요한 초기화를 없앨 수 있지만, 권하지는 않는다. 지연 초기화는 코드를 복잡하게 만드는데, 성능은 크게 개선되지 않을때가 많기 때문이다.(item67) 57 | 58 | ### 가변 객체라도 새로운 인스턴스가 필요하지 않을 수 있다. 59 | [어탭터](https://refactoring.guru/design-patterns/adapter)는 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체다. 어댑터는 뒷단 객체만 관리하면 된다. 즉, 뒷단 객체 외에는 관리할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다. 60 | Map 인터페이스의 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set 뷰(어댑터)를 반환 한다. 반환된 Set 인스턴스가 가변이더라도 반환된 인스턴스들은 기능적으로 모두 똑같다. (?) 즉, 반환한 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀐다. 모두가 똑같은 Map 인스턴스를 대변하기 때문이다. 61 | [HashMap#keySet()](https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/HashMap.java#L914) 62 | ```java 63 | public Set keySet() { 64 | Set ks = keySet; 65 | if (ks == null) { 66 | ks = new KeySet(); 67 | keySet = ks; 68 | } 69 | return ks; 70 | } 71 | 72 | final class KeySet extends AbstractSet { 73 | ... 74 | public final void clear() { HashMap.this.clear(); } 75 | ... 76 | } 77 | ``` 78 | ```java 79 | public static void main(String[] args) { 80 | Map map = new HashMap<>(); 81 | map.put("A", 0); 82 | map.put("B", 1); 83 | map.put("C", 2); 84 | 85 | Set keySet = map.keySet(); 86 | System.out.println(map); // {A=0, B=1, C=2} 87 | // keySet.add("D"); // java.lang.UnsupportedOperationException java.base/java.util.AbstractCollection.add(AbstractCollection.java:267); 88 | keySet.clear(); 89 | System.out.println(map); // {} 90 | } 91 | ``` 92 | 93 | ### 의도치 않은 오토박싱이 숨어들지 않도록 주의하자. 94 | 오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다. 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다. 의미상 별다를 것 없지만 성능에서는 그렇지 않다(item 61) 95 | ```java 96 | private static long sum() { 97 | Long sum = 0L; 98 | for(long i = 0; i <= Integer.MAX_VALUE; i++) 99 | sum += i; // sum = Long.valueOf(sum.longValue() + i); 100 | return sum; 101 | } 102 | ``` 103 | sum 변수를 long이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 약 231개나 만들어진다. 104 | **박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자** 105 | 106 | ### "객체 생성은 비싸니 피해야한다"로 오해하면 안 된다. 107 | 요즘 JVM에서는 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 일이 크게 부담되지 않는다. **프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것이라면 일반적으로 좋은 일이다.** 108 | 아주 무거운 객체가 아닌 다음에야 단순히 객체 생성을 피하고자 여러분만의 객체 풀을 만들지는 말자. 객체 풀을 만드는 게 나은 예가 있지만 일반적으로 자체 객체 풀은 코드를 헷갈리게 만들고 메모리 사용량을 늘리고 성능을 떨어뜨린다. 요즘 JVM 가비지 컬렉터는 상당히 잘 최적화되어서 가벼운 객체를 다룰 때는 직접 만든 객체 풀보다 훨씬 빠르다. 109 | 110 | 대조되는 개념 '새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 마라'(item50) 111 | 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억하자. 방어적 복사에 실패하면 언제 터져 나올지 모르는 버그와 보안 구멍으로 이어지지만, 불필요한 객체 생성은 그저 코드 형태와 성능에만 영향을 준다. 112 | 113 | 114 | -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item7.md: -------------------------------------------------------------------------------- 1 | # Item.07 다 쓴 객체 참조를 해제하라 2 | --- 3 | 4 | C나 C++처럼 메모리를 직접 관리해주는 언어를 쓰다가 Java처럼 Garbage Collector(GC)를 갖춘 언어로 넘어오면, 더 이상 메모리 관리에 대해 신경 쓰지 않아도 된다고 오해할 수 있는데, 절대 5 | 사실이 아니다 6 | 7 | 책에서 보여주는 Stack 클래스 예제 코드를 보자 8 | 9 | ```java 10 | public class Stack { 11 | private Object[] elements; 12 | private int size = 0; 13 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 14 | 15 | public Stack() { 16 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 17 | } 18 | 19 | public void push(Object e) { 20 | ensureCapacity(); 21 | elements[size++] = e; 22 | } 23 | 24 | public Object pop() { 25 | if (size == 0) 26 | throw new EmptyStackException(); 27 | return elements[--size]; 28 | } 29 | 30 | /** 31 | * 원소를 위한 공간을 적어도 하나 이상 확보한다. 32 | * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다. 33 | */ 34 | private void ensureCapacity() { 35 | if (elements.length == size) 36 | elements = Arrays.copyOf(elements, 2 * size + 1); 37 | } 38 | } 39 | ``` 40 | 41 | 특별한 문제가 없어 보이지만, 해당 코드에서는 메모리 누수가 일어나고 있다 42 | 43 | 바로 pop() 메서드가 그 원인이다 44 | 45 | ![item7_1.png](./img/item7_1.png) push를 계속 해서 스택에 많은 데이터가 쌓여 있다가 46 | 47 | ![item7_2.png](./img/item7_2.png) pop을 통해 데이터를 꺼낸다고 했을 때 48 | 49 | 이미 꺼내어진 데이터들은 사용되는 곳은 없지만 다 쓴 참조(obsolete reference)를 여전히 가지고 있기 때문이다 50 | 51 | 52 | 53 | ## 메모리 누수 주원인 54 | 55 | 앞서 말한 예시처럼 GC(Garbage Collector)가 마법처럼 다 해줄 것 같지만 생각보다 메모리 누수가 종종 발생한다 56 | 그 원인 중 크게보면 아래와 같다 57 | 58 | - 직접 메모리를 관리하는 경우 59 | - 캐시 60 | - 리스너/콜백 61 | 62 | 공통점은 어딘가 특정 자료구조에 데이터를 쌓아 놓기만 하고, 실수로 인해서 참조 해제를 하지 않는 경우 주로 발생한다 63 | 64 | 65 | ### 직접 메모리를 관리하는 경우 66 | Stack 예시 처럼 직접 메모리를 관리하는 경우에는 명시적으로 해당 참조를 다 썼을 때 null 처리를 해주면 된다 67 | ```java 68 | class Stack { 69 | // ... 70 | public Object pop() { 71 | if (size == 0) 72 | throw new EmptyStackException(); 73 | Object result = elements[--size]; 74 | elements[size] = null; // 다 쓴 참조 해제 75 | return result; 76 | } 77 | // ... 78 | } 79 | ``` 80 | 이렇게 함으로써 가비지 컬렉터에 더이상 쓰지 않을 것임을 알려 주어야 한다 81 | (가비지 컬렉터 입장에서는 활성/비활성 객체 둘 다 Stack 에서 사용중인 것으로 판단하기 때문) 82 | 83 | 또한, 다 쓴 참조를 null 처리를 하게 되면 실수로 사용하게 될 경우 NullPointerException 을 유발할 수 있으므로 조기에 대응할 수 있다 84 | 85 | 하지만 이 방식은 예외적인 경우이며 모든 객체를 사용하고 나서 일일이 null 처리하는 방식은 바람직하지 않다 86 | 87 | 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이다 88 | 89 | 90 | ### 캐시(Cache) 91 | 객체 참조를 캐시에 넣고 나서, 객체를 다 쓴 뒤로도 한참을 그냥 놔두게 되면 메모리 누수가 발생할 수 있다. 92 | 이 경우 해법은 여러가지가 있는데 93 | 94 | 1. 캐시(Cache)의 Key에 의존하는 동안에만 Value가 필요한 경우 WeakHashMap을 사용하자
95 | WeakHashMap는 더이상 사용하지 않는 객체를 GC할 때 자동으로 삭제해주는 Map으로 WeakReference를 사용하여 구현된 Map이다 96 | 97 | Key를 통해서 캐시에 접근하여 필요한 로직을 모두 수행한 뒤,
98 | Key를 null로 초기화 하면 GC가 돌 때 자동으로 Map에서 삭제가 된다 99 | 100 | 단, 이 방식을 사용한다면 반드시 Map의 Key는 커스텀한 CacheKey 클래스를 만들어 사용해야한다
101 | JVM 내부에서 일부의 값들을 캐싱을 하고 있기 때문에 Interger, Long, String 등과 같은 기본 Reference Type 클래스를 Key로 사용하게 되면 null로 초기화 하더라도 Map에서 삭제되지 않는다. 102 | 103 | (Reference에 대한 내용은 아래에서 자세히 다루도록 한다) 104 | 105 | 2. 쓰지 않는 캐시 내 엔트리를 청소해주자 106 | (ScheduledThreadPoolExecutor 같은) 백그라운드 스레드를 활용해 주기적으로 오래된 캐시는 청소를 해주는 방법이다
107 | 혹은 LinkedHashMap의 경우 removeEldestEntry 메소드도 제공한다 108 | 109 | 3. 더 복잡한 캐시를 만들고 싶다면 java.lang.ref 패키지를 직접 활용해야 할 것이다
110 | java.lang.ref 패키지를 활용하면 다양한 Reference들을 사용할 수 있다 111 | 112 | 113 | ### 리스너(Listener) 혹은 콜백(Callback) 114 | 리스너와 콜백도 결국엔 List나 Map과 같은 자료구조를 통해 저장해두는데, 이를 등록만 하고 명확히 해제하지 않는 경우 메모리 누수가 발생한다
115 | 이럴 때 콜백을 약한 참조(Weak reference)를 활용하면 해결할 수 있다 116 | 117 | (마찬가지로 Reference에 대한 내용은 아래에서 자세히 다루도록 한다) 118 |
119 |
120 |
121 | ## Reference 종류 122 | - **Strong Reference** 123 | - 우리가 흔히 사용하는 '=' 을 사용하여 할당하는 경우 124 | 125 | - **Soft Reference** 126 | - 현재 대상 객체의 참조가 SoftReference만 있는 경우 JVM의 여유 메모리가 없다면 해당 객체을 GC 대상으로, 메모리 여유가 있다면 GC 대상으로 두지 않는다 127 | ```java 128 | public class SoftReferenceExample { 129 | 130 | public static void main(String[] args) throws InterruptedException { 131 | Object strong = new Object(); 132 | SoftReference soft = new SoftReference<>(strong); 133 | strong = null; 134 | 135 | System.gc(); 136 | Thread.sleep(3000L); 137 | 138 | // 없어지지 않는다 139 | // 왜냐면 메모리가 충분해서, 굳이 제거할 필요가 없으니까 140 | System.out.println(soft.get()); 141 | } 142 | } 143 | ``` 144 | 145 | - **Weak Reference** 146 | - 현재 대상 객체의 참조가 WeakReference만 있는 경우 해당 객체를 GC 대상으로 둔다 147 | ```java 148 | public class WeakReferenceExample { 149 | 150 | public static void main(String[] args) throws InterruptedException { 151 | Object strong = new Object(); 152 | WeakReference weak = new WeakReference<>(strong); 153 | strong = null; 154 | 155 | System.gc(); 156 | Thread.sleep(3000L); 157 | 158 | // 거의 없어집니다. 159 | // 왜냐면 약하니까(?) 160 | System.out.println(weak.get()); 161 | } 162 | } 163 | ``` 164 | 165 | - **Phantom Reference** 166 | - 바로 지우지 않고, PhantomReference를 ReferenceQueue에 넣고 나중에 정리할 수 있게 한다 167 | - 자원 정리를 할 때나, 언제 객체가 메모리에서 해제 되는지 알아야 할 때 사용된다 168 | ```java 169 | public class PhantomReferenceExample { 170 | 171 | public static void main(String[] args) throws InterruptedException { 172 | BigObject strong = new BigObject(); 173 | ReferenceQueue rq = new ReferenceQueue<>(); 174 | 175 | BigObjectReference phantom = new BigObjectReference<>(strong, rq); 176 | strong = null; 177 | 178 | System.gc(); 179 | Thread.sleep(3000L); 180 | 181 | // 없어지지 않고 큐에 들어간다 182 | System.out.println(phantom.isEnqueued()); // true 출력 183 | 184 | Reference reference = rq.poll(); 185 | BigObjectReference bigObjectCleaner = (BigObjectReference) reference; 186 | bigObjectCleaner.cleanUp(); 187 | reference.clear(); 188 | } 189 | } 190 | ``` 191 | 192 | --- 193 | -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item8.md: -------------------------------------------------------------------------------- 1 | # 아이템 8: Finalizer와 Cleaner는 피하라 2 | 자바에서는 두 가지 객체 소멸자 `finalizer`와 `cleaner` 두가지를 제공한다. 3 | 책에서는 이 두가지를 사용하는것을 지양하라고 말하고 있다. 4 | 5 | `finalizer의` 경우 `Object`내에 선언되어 있으며, 자바 9버전 부터는 `deprecated`되어 사용하지 말라고한다. 6 | 이를 대체하기 위해 `cleaner` 라는게 소개 된다. 하지만 `cleaner` 또한 `finalizer` 보다는 덜 위험하지만 여전히 예측 불가능하고, 느리다. 7 | 8 | 9 | Q. 작동 방식 10 | 11 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c8159fad-4263-4c9c-aac4-fd64ff5756c5/Untitled.png) 12 | 13 | 1. 객체가 사용되지 않으면 14 | 2. GC가 Queue에 넣고 15 | 3. 해당 객체에 선언된 finalize를 실행시킴 16 | 4. 실행이 완료 된후 GC를 기다려서 제거를 함 17 | 18 | 19 | ## 수행여부를 보장하지 않는다. 20 | `finalizer`와 `cleaner`는 둘다 언제 실행될지 알 수 없다. 그래서 객체가 더 이상 필요 없어진 시점에서 바로 실행 되지 않을 수 있다. 21 | 그래서 DB Lock을 거는 부분과 같으 공유 자원을 사용하는 곳에서는 절대 사용하면 안된다. 22 | 23 | `System.gc`와 `System.runFinalization`의 경우에도 둘의 실행 확률을 높혀줄뿐 보장해주진 않는다. 24 | 보장 해주겠다고 만든 `System.runFinalizersOnExit`와 `Runtime.runFinalizersOnExit`은 아직까지도 지탄받고 있다. 25 | 26 | Q. 둘은 왜 지탄을 받을까? 27 | 28 | → 쓰레드들 간의 교착상태가 발생 할 수 있어 현재 deprecated 상태 29 | 30 | ```java 31 | @Deprecated 32 | public static void runFinalizersOnExit(boolean value) { 33 | Runtime.runFinalizersOnExit(value); 34 | } 35 | ``` 36 | 37 | [https://howtodoinjava.com/java/basics/why-not-to-use-finalize-method-in-java/](https://howtodoinjava.com/java/basics/why-not-to-use-finalize-method-in-java/) 38 | 39 | 40 | ## 동작 중 발생한 예외를 무시한다. 41 | `finalizer` 는 동작 중 발생한 예외는 무시되며, 처리할 작업이 남아있더라도 그 순간 종료된다. 42 | 잡지 못한 예외로 인해 해당 객체는 마무리가 덜 된 상태로 남을 수 있다. 43 | 44 | ## 심각한 성능 문제 45 | `AutoCloseable`객체를 만들고, `try-with-resource`를 사용한 것 대비, `finalizer`는 50배, `cleanable`은 5배나 느렸다. 46 | 47 | 48 | ## 보안 문제 49 | `finalizer`는 심각한 보안 이슈가 존재한다. 아래 예제를 보게 되면 `BrokenUser` 클래스는 `User`클래스를 상속받아 만들어져있다. 50 | 51 | ```java 52 | public class User { 53 | private final String userId; 54 | public User(String userId) { 55 | this.userId = userId; 56 | 57 | if (userId.equals("TEST")) { 58 | throw new IllegalArgumentException("테스트 계정은 회원가입 할 수 없습니다."); 59 | } 60 | } 61 | 62 | public void signUp(String userName) { 63 | System.out.println(userName + " 님이 성공적으로 회원가입 하셨습니다."); 64 | } 65 | } 66 | ``` 67 | 68 | ```java 69 | public class BrokenUser extends User { 70 | 71 | public BrokenUser(String userId) { 72 | super(userId); 73 | } 74 | } 75 | 76 | ``` 77 | 상단에 빈객체 `user`를 만들고, `BrokenUser`객체를 생성할 때 `TEST` 라는 값을 넣어 예외를 발생시킨다. 78 | `BrokenUser`의 상위클래스 `User`의 생성자에 `TEST`라는 값이 들어오면 예외가 발생하도록 로직이 짜여져있기 때문에 catch문으로 빠지게 되어 79 | 예외가 발생한다. 그 후 가비지 컬랙터가 돌게되고 빈객체를 제거하면서 선언된 `finalize`가 실행되면서 `User` 클래스의 `signUp` 이 실행된다. 80 | 81 | ```java 82 | class BrokenUserTest { 83 | 84 | @Test 85 | void broken() throws InterruptedException { 86 | User user = null; 87 | try { 88 | user = new BrokenUser("TEST"); 89 | } catch (Exception e) { 90 | System.out.println("예외 발생"); 91 | } 92 | 93 | System.gc(); 94 | Thread.sleep(3000L); 95 | } 96 | } 97 | ``` 98 | 책에서는 이를 방지 하기 위해 `final` 을 이용하여 상속을 막거나, `finalize` 메서드에 final 키워드를 상속해서 오버라이딩 하는것을 막으라고 추천한다. 99 | 100 | ```java 101 | public class User { 102 | private final String userId; 103 | public User(String userId) { 104 | this.userId = userId; 105 | 106 | if (userId.equals("TEST")) { 107 | throw new IllegalArgumentException("테스트 계정은 회원가입 할 수 없습니다."); 108 | } 109 | } 110 | 111 | public void signUp(String userName) { 112 | System.out.println(userName + " 님이 성공적으로 회원가입 하셨습니다."); 113 | } 114 | 115 | @Override 116 | protected final void finalize() throws Throwable { 117 | this.signUp("비정상적"); 118 | } 119 | } 120 | ``` 121 | 122 | 123 | 124 | ## 추천하는 해결방법은? 125 | 책에서는 두가지 방법을 지양하고, 126 | 1. 클라이언트가 명시적으로 `close`를 호출하거나 127 | 2. `AutoCloseable` 인터페이스를 구현하고 `try-with-resource`를 사용하라고한다. 128 | 129 | ## 그럼 둘은 언제 쓰는것이 좋을까? 130 | 그래도 `finalizer`와 `cleaner` 둘다 사용하면 좋은 경우가 두가지가 있다. 131 | 132 | ### 1. 안전망 역할로 자원을 반납하는 경우 133 | 즉시 호출되리라는 법은 없지만, 클라이언트가 `close` 메서드를 호출 하지 않을 경우를 대비하여 사용하는 것이 좋다 134 | `FileInputStream`, `FileOutputStream`, `ThreadPoolExecutor` 3가지 메서드는 finalizer 안전망으로 작동하고있다. 135 | 136 | ```java 137 | // ThreadPoolExecutor 138 | @Deprecated(since="9") 139 | protected void finalize() {} 140 | ``` 141 | 142 | ### 2. 네이티브 피어 관련 리소스를 정리해야 하는 경우 143 | 네이티브 피어는 자바 객체가 아니기 떄문에 GC가 해당 존재에 대해서 알지 못한다. 네이티브 피어가 들고 있는 리소스가 중요하지 않고 성능상 영향이 크지 않다면 144 | `finalize`, `cleaner`를 사용하여 해당 자원을 반납시킬 수 있다. 145 | -------------------------------------------------------------------------------- /2장_객체_생성과_파괴/item9.md: -------------------------------------------------------------------------------- 1 | 2 | 자바 라이브러리에서 close 메서드를 호출해 직접 닫아줘야 하는 자원 예시 : InputStream, OutputStream, java.sql.Connection 등 3 | 4 |
자원 닫기는 예측할 수 없는 성능 문제로 이어져 finalizer을 사용하지만 썩 믿음직스럽지 못하다. 5 | 6 | #### 코드 9-1. try-finally 더 이상 자원을 회수하는 최선의 방책이 아니다. 7 | 8 | ```java 9 | public static String firstLineOfFile(String path) throw IOException { 10 | BufferedReader br = new BufferedReader(new FileReader(path)); 11 | try { 12 | return br.readLine(); 13 | } finally { 14 | br.close(); 15 | } 16 | } 17 | ``` 18 | 19 | #### 코드 9-2. 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! 20 | 21 | ```java 22 | static void copy(String src, String dst) throws IOException { 23 | InputStream in = new FileInputStream(src); 24 | try { 25 | OutputStream out = new FileOutputStream(dst); 26 | try { 27 | byte[] buf = new byte[BUFFER_SIZE]; 28 | int n; 29 | while ((n = in.read(buf)) >= 0) 30 | out.write(buf, 0, n); 31 | } finally { 32 | out.close(); 33 | } 34 | } finally { 35 | in.close(); 36 | } 37 | } 38 | ``` 39 | 40 | - try-finally 구문을 하나만 쓰면 leak이 생길 수 있다. 41 | - 예외는 try 블록과 finally 블록 모두에서 발생할 수 있음. 42 | - 하지만 기기에서 물리적인 문제가 생긴다면 firstLineOfFile 메서드 안의 readLine 메서드가 예외를 던지고, 같은 이유로 close 메서드도 실패할 것이다. 43 | - 이런 상황에선 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다. 이 경우 디버깅이 굉장히 어렵다. 44 | 45 | ### 해결책 : try-with-resources 46 | 이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스(단순히 void를 반환하는 close 메소드 하나만 정의한 인터페이스)를 구현해야 한다. 47 | 48 | 자바 라이브러리와 서드파티 라이브러리들의 수많은 클래스와 인터페이스가 이미 AutoCloseable을 구현하거나 확장해뒀다. 49 | 50 | #### 코드 9-3 try-with-resource 자원을 회수하는 최선책! (코드 9-1 재작성) 51 | 52 | ```java 53 | public static String firstLineOfFile(String path) throw IOException { 54 | try (BufferedReader br = new BufferedReader(new FileReader(path))){ 55 | return br.readLine(); 56 | } 57 | } 58 | ``` 59 | 60 | #### 코드 9-4 복수의 자원을 처리하는 try-with-resources 짧고 매혹적이다! (코드 9-2에 적용한 모습) 61 | 62 | ```java 63 | static void copy(String src, String dst) throws IOException { 64 | try (InputStream in = new FileInputStream(src); 65 | OutputStream out = new FileOutputStream(dst)) { 66 | byte[] buf = new byte[BUFFER_SIZE]; 67 | int n; 68 | while ((n = in.read(buf)) >= 0) 69 | out.write(buf, 0, n); 70 | } 71 | } 72 | ``` 73 | 74 | #### firstLineOfFile 메서드 75 | - readLine과 (코드에서 보이지 않는) fclose 호출 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다. 76 | - 이처럼 실전에선 프로그래머에게 보여줄 예외 하나만 보존되고 여러개의 다른 예외가 숨겨질 수도 있다. 77 | - 이렇게 숨겨진 예외들은 스택 추적 내역에서 '숨겨졌다(suppressed)'라는 꼬리표를 달고 출력된다. 78 | - 자바 7에서 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다. 79 | 80 | 81 | #### 코드 9-5 try-with-resouces를 catch절과 함께 쓰는 모습 82 | ```java 83 | public static String firstLineOfFile(String path) throw IOException { 84 | try (BufferedReader br = new BufferedReader(new FileReader(path))) { 85 | return br.readLine(); 86 | } catch (Exception e) { 87 | return defaultVal; 88 | } 89 | } 90 | ``` 91 | 파일을 열거나 데이터를 읽지 못했을 때 예외를 던지는 대신 기본값을 반환하도록 한 예시. 92 | 93 | ### 핵심 정리 94 | - 꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자. 95 | - 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다. 96 | - try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resouces로는 쉽게 자원을 회수할 수 있다. 97 | - 만들어지는 예외 정보도 훨씬 유용하다. 98 | 99 | ### 왜 try-with-resources를 써야하나요? 100 | 101 | p48. 자바 퍼즐러 예외 처리 코드의 실수 102 | ```java 103 | public class Copy { 104 | private static final int BUFFER_SIZE = 8 * 1024; 105 | 106 | static void copy(String src, String dst) throws IOException { 107 | InputStream in = new FileInputStream(src); 108 | OutputStream out = new FileOutputStream(dst); 109 | try { 110 | byte[] buf = new byte[BUFFER_SIZE]; 111 | int n; 112 | while ((n = in.read(buf)) >= 0) 113 | out.write(buf, 0, n); 114 | } finally { 115 | try { 116 | out.close(); 117 | } catch(IOException e){ 118 | //TODO 이렇게 하면 되는 거 아닌지? 119 | } 120 | 121 | try { 122 | in.close(); 123 | } catch(IOException e){ 124 | //TODO 안전한지? 125 | } 126 | } 127 | ``` 128 | 129 | IOException이 아닌 RuntimeException이 발생하면 out.close()에서 끝남. 130 | 131 | p49. try-with-resources 바이트코드 132 | ```java 133 | public class TopLine{ 134 | public TopLine(){ 135 | } 136 | 137 | static String firstLineOfFile(String path) throws IOException{ 138 | BufferedReader br = new BufferedReader(new FileReader(path)); 139 | 140 | String var2; 141 | try{ 142 | var2 = br.readLine(); 143 | } catch (Throwable var5) { 144 | try{ 145 | br.close(); 146 | } catch(Throwable var4) { 147 | var5.addSuppressed(var4); 148 | //close할 때 발생하는 예외를 계속 추가해줌 149 | } 150 | 151 | throw var5; 152 | } 153 | 154 | br.close(); 155 | return var2; 156 | } 157 | 158 | public static void main(String[] args) throws IOException{ 159 | String path = args[0]; 160 | System.out.println(firstLineOfFile(path)); 161 | } 162 | } 163 | ``` 164 | 165 | - resource 반납을 위해 finally 구문을 쓰지 않고 중첩된 try-catch 문을 쓰고 있다. 166 | - 첫 번째 에러를 그냥 던져주고, 추가적으로 발생하는 에러를 addSuppresed로 추가해줌. 167 | -------------------------------------------------------------------------------- /3장_모든_객체의_공통_메서드/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |---------------------------------------------------|------|------| 3 | | 10. equals는 일반 규약을 지켜 재정의하라 | 소정 | 윤희 | 4 | | 11. equals를 재정의하려거든 hashCode도 재정의하라 | 영진 | 세용 | 5 | | 12. toString을 항상 재정의하라 | 영준 | 사욱 | 6 | | 13. clone 재정의는 주의해서 진행하라 | 사욱 | 지영 | 7 | | 14. Comparable을 구현할지 고민하라 | 영준 | 영진 | 8 | -------------------------------------------------------------------------------- /3장_모든_객체의_공통_메서드/item11.md: -------------------------------------------------------------------------------- 1 | # Item11. equals를 재정의하려거든 hashCode도 재정의하라 2 | 3 | ## Object 의 HashCode 규약 4 | 5 | - 애플리케이션이 유지되는 동안 equals비교에 사용되는 정보(핵심필드)가 유지된다면, hashCode는 항상 같은값을 반환해야한다 6 | 7 | - **equals() 로 두 객체를 같다고 판단했다면 두 객체의 hashCode는 똑같은 값을 반환해야한다** 8 | 9 | - equals()가 두 객체를 다르다고 판단했더라도, hashCode를 각각 다른값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다 10 | 11 | 12 | ## 왜 hashCode도 재정의 해야되는지 13 | 14 | - equals() 를 재정의 해서 물리적으로 다른 두 객체를 논리적으로 같다고 할수있다. 15 | 이경우 equals() 로 두객체를 같다고 판단하는데 hashCode는 다른값을 가지게되어 **규약을 어기게된다** 16 | 17 | 18 | ```java 19 | public class PhoneNumber { 20 | private final int areaCode, prefix, lineNum; 21 | 22 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 23 | this.areaCode = areaCode; 24 | this.prefix = prefix; 25 | this.lineNum = lineNum; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | PhoneNumber that = (PhoneNumber) o; 33 | return areaCode == that.areaCode && prefix == that.prefix && lineNum == that.lineNum; 34 | } 35 | 36 | public static void main(String[] args) { 37 | Map m = new HashMap<>(); 38 | PhoneNumber phoneNumber = new PhoneNumber(707, 867, 5309); 39 | PhoneNumber phoneNumber2 = new PhoneNumber(707, 867, 5309); 40 | System.out.println(phoneNumber2.equals(phoneNumber)); // true 41 | 42 | m.put(phoneNumber, "제니"); 43 | System.out.println(m.get(phoneNumber2)); // null 44 | } 45 | ``` 46 | 47 | 48 | ## hashCode 재정의하는 최악의 방법 49 | ```java 50 | @Override 51 | public int hashCode() { 52 | return 42; 53 | } 54 | ``` 55 | #### 특정 int값을 반환하는 방법 56 | 57 | - 불가능한것은 아니지만 성능에 악영향을 준다 58 | 59 | - 42라는 똑같은 값만 내주어 해시버킷하나에 노드들이 쌓이게 되어 연결리스트처럼 동작한다 60 | => 해시코드가 42인 객체들의 노드가 버킷에 쌓이게 되고 연결리스트로 버킷을 관리하게되어 조회시 속도가 O(1) -> O(N)으로 느려진다 61 | 62 | > 자바8부터는 연결리스트의 노드가 8개 이상이되면 그때부터 레드블랙트리로 관리합니다 O(LogN) 63 | 64 | 65 | ## hashCode 작성요령 66 | 67 | > 좋은 해시 함수는 같지 않은 인스턴스에 대해 같지 않은 해시 코드를 생성해야 한다 68 | 이상적으로 해시 함수는 모든 int 값에 균일하지 않은 인스턴스의 합당한 컬렉션을 배포해야 한다. 69 | 70 | - 객체의 핵심필드 하나의 해시코드 값을 구해서 result라는 변수에 초기화시킨다 71 | 72 | - 객체의 필드 타입에 따라 Type.hashCode(필드명)으로 수행한다 (Type은 Wrapper class) 73 | 74 | - 참조타입인경우 그 참조타입의 hashCode()를 호출한다 75 | 76 | - 배열인경우 핵심원소 각각을 필드처럼 다룬다 77 | 78 | 위의 과정에서 계산한 해시코드로 result를 갱신한다 79 | > result = 31 * result + c(핵심필드로 계산한 해시코드) 80 | 81 | #### 곱숫자가 31인이유 82 | 83 | - 홀수이면서 소수이며 빠르게 계산할수있다 84 | - 31N = 32N - N 으로 32N- N은 (N<<5) - N과 같아 빠르게 계산할수있다. 85 | 86 | 87 | ## hashCode 지연초기화 88 | ```java 89 | private int hashCode; 90 | 91 | @Override 92 | public int hashCode() { 93 | int result = hashCode; 94 | if (result == 0) { 95 | result = Integer.hashCode(areaCode); 96 | result = 31 * result + Integer.hashCode(prefix); 97 | result = 31 * result + Integer.hashCode(lineNum); 98 | hashCode = result; 99 | } 100 | return result; 101 | } 102 | ``` 103 | - 해시코드를 항상 계산하는것도 비용이기때문에 캐싱방식을 고려할수있다. 104 | 105 | - 위의 코드에선 스레드 안정성까지 고려해야한다. 106 | -------------------------------------------------------------------------------- /3장_모든_객체의_공통_메서드/item12.md: -------------------------------------------------------------------------------- 1 | # item 12. toString을 항상 재정의하라 2 | 3 | ## 1️⃣ toString의 일반 규약 4 | 5 | ### 1-1. 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환해야 한다. 6 | 7 | - Object의 기본 toString : `클래스_이름@16진수_해시코드` 8 | - 유익하지 않은 경우가 대부분이다. 9 | - 예시(전화번호) 10 | ```java 11 | public final class PhoneNumber { 12 | // 지역코드 + 프리픽스 + 가입자번호 13 | private final short areaCode, prefix, lineNum; 14 | // 생성자 생략 15 | 16 | @Override 17 | public String toString() { 18 | return String.format("%03d-%03d-%04d", 19 | areaCode, prefix, lineNum); 20 | } 21 | } 22 | ``` 23 | 24 | ### 1-2. 모든 하위 클래스에서 이 메서드를 재정의하라. 25 | 26 | - 직접 호출하지 않아도, 다른 어딘가에서 충분히 사용될 수 있다. 27 | - 디버거가 객체를 출력 28 | - 문자열 연결 연산 29 | - assert 구문 30 | - 객체를 참조하는 컴포넌트가 오류 메시지를 로깅할 때 31 | 32 | > 상황에 맞는 적절한 toString을 항상 재정의하는 것이 일반 규약이라고 한다. 33 | 34 | 35 | ## 2️⃣ 재정의 Guide 36 | 37 | ### 2-1. 어떤 필드(정보)를 출력할까 38 | 39 | - 보통은 객체가 가진 `주요 정보`를 모두 반환하는게 좋다. 40 | - 너무 많다면 `요약 정보`를 담자. 41 | 42 | > 실무에서는 외부에 노출할 수 있는 데이터만 반환해야한다. 43 | 44 | ### 2-2. 반환값의 포맷을 문서화할까 45 | 46 | - 포맷을 명시해도 되고 안해도 된다. (`개발자의 의도를 명확하게 밝히면 됨`) 47 | - 문서화할 경우 정적 팩터리나 생성자를 함께 제공하자. 48 | - 문서화 장점 49 | - 명확하고, 가독성이 좋음(표준화) 50 | - 데이터 객체로 저장도 가능 51 | - 문서화 단점 52 | - 포맷에 얽매이게 됨(의존적이게 된다) 53 | - 변경과 확장에 불리 54 | 55 | ```java 56 | /** 57 | * 이 전화번호의 문자열 표현을 반환한다. 58 | * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 59 | * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. 60 | * 각각의 대문자는 10진수 숫자 하나를 나타낸다. 61 | * 62 | * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, 63 | * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 64 | * 전화번호의 마지막 네 문자는 "0123"이 된다. 65 | */ 66 | @Override public String toString() { 67 | return String.format("%03d-%03d-%04d", 68 | areaCode, prefix, lineNum); 69 | } 70 | 71 | // 정적 팩터리도 함께 제공 72 | public static PhoneNumber of(String phoneNumberString) { 73 | String[] split = phoneNumberString.split("-"); 74 | PhoneNumber phoneNumber = new PhoneNumber( 75 | Short.parseShort(split[0]), 76 | Short.parseShort(split[1]), 77 | Short.parseShort(split[2])); 78 | return phoneNumber; 79 | } 80 | ``` 81 | 82 | ### 2-3. 반환한 값에 포함된 정보를 API로 제공하면 좋다 83 | 84 | - toString의 반환 값을 getter와 같은 `정보 접근 API를 제공`하자 85 | 86 | ```java 87 | public short getAreaCode() { 88 | return areaCode; 89 | } 90 | 91 | public short getPrefix() { 92 | return prefix; 93 | } 94 | 95 | public short getLineNum() { 96 | return lineNum; 97 | } 98 | ``` 99 | 100 | ### 2-4. 예외(재정의 필요 없음) 101 | 102 | - 상위 클래스에서 알맞게 재정의한 경우(Enum 클래스 등) 103 | - 정적 유틸리티 클래스 104 | 105 | ### 2-5. 자동 생성에 의존해도 될까? 106 | 107 | - 자동 생성 가능한 종류 108 | - AutoValue 프레임워크, IDE, Lombok 프레임워크 109 | 110 | > 위 PhoneNumber 클래스와 같이 직접 재정의하는게 의미있는 경우가 더 많기 때문에 자동 생성에 의존하지 않는게 좋다. 111 | 112 | ## 3️⃣ 핵심 정리 113 | 114 | - toString은 필요한 주요 정보를 사람이 읽기쉽게 출력하도록 재정의하자 115 | - 값 클래스라면 포맷을 문서에 명시하는 것이 좋으며 해당 포맷으로 객체를 생성할 수 있는 정적 팩터리나 생성자를 제공하는 것이 좋다. 116 | - toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하라.(getter) 117 | - 경우에 따라 자동 생성보다 직접 재정의하는게 적절할 수 있다. 118 | - 상위 클래스에서 적절히 재정의하였다면, 재정의할 필요 없다. 119 | -------------------------------------------------------------------------------- /3장_모든_객체의_공통_메서드/item13.md: -------------------------------------------------------------------------------- 1 | # 아이템 13: clone 재정의는 주의해서 진행하라 2 | 3 | 4 | ## Cloneable 인터페이스란? 5 | 6 | 복제해도 되는 클래스임을 명시하는 믹스인 인터페이스이다. 7 | 8 | ### 믹스인 클래스란? 9 | 10 | ```java 11 | public interface Singer { 12 | String sing(String s); 13 | } 14 | 15 | interface Songwriter { 16 | String compose(boolean hit); 17 | } 18 | 19 | interface SingerSongwriter extends Singer, Songwriter { 20 | void actSensitive(); 21 | } 22 | 23 | ``` 24 | 25 | ```java 26 | public interface Cloneable{ 27 | } 28 | ``` 29 | 30 | Cloneable을 상속 받아 만든 모습 31 | 32 | ```java 33 | public class Food implements Cloneable { 34 | 35 | @Override 36 | public Food clone() { 37 | try { 38 | return (Food) super.clone(); 39 | } catch (CloneNotSupportedException e) { 40 | throw new AssertionError(); 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | Object 내에 있는 clone 47 | 48 | ```java 49 | protected native Object clone() throws CloneNotSupportedException; 50 | ``` 51 | 52 | ## Clone의 문제점 53 | 54 | ### 허술한 일반 규약 55 | 56 | - x.clone() != x (복사한 객체는 원본 객체와 독립적) 57 | - x.clone().getClass() == x.getClass() (복사한 객체와 원본 객체는 같은 클래스) 58 | - x.clone().equals(x) (clone() 은 super.clone() 을 통해 객체를 얻어서 반환한다.) 59 | 60 | 위 조건은 필수가 아닌 `선택 사항` 이다. 61 | 62 | 이런 강제성이 없는 형태는 생성자 연쇄 패턴과 매우 유사하다 63 | 64 | => 생성자 연쇄 패턴이란? 65 | 66 | - 어떤 클래스에서 clone()을 new 키워드를 통해서 생성자를 생성했다 67 | - 컴파일러는 당연 문제가 없기에 잡아 주지 않는다. 68 | - 하지만 해당 클래스를 상속한 클래스에서 super.clone()을 하게 된다면, 하위 클래스의 타입이 아닌 상위 클래스의 타입이 만들어져 잘 작동하지 않는다. 69 | 70 | 71 | 72 | ### 해결 방법 73 | 74 | 1. super.clone()을 호출한다. 75 | 2. 그렇게 얻은 객체는 완벽한 복제본 76 | 3. 모든 필드가 기본 타입이거나, 불변 객체를 참조한다면 이 객체는 더욱 완벽하다 (그러나 불변클래스를 굳이 clone을 해야하나? 제공안하는게 좋다) 77 | 78 | 79 | ## 가변 객체를 참조하는 문제 80 | 81 | 가변 객체를 참조하는 순간 끔찍한 일이 벌어진다. 82 | 83 | ```java 84 | public class Stack { 85 | private Object[] elements; 86 | private int size = 0; 87 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 88 | //... 89 | } 90 | ``` 91 | 92 | 해당 클래스에 clone을 이용하여 그대로 복제를 하게 된다면, 93 | 94 | size의 경우 올바른 값을 갖게되지만, elements 필드의 경우 원본 Stack 인스턴스와 똑같은 배열을 참조하게 된다. 95 | 96 | 즉 다른 하나를 수정하면 다른 하나도 수정이 된다는 뜻이다. 97 | 98 | 그래서 원본 객체에 아무런 영향을 끼치지 않도록 보장해야한다. 99 | 100 | ### 해결 방법 101 | 102 | ### 1. clone을 재귀적으로 호출해주는 방법이다. 103 | 104 | ```java 105 | @Override 106 | public Stack clone() { 107 | try { 108 | Stack result = (Stack) super.clone(); 109 | result.elements = elements.clone(); 110 | return result; 111 | } catch (CloneNotSupportedException e) { 112 | throw new AssertionError(); 113 | } 114 | } 115 | ``` 116 | 117 | ### 2. 연결리스트를 재귀적으로 복사 118 | 119 | 재귀적은 clone() 호출만으로는 부족 할 수 있는데 이때 DeepCopy를 이용한다. 120 | ```java 121 | public Entry deepCopy() { 122 | return new Entry(key, value, 123 | next == null ? null : next.deepCopy()); 124 | } 125 | ``` 126 | 127 | 하지만 위 방식은 원소 수만큼 연결리스트를 만드므로, 원소 수만큼의 스텍프레임을 소비하여, 스텍오버 플로우가 발생한다 128 | 129 | 그럴땐 반복자를 이용하여 구한다. 130 | 131 | ### 3. 연결리스트를 반복적으로 복사 132 | 133 | ```java 134 | public Entry deepCopy() { 135 | Entry result = new Entiry(key, value, next); 136 | for (Entry p = result; p.next != null; p p.next) 137 | p.next = new Entry(p.next.key, p.next.value, p.next.next); 138 | return result; 139 | } 140 | ``` 141 | deepCopy를 재귀적으로 호출 하는 대신 반복자를 쓰도록 순회하여 StackOverFlow가 발생하지 않도록 한다. 142 | 143 | 144 | ## 복제가 필요한 클래스 생성시 주의사항 145 | 146 | 1. 상속용 클래스는 cloneable을 구현해서는 안된다. 147 | 2. Object의 방식을 모방하여 clone() 메서드를 구현해 protected로 두고 ClassNotSupportedException을 던지도록 선언한다 148 | 1. cloneable 구현 여부를 하위클래스에 선택하도록 여지를 준다. 149 | 3. cloneable을 구현한 thread-safe한 클래스를 작성할 때는 clone 메서드는 적절히 동기화 해줘야한다. 150 | → Object의 clone 메서드는 동기화를 신경 쓰지 않는다. 그러므로 동기화 해줘야한다. 151 | 152 | ## 내용 정리 153 | 154 | 1. Cloneable을 구현 하는 모든 클래스는 clone을 재정의 해야하며, 155 | 2. 이 때 접근 제한자는 public 이여야 하며 반환 타입은 자기 자신 이여야한다. 156 | 3. 기본타입 필드와 불변 객체 참조만 갖는 클래스라면 아무 필드도 수정할 필요는 없지만, 일련번호나 고유 ID는 기본타입이고, 불변이겠지만 고유한 값이므로, 수정해줘야한다. 157 | 158 | ## 다른 방법은 없는가? 159 | 160 | 복사생성자와 복사 팩터리라는 객체 복사 방식을 사용해보자. 161 | 162 | ### 복사 생성자란 ? 163 | 단순히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자를 말한다. 164 | 165 | ```java 166 | public Yum(Yum yum) { ... } ; 167 | ``` 168 | 169 | ### 복사 팩터리란? 170 | 171 | ```java 172 | public static Yum newInstance(Yum yum) { ... } ; 173 | ``` 174 | 175 | 위 두방식은 176 | 177 | 1. 객체 생성 매커니즘을 사용하지 않아도 될 뿐만 아니라 178 | 2. 엉성하게 문서화 된 규약에 기대지 않아도 된다 179 | 3. 정상적인 final 필드 용버ㅓㅂ과 충돌하지 않는다 180 | 4. 불필요한 예외 검사를 던지지도 않는다. 181 | 5. 형변환도 하지 않아도 된다. 182 | 6. 인터페이스 타입의 인스턴스를 인수로 받을 수 있다. (HashSet 객체를 TreeSet객체로 복제 할 수 있다.) -------------------------------------------------------------------------------- /3장_모든_객체의_공통_메서드/item14.md: -------------------------------------------------------------------------------- 1 | # item 14. Comparable을 고려하라 2 | 3 | > **알파벳, 숫자, 연대, 번호 등 순서가 있는 값 클래스를 작성하면 반드시 Comparable 를 구현하자.** 4 | 5 | - Comparable 인터페이스는 compareTo() 라는 하나의 메서드를 정의한다. 6 | - 이 메서드는 Object 메서드가 아니다. 7 | - 이 메서드의 성격은 Object의 equals와 유사하지만 아래와 같은 차이가 있음 8 | - 동치성 비교 뿐 아니라, 순서까지 비교가 가능 9 | - 제네릭하다 10 | 11 | > Comparable을 구현했다는 것은 순서가 있음을 뜻함 12 | 13 | - 자바에서 제공하는 모든 값 클래스와 열거 타입이 Comparable을 구현했다. 14 | - 수많은 제네릭 알고리즘과 컬렉션의 힘을 누릴 수 있음. 15 | 16 | ## 1️⃣ compareTo 규약 17 | 18 | compareTo 메서드의 일반 규약은 equals과 비슷하다. 19 | 20 | Comparable 을 구현한 객체는 다음 규약들을 지켜야 한다. 21 | 22 | **특징** 23 | 24 | > 해당 객체와 주어진(매개변수) 객체의 순서를 비교한다. 25 | 26 | - 해당 객체가 더 크다면 **양수**를 반환 27 | - 해당 객체가 더 작다면 **음수**를 반환 28 | - 해당객체와 주어진 객체가 같을경우 **0**을 반환 29 | - 해당 객체와 비교할 수 없는 타입의 객체가 전달되면 `ClassCastException`이 발생 30 | 31 | ### 1-1. 대칭성 보장 32 | 33 | - 모든 x, y클래스에 대해서 `sgn(x.compareTo(y) == -sgn(y.compareTo(x))` 이다. 34 | - `x.compareTo(y)`는 `y.compareTo(x)`가 예외를 던질 때에 한해서 예외가 발생해야 한다. 35 | 36 | ```java 37 | BigDecimal n1 = BigDecimal.valueOf(23134134); 38 | BigDecimal n2 = BigDecimal.valueOf(11231230); 39 | 40 | System.out.println(n1.compareTo(n2)); 41 | System.out.println(n2.compareTo(n1)); 42 | ``` 43 | 44 | ### 1-2. 추이성 보장 45 | 46 | 객체 `x`, `y`, `z`가 있다고 할 때 47 | 48 | - x.compareTo(y)가 양수이고 y.compareTo(z)도 양수라면, x.compareTo(z)도 양수여야한다. 49 | - (x > y && y > z 이면 x > z여야 한다.) 50 | - x.compareTo(y) == 0 일 때 sgn(x.compareTo(z)) == sgn(y.compareTo(z))이어야 한다. 51 | 52 | ### 1-3. 반사성 보장 (equlas 규약과 동일) 53 | 54 | - null이 아닌 모든 참조 값(n1)에 대해 아래 n1.compareTo(n1)은 true 55 | 56 | ```java 57 | BigDecimal n1 = BigDecimal.valueOf(23134134); 58 | 59 | System.out.println(n1.compareTo(n1)); 60 | ``` 61 | 62 | ### 1-4. 동치성 결과가 equals와 같아야 한다. 63 | 64 | - x.compareTo(y) == 0 일 때 x.equals(y)어야 한다. 65 | 66 | ```java 67 | BigDecimal oneZero = new BigDecimal("1.0"); 68 | BigDecimal oneZeroZero = new BigDecimal("1.00"); 69 | System.out.println(oneZero.compareTo(oneZeroZero)); // Tree, TreeMap 70 | System.out.println(oneZero.equals(oneZeroZero)); // 순서가 없는 콜렉션 71 | ``` 72 | 73 | ```bash 74 | 0 75 | false 76 | ``` 77 | 78 | 같지 않다면 문서화하는 것을 권장한다. 79 | 80 | ![image](https://user-images.githubusercontent.com/42997924/192532863-d816b84c-8ba9-4cdd-9d29-ea63d3a9e9c3.png) 81 | 82 | 83 | ### 1-5. equals 규약과의 차이점 정리 84 | 85 | - `대칭성`, `추이성`, `반사성` 규약은 equals와 규약들이 비슷하다. 86 | - 하지만, 모든 객체에 대해 전역 동치관계를 부여하는 equals와 다르게 compareTo는 타입이 다른 객체를 신경쓰지 않아도 된다. 87 | - 다를 경우 `ClassCastException`을 던지면 된다. 88 | 89 | ## 2️⃣ 작성 요령 90 | 91 | - equals와 비슷하지만, Comparable은 타입을 인수로 받는 `제네릭 인터페이스`라서 compareTo 메서드의 인수 타입은 `컴파일 타임`에 정해진다. 92 | - 입력 인수의 타입을 확인 & 형변환 할 필요가 없다는 의미이다. 93 | 94 | - compareTo 메서드는 각 필드의 동치관계를 보는게 아니라 **그 순서를 비교**한다. 95 | - 객체 참조 필드를 비교하려면 compareTo 메서드를 재귀적으로 호출한다. 96 | - Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 할 경우 Comparator를 쓰면 된다. 97 | - Sample Code 98 | 99 | ```java 100 | @Getter 101 | public final class PhoneNumber implements Comparable { 102 | private short areaCode, prefix, lineNum; 103 | 104 | // 기본 타입 필드가 여럿일 때의 비교자 (91page) 105 | @Override 106 | public int compareTo(PhoneNumber pn) { 107 | int result = Short.compare(areaCode, pn.areaCode); 108 | if (result == 0) { 109 | result = Short.compare(prefix, pn.prefix); 110 | if (result == 0) 111 | result = Short.compare(lineNum, pn.lineNum); 112 | } 113 | return result; 114 | } 115 | } 116 | ``` 117 | 118 | - 중요도에 따라 우선순위로 비교 가능 119 | 120 | ### 2-1. 비교자 생성 메서드(comparator construction method) 121 | 122 | - 자바 8 부터는 Comparator 인터페이스가 비교자 생성 메서드를 이용해 `메서드 연쇄 방식`으로 비교자를 생성할 수 있게 되었다. 123 | - 방식은 간결하지만 성능은 떨어진다. 124 | 125 | ```java 126 | public final class PhoneNumber implements Comparable { 127 | private short areaCode, prefix, lineNum; 128 | 129 | // 비교자 생성 메서드를 활용한 비교자 (92page) 130 | private static final Comparator COMPARATOR = 131 | comparingInt((PhoneNumber pn) -> pn.areaCode) 132 | .thenComparingInt(pn -> pn.getPrefix()) 133 | .thenComparingInt(pn -> pn.lineNum); 134 | // 135 | // @Override 136 | // public int compareTo(PhoneNumber pn) { 137 | // return COMPARATOR.compare(this, pn); 138 | // } 139 | } 140 | ``` 141 | 142 | - 자바에서 타입추론을 제대로 하지 못하기 때문에 명시해 줄 필요가 있다. 143 | 144 | ### 2-2. 정적 메서드 혹은 비교자 생성 메서드를 활용하자. 145 | 146 | 객체간 순서를 정한다고 해시코드를 기준으로 정렬하기도하는데 단순히 첫 번째 값이 크면 양수, 같으면 0, 첫 번째 값이 작으면 음수를 반환한다는 것만 생각해서 다음과 같이 작성을해선 안된다. 147 | 148 | ```java 149 | static Comparator hashCodeOrder = new Comparator<>() { 150 | public int compare(Object 01, Object 02) { 151 | return ol.hashCode() - o2.hashCode(); 152 | } 153 | } 154 | ``` 155 | 156 | 이런 방식은 얼핏보면 문제 없을 것 같지만 정수 오버플로 혹은 IEEE754 부동소수점 계산 방식에 따른 오류를 낼 수 있다. 게다가 속도가 엄청 빠르지도 않다. 157 | 158 | 대신, 다음처럼 정적 compare메서드 혹은 비교자 생성 메서드를 활용해보자. 159 | 160 | ```java 161 | static Comparator() { 162 | public int compare(Object o1, Object 02){ 163 | return Integer.compare(01.hashCode(), o2.hashCode()); 164 | } 165 | } 166 | ``` 167 | 168 | ```java 169 | static Comparator hashCodeOrder = 170 | Comparator.comparingInt(Object::hashCode); 171 | ``` 172 | 173 | ## 3️⃣ Comparable 구현 클래스를 확장할 때 주의점 174 | 175 | > 기존 클래스를 확장한 구체 클래스에서 새로운 값 컴포넌트를 추가했다면 compareTo 규약을 지킬 방법이 없다. 176 | 177 | 객체 지향적 추상화의 이점을 포기해서 `뷰 메서드`를 활용해 우회한 코드는 아래와 같다. 178 | 179 | `Point 클래스` 180 | 181 | ```java 182 | class Point implements Comparable { 183 | private int x; 184 | private int y; 185 | 186 | public Point(int x, int y) { 187 | this.x = x; 188 | this.y = y; 189 | } 190 | 191 | @Override 192 | public int compareTo(Point point) { 193 | int result = Integer.compare(x, point.x); 194 | if (result == 0) { 195 | return Integer.compare(y, point.y); 196 | } 197 | return result; 198 | } 199 | } 200 | ``` 201 | 202 | `NamedPoint 클래스` 203 | 204 | ```java 205 | class NamedPoint implements Comparable { 206 | // 원래 클래스의 인스턴스를 가르키는 필드 207 | private Point point; 208 | 209 | private String name; 210 | 211 | public NamedPoint(Point point, String name) { 212 | this.point = point; 213 | this.name = name; 214 | } 215 | // 내부 인스턴스를 반환하는 '뷰' 메서드 216 | public Point viewPoint() { 217 | return point; 218 | } 219 | 220 | @Override 221 | public int compareTo(NamedPoint namePoint) { 222 | int result = point.compareTo(namePoint.point); 223 | if (result == 0) { 224 | return name.compareTo(namePoint.name); 225 | } 226 | return result; 227 | } 228 | } 229 | ``` 230 | 231 | 이렇게 하면 NamedPoint 클래스에 원하는 compareTo 메서드를 새롭게 구현할 수 있게 되어 구체 클래스 Point에서 compareTo 일반 규약을 지킬 수 있게 된다. 232 | 233 | Test 코드로 검증하면 아래와 같다. 234 | 235 | ```java 236 | class PointTest { 237 | 238 | @DisplayName("compareTo의 일반 규약을 지킬 수 있다") 239 | @Test 240 | void test() { 241 | Point point = new Point(1, 3); 242 | NamedPoint namedPoint = new NamedPoint(new Point(1, 2), "food"); 243 | 244 | assertThat(point.compareTo(namedPoint.viewPoint())).isEqualTo(1); 245 | assertThat(namedPoint.viewPoint().compareTo(point)).isEqualTo(-1); 246 | } 247 | 248 | } 249 | ``` 250 | 251 | ## 4️⃣ 정리 252 | 253 | - 순서를 고려해야하는 값 클래스는 Comparable인터페이스를 꼭 구현하면 좋다. 254 | - compareTo 메서드에서는 `<` , `>`같은 연산자는 쓰지 않아야 한다. 255 | - 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 활용하자. 256 | -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |-----------------------------------------------|-----|-----| 3 | | 15. 클래스와 멤버의 접근 권한을 최소화하라 | 소정 | 윤희 | 4 | | 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 | 세용 | 승재 | 5 | | 17. 변경 가능성을 최소화하라 | 승재 | 사욱 | 6 | | 18. 상속보다는 컴포지션을 사용하라 | 영진 | 소정 | 7 | | 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 | 영준 | 세용 | 8 | | 20. 추상 클래스보다는 인터페이스를 우선하라 | 윤희 | 지영 | 9 | | 21. 인터페이스는 구현하는 쪽을 생각해 설계하라 | 지영 | 영준 | 10 | | 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 | 소정 | 사욱 | 11 | | 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 | 세용 | 윤희 | 12 | | 24. 멤버 클래스는 되도록 static으로 만들라 | 영진 | 승재 | 13 | | 25. 톱레벨 클래스는 한 파일에 하나만 담으라 | 사욱 | 윤희 | 14 | -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item16.md: -------------------------------------------------------------------------------- 1 | # Item.16 pulibc 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 2 | --- 3 | 4 | 필드만 정의된 것 외에는 아무 목적도 없는 클래스를 작성하는 경우가 있을 것이다 5 | 6 | ```java 7 | public class Point { 8 | public double x; 9 | public double y; 10 | } 11 | ``` 12 | 이런 클래스는 외부에서 필드로 직접 접근 할 수 있어 캡슐화의 이점을 제공하지 못한다 13 | 또한, 14 | - 클라이언트 코드 수정 없이는 표현 방식을 바꿀 수도 없고 15 | - 불변식을 보장할 수도 없으며 16 | - 부수 작업을 수행할 수도 없다 17 | 18 | ### public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하자 19 | 위 클래스의 필드를 모두 private로 바꾸고 public 접근자를 추가해보자 20 | ```java 21 | public class Point { 22 | 23 | private double x; 24 | private double y; 25 | 26 | public Point() { 27 | } 28 | 29 | public Point(double x, double y) { 30 | this.x = x; 31 | this.y = y; 32 | } 33 | 34 | public double getX() { 35 | return x; 36 | } 37 | 38 | public double getY() { 39 | return y; 40 | } 41 | 42 | public void setX(double x) { 43 | this.x = x; 44 | } 45 | 46 | public void setY(double y) { 47 | this.y = y; 48 | } 49 | } 50 | ``` 51 | 위 코드처럼 패키지 바깥에서 접근할 수 있는 클래스라면 접근자를 제공함으로써 클래스 내부 표현 방식을 언제든 바꿀 수 있는 유연성을 갖게 하는 것이 좋다 52 | 53 | ### package-private 클래스 혹은 private 중첩 클래스라면? 54 | package-private 클래스 혹은 private 중첩 클래스에서 데이터 필드를 노출하는 것은 본질적으론 문제가 없다 55 | 56 | package-private 클래스에서 public으로 필드를 노출하는 경우 동일 패키지 내에서 접근할 때 더 간결하다는 장점이 있다 57 | 또한 만약 변경되는 것이 바람직하다면 패키지 외부의 소스 수정 없이 동일 패키지 내에서만 수정을 하면 된다 58 | (그 패키지가 엄청 커서 수정을 많이 해야된다면, 패키지 분리를 잘 하지 못한거겠지?) 59 | 60 | private 클래스의 경우 '해당 클래스에서만'이라는 더 강한 제약이면서 해당 클래스에서만 수정하면 된다 61 | 62 | 63 | ### Java awt 패키지의 Point와 Dimension 64 | java.awt 패키지의 Point와 Dimension 클래스를 타산지석으로 삼자. 65 | 66 | 67 | ### 불변 필드는 public으로 해도 괜찮은가? 68 | 불변 필드의 경우 불변식은 보장할 수 있지만, 69 | 클라이언트 코드를 수정하지 않고선 API 표현을 바꾼다거나 부수적인 작업을 할 수 없다는 단점은 여전히 존재한다 70 | -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item17.md: -------------------------------------------------------------------------------- 1 | # Item 17. 변경 가능성을 최소화하라 2 | 3 | > 불변 클래스란? 4 | 5 | 해당 인스턴스의 내부 값을 수정할 수 없는 클래스 6 | 7 | 불변 인스턴스에 간직된 정보는 객체가 파괴되는 순간까지 절대 달라지지 않는다. 8 | 9 | 자바 플랫폼 라이브러리에도 다양한 불변 클래스가 있다. 10 | 11 | String, 기본타입의 박싱된 클래스, BigInteger, BigDecimal 등등이 있다. 12 | 13 | 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉽다! 14 | 15 | 오류 생길 여지도 적어 안전하다. 16 | 17 | ### 불변 클래스로 만드는 다섯가지의 규칙 18 | 19 | 1. 객체의 상태를 변경하는 메소드를 제공하지 않는다. 20 | 1. 흔히 구현하는 setter + @ 21 | 2. 클래스를 확장할 수 없도록 한다. 22 | 1. 하위 클래스에서 나쁜 의도로 객체의 상태를 변하게 만드는 사태를 막아줌 23 | 2. 대표적인 방법으로는 클래스를 `final` 로 선언하는것 24 | 3. 모든 필드를 final로 선언한다. 25 | 1. 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작하게끔 보장한다. 26 | 4. 모든 필드를 private로 선언한다. 27 | 1. 16장에서 봤던 것처럼 가변 객체에 클라이언트가 직접 접근해서 수정하는 것을 막아준다. 28 | 5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 해야한다. 29 | 1. 가변 객체를 참조하는게 하나라도 있다면 클라이언트에서 그 참조를 못얻게 만들어주어야 한다. 30 | 2. 접근자 메소드가 필드를 그대로 반환해서도 안된다. 31 | 32 | 기존의 책에서 복소수 예제로 주어졌지만, 좀 더 간략하게 보기위해 33 | 34 | Money 객체를 이용했다. 35 | 36 | [일반 코드](https://github.com/lsj8367/laboratory/tree/master/effective-java/src/main/java/com/github/lsj8367/item17) 37 | 38 | [테스트 코드](https://github.com/lsj8367/laboratory/tree/master/effective-java/src/test/java/com/github/lsj8367/item17) 39 | 40 | 이러한 기능을 JDK 14이상부터는 [record Class](https://www.baeldung.com/java-record-keyword) 가 있기 때문에 41 | 42 | 클래스에 선언해주면 위에서 구현했던 보일러 플레이팅 과정의 코드들을 record 키워드 하나만으로 대체가 가능하다. 43 | 44 | 함수형에 익숙하지 않다면 부자연스러워 보일 수 있겠지만, 45 | 46 | 불변의 영역 비율이 높아지는 장점을 얻을 수 있다. 47 | 48 | # 불변 객체의 장점 49 | 50 | ### 불변 객체는 단순하다 51 | 52 | 불변 객체는 생성된 시점의 상태를 파괴될 때까지 그대로 간직한다. 53 | 54 | 모든 생성자가 클래스 불변식을 보장한다면 그 클래스를 사용하는 프로그래머가 다른 노력을 들이지 않더라도 영원히 불변으로 남는다. 55 | 56 | > 클래스 불변식이란? 57 | 58 | 버트런드 마이어는 `계약에 의한 설계(Design By Contract)`라는 개념을 제시한다. 59 | 60 | 소프트웨어의 모듈은 권리와 책임을 문서화하고, 그것을 검증하는 것이 핵심. 61 | 62 | 예를 들어서 이전에 주어진 돈에서 돈을 만들때 null로 만들거나 음수로 만들어서는 안된다! 63 | 64 | 그리고 더하거나 뺄때의 인자는 `null` 이거나 음수 값이면 예외를 던지는 것이 권리이다. 65 | 66 | 생성자에서 들어올 수 없는 값이 있다면 전부 예외를 처리해주면 그 클래스는 조건이 맞아 떨어진다고 보증해줄 수 있는 이런것이 바로 클래스 불변식이다. 67 | 68 | **돈은 주어진 돈보다 더 많은 돈을 뺄 수 없다** 라는것을 보장받을 수 있는 클래스 불변식이 생긴다. 69 | 70 | 가변객체는 임의의 복잡한 상태에 놓일 수 있다. 71 | 72 | 변경자 메소드가 일으키는 상태 변화들을 문서로 남겨놓지 않는다면 믿고 사용하기 어려울 수 있다. 73 | 74 | ### 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요가 없다. 75 | 76 | Thread가 동시에 사용해도 훼손되지 않는다. 77 | 78 | 클래스를 가장 Thread-safe 하게 만드는 방법이 이 불변객체로 만드는 방법이다. 79 | 80 | 변경하는 값들이 없으니 서로 다른 스레드에 영향을 줄 수 없으니까 안심하고 공유가 가능하다. 81 | 82 | 불변 클래스라면 한번 만든 인스턴스를 최대한 재활용하기를 권장한다. 83 | 84 | 앞장에서 살펴봤던 `Boolean.valueOf()` 가 될 수있다. 85 | 86 | ### 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩토리를 제공할 수 있다. 87 | 88 | 앞장에서 봤던 아이템1의 내용이다. 89 | 90 | 박싱된 기본 타입 클래스들 전부와 BigInteger 가 여기 속한다. 91 | 92 | 이러한 정적 팩토리를 사용하게 되면 여러 클라이언트가 인스턴스를 **공유** 하게 되어 **메모리 사용량** 과 **GC** 비용이 줄어든다. 93 | 94 | 새로운 클래스를 설계시 public 생성자가 아닌 정적 팩토리를 만들어두게 되면, 나중에 클라이언트를 수정하지 않고도 캐시 기능을 덧붙일 수도 있다. 95 | 96 | ### 방어적 복사도 필요 없다. 97 | 98 | 불변 객체를 자유롭게 공유한다는 점은 이도 된다는 결론이 난다. 99 | 100 | 아무리 복사해봐야 원본과 똑같아 복사 자체가 의미 없다. (Collections.unmodifiableList()) 101 | 102 | [방어적 복사 vs Unmodifiable Collection](https://tecoble.techcourse.co.kr/post/2021-04-26-defensive-copy-vs-unmodifiable/) 103 | 104 | 불변 클래스는 clone이나 복사 생성자를 제공하지 않는게 좋다. 105 | 106 | String 클래스의 복사생성자는 되도록 사용하지 말자. 107 | 108 | ### 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다. 109 | 110 | BigInteger의 negate 메소드 111 | 112 | ![negate메소드](https://user-images.githubusercontent.com/74235102/193304567-9c6c9d12-680a-4c9a-b715-28e23914f50b.png) 113 | 114 | 변경되어야 하는 값 외에 나머지는 그대로 재활용해도 새로 만들어주는 것이기 때문에 115 | 116 | 데이터를 공유해도 안전하단 뜻 같다. 117 | 118 | ### 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다. 119 | 120 | 불변 x 불변이면 아무리 복잡해도 불변식은 계속 유지될 것이다. 121 | 122 | 불변 객체는 Map의 키와 Set 원소로 쓰기 좋다. (hashCode를 재정의한다에서 배웠던 내용) 123 | 124 | 그래서 불변식이 허물어지는 것들을 생각할 필요가 없다. 125 | 126 | ### 불변 객체는 그 자체로 실패 원자성을 제공한다. 127 | 128 | 실패원자성은 어떠한 가변 객체에 대해 값을 처리해준다고 가정하면, 129 | 130 | 그 값에 대해 유효성 검증을 처리하는 역할을 말한다. 131 | 132 | 실패 원자성을 성취하기 위한 더 좋은 방법은, 객체를 변경하는 코드 전에 앞서서 실패할 수 있는 코드 부분들이 실행되도록 순서를 조정 하는것이 좋다. 133 | 134 | # 불변클래스의 단점 135 | 136 | 값이 다르다면 반드시 독립된 객체로 만들어야 한다는 것. 137 | 138 | 값의 가지수가 많다면 이들을 만드는데에 전부 큰 비용을 치러야 한다. 139 | 140 | 원하는 객체를 완성하기까지의 단계가 많고, 그 중간 단계에서 만들어진 객체들이 모두 버려진다면 141 | 142 | 성능 문제가 더 불거진다. 143 | 144 | ### 문제를 대처하는 방법 145 | 146 | > 흔히 쓰일 다단계 연산들을 예측하여 기본기능으로 제공 147 | 148 | 연산 속도를 높이기 위해 가변동반 클래스사용 149 | 150 | `String` 의 가변 동반 클래스인 `StringBuilder` 도 안에 `AbstractStringBuilder` 를 package-private로 두고 있다. 151 | 152 | 마지막에 toString 메소드를 통해 String을 반환해주는 식으로 동작하게 되어있다. 153 | 154 | # 불변 클래스를 만드는 또 다른 설계 방법 155 | 156 | 기존 방식은 클래스를 final로 만드는 것. 157 | 158 | 더 유연한 방법은 모든 생성자를 private로 만들고, 정적 팩토리를 제공하는 방법이다. 159 | 160 | ![demo클래스](https://user-images.githubusercontent.com/74235102/193304589-4286e7ae-4279-43b9-9934-4394e6a13bd3.png) 161 | 162 | ![subdemo클래스](https://user-images.githubusercontent.com/74235102/193304601-e102acaa-ab11-4270-b06a-938d83b53be1.png) 163 | 164 | 다 막아두면 위 이미지와 같이 상속을 받을 수 없다. 165 | 166 | package-private로 구현하게 된다면, 바깥에서는 불변객체지만, 패키지 안에서는 자유롭게 원하는 수준으로 만들 수 있다. 167 | 168 | 때문에 정적 팩토리 방식은 다수의 구현 클래스를 활용한 유연성을 제공한다. 169 | 170 | 위에서 봤던 `BigInteger` 나 `BigDecimal` 을 설계 당시에는 불변 객체가 사실상 final이어야 한다는 생각이 널리 퍼지지 않았다고 한다. 171 | 172 | 그래서 재정의 할 수 있게 설계되어 하위 호환성이 고려된다. 173 | 174 | 그래서 이 타입의 값들을 받을때에는 주의해서 받아야한다. 175 | 176 | > 모든 필드가 final이고 어떤 메소드도 그 객체를 수정할 수 없어야 한다. 177 | 178 | **너무 과하다!!!** 179 | 180 | > 어떤 메소드도 객체의 상태중 외부에 비춰지는 값을 변경할 수 없다. 181 | 182 | 완화된 느낌 183 | 184 | 어떤 불변 클래스는 계산 비용이 큰 값을 나중에 계산하여 final이 아닌 필드에 캐시를 발라놓기도 한다. 185 | 186 | 똑같은 값을 다시 요청하면 캐시해둔 값을 반환하여 계산 비용을 절감하는 것이다. 187 | 188 | Ex) `ObjectMapper` 189 | 190 | ### 직렬화 시 추가로 주의할 점 191 | 192 | `Serializable` 을 구현하는 불변 클래스 내부에 가변 객체를 참조하는 필드가 있다면, 193 | 194 | readObject나 readResolve 메소드를 **반드시** 제공하거나 195 | 196 | Object I/O Stream 의 writeUnshared, readUnshared 메소드를 사용해야 한다. 197 | 198 | # 정리 199 | 200 | getter가 있다고해서 setter를 정의할 필요가 없다. (**무지성 롬복으로 @Getter, @Setter 주의!!** ) 201 | 202 | > 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. 203 | 204 | 그래서 jdk14에서 record부터 구현시작하는게 그런 이유일까 싶기도 하다. 205 | 206 | 불변클래스는 장점이 많으며, 단점은 특정 상황에서의 성능저하. 207 | 208 | 단순 값 객체는 항상 불변으로 만드는 습관을 들이자. 209 | 210 | 무거운 객체들도 불변으로 만들수 있는지 고려해보고, 성능 때문에 안될 것 같다면 211 | 212 | 가변 동반 클래스를 사용하여 제공하는것도 방법이다. 213 | 214 | ## 모든 클래스를 불변으로 만들순 없다. 215 | 216 | 불변으로 만들 수 없다면 변경할 수 있는 부분을 최소한으로 줄이자. 217 | 218 | **꼭 변경해야할 필요가 있는 필드를 제외하고는 모두 final로 선언하자.** 219 | 220 | ## 핵심요약 221 | 222 | - 다른 합당하고 타당한 이유가 없다면 모든 필드는 `private final` 을 붙이자. 223 | - 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다. 224 | - 확실한 이유가 없다면 생성자와 정적 팩토리 외에는 그 어떤 초기화 메소드도 `public` 으로 제공해선 안된다. 225 | - 복잡성만 커지고 성능 이점은 거의 없다. -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item21.md: -------------------------------------------------------------------------------- 1 | 2 | ### 자바 8 이전 3 | 기존 구현체(인터페이스를 직접 구현한 클래스)를 깨뜨리지 않고 인터페이스에 메서드를 추가할 방법이 없었음. 4 | > 인터페이스에 메서드를 추가하면 보통은 컴파일 오류가 나는데, 추가된 메서드가 우연히 기존 구현체에 이미 존재할 가능성이 아주 낮기 때문. 5 | 6 | 7 | ### 자바 8 이후 8 | 기존 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드를 소개했지만 [JLS 9.4], 위험이 완전히 사라진 것은 아니다. 9 | 10 | - 인터페이스는 메서드에 대한 선언만 가능하기 때문에, 실제 로직은 포함될 수 없다. 11 | - 하지만 메소드 선언시 default를 명시하게 되면 인터페이스 내부에서도 동작 코드가 포함된 메서드를 선언 할 수 있다. 12 | - default 키워드는 인터페이스에 메서드를 추가함과 동시에 하위호환성을 유지할 수 있도록 해주는 큰 편의를 제공해준다. 13 | ([출처](https://hyeon9mak.github.io/Effective-Java-item21/)) 14 | 15 | 디폴트 메서드 선언 시 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 16 | > 하지만 모든 기존 구현체들과 연동된다는 보장은 없다. 왜냐하면 디폴트 메서드는 구현 클래스에 대해 아무것도 모르기 때문이다. 17 | 18 | 자바 8에서는 핵심 컬렉션 인터페이스에 다수의 디폴트 메서드가 추가되었다. 람다를 활용하기 위해서이기 때문이다. 19 | > 하지만 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기란 어려운 법. 20 | 21 | #### 예시) 자바 8의 Collection 인터페이스에 추가된 removeIf 메서드 22 | removeIf : 주어진 불리언 함수(predicate)가 true를 반환하는 모든 원소를 제거.
23 | 디폴트 구현은 반복자를 이용해 순회하면서 각 원소를 인수로 넣어 프레디키트 호출, 프레디키트가 true를 반환하려면 반복자의 remove 메서드를 호출해 그 원소 제거. 24 | 25 | ``` 26 | default boolean removeIf(Predicate filter) { 27 | Objects.requireNonNull(filter); 28 | boolean result = false; 29 | for(Iterator it = iterator(); it.hasNext();) { 30 | if(filter.test(it.next())) { 31 | it.remove(); 32 | result = true; 33 | } 34 | } 35 | 36 | return result; 37 | } 38 | ``` 39 | 범용적으로 구현되어 있어, 재정의가 필요없다. 40 | 하지만 현존하는 모든 Collection 구현체와 잘 어우러지는 것은 아님. 41 | 42 | #### org.apache.commons.collections4.collection.SynchronizedCollection 43 | 44 | - java.util의 Collections.synchronizedCollections 정적 패터리 메서드가 반환하는 클래스와 비슷. 45 | - 아파치 버전은 컬렉션 대신 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공. 46 | - 즉, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스 47 | - 하지만, 현 시점에선 removeIf 메서드를 재정의하고 있지 않으므로 이 클래스를 자바 8과 함께 사용할 땐 모든 메서드 호출을 알아서 동기화해주지 못 함. 48 | - removeIf의 구현은 동기화에 관해 아무것도 모르므로 락 객체 사용 불가. 49 | - SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 다른 예기치 못한 결과로 이어진다. 50 | 51 | #### 위 문제를 해결하기 위한 조치 52 | - 구현한 인터페이스의 디폴트 메서드 재정의, 다른 메서드에선 디폴트 메서드를 호출하기 전 필요한 작업 수행 53 | - Collections.synchronizedCollection이 반환하는 package-private 클래스들은 removeIf 재정의, 이를 호출하는 다른 메서드들은 디폴트 구현을 호출하기 전에 동기화하도록 함. 54 | - 하지만 자바 플랫폼에 속하지 않은 제3의 기존 컬렉션 구현체들은 이런 언어 차원이 인터페이스 변화에 발맞춰 수정될 기회가 없었으며, 그 중 일부는 여전히 수정되고 있지 않음. 55 | 56 | #### 디폴트 메서드는 (컴파일에 성공하더라도) 기존 구현체에 런타입 오류를 일으킬 수 있다. 57 | 58 | 자바 8은 컬렉션 인터페이스에 꽤 많은 디폴트 메서드를 추가했고, 그 결과 기존에 짜여진 많은 자바 코드가 영향을 받은 것으로 알려졌다. 59 | 60 | #### 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일을 꼭 필요한 경우가 아니라면 피해야 한다. 61 | 62 | - 추가하려는 디폴트 메서드가 기존 구현체들과 충돌하지 않을 지 심사숙고해야한다. 63 | - 반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 아주 유용하며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게 해준다(아이템 20). 64 | 65 | #### 디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명심 66 | 이런 형태로 인터페이스를 변경하면 반드시 기존 클라이언트를 망가뜨림. 67 | 68 | ## '디폴트 메서드'라는 도구가 생겼더라도 인터페이스를 설계할 땐 여전히 주의! 69 | 70 | - 디폴트 메서드로 기존 인터페이스에 새로운 추가하면 커다란 위험도 딸려온다. 71 | - 심히 잘못된 인터페이스라면 이를 포함한 API에 큰 문제가 생길 수 있음. 72 | 73 | #### 대안 74 | - 새로운 인터페이스라면 릴리스 전에 반드시 테스트하기 75 | - 각 인터페이스의 인스턴스를 다양한 작업에 활용하는 클라이언트도 여러 개 만들기 76 | 77 | #### 결론 78 | 인터페이스를 릴리스한 후 결함 수정이 가능은 하겠지만, 절대로 그 가능성에 기대지 말라. 79 | -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item22.md: -------------------------------------------------------------------------------- 1 | # item 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 2 | --- 3 | 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 4 | 클래스가 어떤 인터페이스를 구현한다 : 인스턴스로 무엇을 할 수 있는지를 클라이언트에 이야기 해주는 것 5 | 6 | 7 | ```java 8 | public interface PhysicalConstants { 9 | // 아보가드로 수 (1/몰) 10 | static final double AVOGADROS_NUMBER = 6.022_140_857e23; 11 | 12 | // 볼츠만 상수 (J/K) 13 | static final double boltzmann_constant = 1.380_648_52e-23; 14 | 15 | // 전자 질량 (kg) 16 | static final double ELECTRON_MASS = 9.109_383_56e-31; 17 | } 18 | ``` 19 | 위와 같이 메서드 없이, 상수를 뜻하는 static final 필드로만 가득 찬 인터페이스의 형태는 상수 인터페이스이다. 20 | 상수 인터페이스 안티패턴은 인터페이스를 잘못 사용한 예이다. 21 | 상수 인터페이스를 구현하는 것은 내부구현을 클래스의 API로 노출하는 행위이다. 22 | 23 | ### 상수를 공개할 목적이라면 24 | 1. 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해야 한다. 25 | ex) 모든 숫자 기본 타입의 박싱 클래스가 대표적으로, Integer와 Double에 선언된 MIN_VALUE와 MAX_VALUE상수가 이런 예다. 26 | 2. 열거 타입으로 나타내기 적합한 상수라면 열거타입으로 만들어 공개한다. 27 | 3. 인스턴스화할 수 없는 유틸리티 클래스에 담아 공개한다. 28 | 아래는 유틸리티 클래스 버전 코드이다. 29 | ```java 30 | public class PhysicalConstants { 31 | 32 | private PhysicalConstants() { } // 인스턴스화 방지 33 | 34 | // 아보가드로 수 (1/몰) 35 | public static final double AVOGADROS_NUMBER = 6.022_140_857e23; 36 | 37 | // 볼츠만 상수 (J/K) 38 | public static final double boltzmann_constant = 1.380_648_52e-23; 39 | 40 | // 전자 질량 (kg) 41 | public static final double ELECTRON_MASS = 9.109_383_56e-31; 42 | } 43 | ``` 44 | 45 | 유틸리티 클래스에 정의된 상수를 클라이언트에서 사용하려면 클래스 이름까지 함꼐 명시해야 한다. 46 | 유틸리티 클래스의 상수를 빈번히 사용한다면 정적 임포트 (static import)하여 클래스 이름은 생략할 수 있다. 47 | ```java 48 | import static effective_java.item_22.PhysicalConstants.*; 49 | 50 | public class Test { 51 | 52 | double atoms(double mols) { 53 | return AVOGADROS_NUMBER * mols; 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item24.md: -------------------------------------------------------------------------------- 1 | # Item 24. 멤버 클래스는 되도록 static 으로 만들라 2 | 3 | 중첩클래스 4 | 5 | > 다른 클래스 안에 정의된 클래스 6 | 7 | - 중첩 클래스는 자신을 감싼 바깥 클래스에서만 사용되어야한다 8 | - 불필요한 노출을 줄여 캡슐화 할수있고, 가독성, 유지보수하기 좋은코드를 작성할수있다. 9 | 10 | ### 종류 11 | - static 멤버 클래스 12 | - non-static 멤버 클래스 13 | - 익명 클래스 14 | - 지역 클래스 15 | 16 | ## static 멤버 클래스 17 | 18 | - 바깥 클래스의 private 멤버에 접근할수있다 19 | 20 | ```java 21 | public class OuterClass { 22 | private String outerClassName = "MyClass"; 23 | 24 | //정적 멤버 클래스 25 | private static class PrivateStaticClass { 26 | void print() { 27 | OuterClass outerClass = new OuterClass(); 28 | System.out.println(outerClass.outerClassName); 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ### 바깥 클래스와 함께 쓰일때 유용하게 사용될수있다. 35 | - public 도우미 클래스로 쓰인다 36 | - Calculator.Operation.PLUS 같은 형태로 원하는 연산을 참조할수있다. 37 | 38 | 39 | ```java 40 | public class Calculator { 41 | public static enum Operation { 42 | PLUS("+", (x, y) -> x + y), 43 | MINUS("-", (x, y) -> x - y); 44 | } 45 | } 46 | ``` 47 | 48 | 49 | ## non-static 멤버 클래스 50 | 51 | - 비정적 멤버클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다 52 | 53 | - 바깥 인스턴스 없이 생성할수없다. 54 | 55 | 56 | ```java 57 | public class OuterClass { 58 | private String className = "MyClass"; 59 | 60 | // 비정적 61 | public class nonStaticClass { 62 | private String className = "nonStatic"; 63 | 64 | void print() { 65 | OuterClass.this.outerPrint(); //정규화된 this로 바깥인스턴스의 메서드를 호출 66 | } 67 | } 68 | 69 | // 바깥 인스턴스의 메서드 70 | public void outerPrint() { 71 | System.out.println(className); 72 | } 73 | 74 | public static void main(String[] args) { 75 | OuterClass outerClass = new OuterClass(); 76 | OuterClass.nonStaticClass inner = outerClass.new nonStaticClass(); 77 | inner.print(); // MyClass 78 | } 79 | } 80 | ``` 81 | 82 | ### non-static 멤버클래스의 사용 83 | - 어댑터를 정의할때 자주쓰인다. 84 | 어댑터? 어떤클래스의 인스턴스를 감싸 다른클래스의 인스턴스처럼 보이게하는 뷰로 사용 85 | 86 | - Set, List같은 컬렉션 인터페이스 구현체들도 반복자를 구현할때 non-static 멤버클래스를 주로 사용한다. 87 | 88 | 89 | ![](https://velog.velcdn.com/images/rodlsdyd/post/bf80e196-c129-449d-952d-4d00ec4b8082/image.png) 90 | 91 | ![](https://velog.velcdn.com/images/rodlsdyd/post/ce693435-9e3a-4469-83c8-0a50eb491a26/image.png) 92 | 93 | - 위의 ListItr 멤버클래스는 바깥클래스를 참조한다. 그래서 non-static으로 선언한듯하다 94 | 95 | 96 | 97 | ## non-static 멤버클래스 단점 98 | 99 | - 멤버 클래스에서 바깥 인스턴스에 접근할일이 없다면 무조건 static을 붙여서 정적멤버 클래스로 만들자 100 | 101 | - non-static 멤버 클래스는 바깥인스턴스의 외부참조를 가지게 되어 시간,공간이 낭비되기때문이다. 102 | - gc가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리누수가 생길수도있다. 103 | 104 | ![](https://velog.velcdn.com/images/rodlsdyd/post/f1bd334c-8629-4432-b1bd-bb03d2f12ab4/image.png) 105 | 106 | - 모든 엔트리가 맵과 연관되어있지만 엔트리의 메서드들(getKey, geyValue ..)은 맵을 직접 사용하지는 않는다. 따라서 엔트리를 private static 메서드로 선언하는것이 알맞다 107 | - 실수로 static을 빠트려도 동작은 하나 모든 엔트리가 바깥 맵의 참조를 갖게되어 공간,시간을 낭비하게된다. 108 | 109 | 110 | 111 | 112 | ## 익명클래스 113 | 114 | - 즉석에서 작은 함수객체나 처리객체를 만드는데 주로 사용된다. 115 | 116 | - non-static 문맥에서만 바깥 클래스의 인스턴스를 참조할수있다. 117 | 118 | - 쓰이는 시점에 선언+객체 생성이 동시에 이루어진다 119 | 120 | - static문맥에서 사용될때엔 static 변수외의 필드는 가질수없다. 121 | 122 | - static 팩터리 메서드를 만들때 사용되기도 한다 123 | ```java 124 | static List intArrayAsList(int[] a) { 125 | 126 | Objects.requireNonNull(a); 127 | return new AbstractList<>() { 128 | @Override 129 | public Interger get(int i) { return a[i]; } 130 | 131 | @Override 132 | public Integer set(int i, Integer val) { 133 | int oldVal = a[i]; 134 | a[i] = val; 135 | return oldVal; 136 | } 137 | 138 | @Override 139 | public int size() { return a.length; } 140 | } 141 | } 142 | ``` 143 | 144 | ## 지역클래스 145 | 146 | ```java 147 | public void foo() { 148 | class LocalClass{ 149 | private String className; 150 | 151 | public LocalClass(String className) { 152 | this.className = className; 153 | } 154 | 155 | public void print() { 156 | // 비정적 문맥에선 바깥 인스턴스를 참조 가능 157 | System.out.println("로컬클래스= " + className); 158 | // foo()가 static이면 className을 참조할수없다 컴파일 에러 159 | } 160 | } 161 | LocalClass localClass = new LocalClass("로컬"); 162 | localClass.print(); 163 | } 164 | ``` 165 | 166 | - 지역변수를 선언할수있는 곳이면 어디서든 선언가능하다 167 | - 지역변수와 같은 스콥을 가진다 168 | - 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 클래스 객체를 참조할 수 있으며, 정적 필드는 가질 수 없고 가독성을 위해 짧게 작성해야 한다. 169 | 170 | 171 | 172 | ### 정리 173 | 174 | - 바깥 클래스를 참조할일이 없다면 static멤버클래스로 만들어라 (non-static으로 만든다면 쓸데없는 외부참조가생겨 낭비가 생긴다) 175 | 176 | - 메서드 밖에서도 사용해야하거나 메서드안에 정의하기엔 너무 길다면 멤버클래스로 만든다. 177 | -------------------------------------------------------------------------------- /4장_클래스와_인터페이스/item25.md: -------------------------------------------------------------------------------- 1 | # item25 톱레벨 클래스는 한 파일에 하나만 담아라 2 | 3 | 소스파일 하나에 톱레벨 클래스를 여러개 선언하더라고 자바 컴파일러는 잘 작동한다. 4 | 하지만 이러한 행위는 심각한 위험을 발생시키는 행위이다. 5 | 6 | ### Why? 7 | 어느 소스 파일을 먼저 컴파일 하느냐에 따라 작동이 달라지기 때문이다. 8 | 9 | 아래는 같이 소스파일 하나에 톱레벨 클래스가 여러개 담긴것에 대한 예시이다. 10 | 11 | ```java 12 | // Utensil.java 13 | class Utensil { 14 | static final String NAME = "PAN"; 15 | } 16 | 17 | class Dessert { 18 | static final String NAME = "CAKE"; 19 | } 20 | ``` 21 | 22 | ```java 23 | // Dessert.java 24 | class Utensil { 25 | static final String NAME = "PAN"; 26 | } 27 | 28 | class Dessert { 29 | static final String NAME = "CAKE"; 30 | } 31 | ``` 32 | 33 | 34 | ```java 35 | // MAIN 36 | public class Main { 37 | public static void main(String[] args) { 38 | System.out.println(Utensil.NAME + Dessert.NAME); 39 | } 40 | } 41 | ``` 42 | 43 | 위와같이 코드를 작성 한 후 컴파일을 해보자 44 | 1) `javac Main.java Dessert.java` 45 | 컴파일 오류가 발생하고, Utensil과 Dessert가 중복되었다고 알려준다. 46 | 47 | 2) `javac Main.java Utensil.java` or `javac Main.java` 48 | 컴파일 하면 Dessert.java를 작성하기 전처럼 `pancake`를 출력한다. 49 | 50 | 3) `javac Dessert.java Main.java` 51 | 컴파일 하면 `potpie`를 출력한다. 52 | 53 | 위와 같이 어느 소스파일을 먼저 컴파일러에게 건네느냐에 따라 동작 방식이 달라지는 문제가 있다. 54 | 55 | ### 해결방안 56 | 1. 톱레벨 클래스들을 정적 멤버 클래스를 이용하여 서로다른 클래스로 분리해보자 57 | 58 | ```java 59 | // 정적 멤버 클래스 적용 60 | public class Main { 61 | public static void main(String[] args) { 62 | System.out.println(Utensil.NAME + Dessert.NAME); 63 | } 64 | 65 | private static class Utensil { 66 | static final String NAME = "PAN"; 67 | } 68 | 69 | private static class Dessert { 70 | static final String NAME = "CAKE"; 71 | } 72 | } 73 | ``` 74 | 75 | 76 | 2. 인텔리제이가 빨간글씨로 문제가 있다고 알려준다..! 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /5장_제네릭/Item28.md: -------------------------------------------------------------------------------- 1 | # Item28. 배열보다는 리스트를 사용하라 2 | 3 | 4 | ### 1. 배열은 공변이고 제네릭은 불공변이다. 5 | - Object가 Long의 부모타입이라 Object[]도 Long[] 는 호환된다 6 | - `List`는`List`와 호환되지않는다. 7 | 8 | ```java 9 | // 런타임 시점에 확인됨(컴파일이 된다는문제) 10 | Object[] array = new Long[1]; 11 | array[0] = "타입이 달라 넣을수 없음"; // 런타임 ArrayStoreException 예외 12 | 13 | // 컴파일 시점에 확인됨 14 | List list = new ArrayList(); // 컴파일 에러 15 | list.add("타입이 달라 넣을수 없음"); 16 | ``` 17 | 18 | ### 2. 배열은 런타임에도 원소의 타입을 인지하지만 제네릭은 런타임에는 타입정보가 소거(erasure)된다. 19 | 20 | ### erasure란? 21 | 제네릭 도입전 코드와의 **호환성을 유지하기위해** 컴파일러가 필요한곳에 **형변환을 넣어주는 역할을한후, 제네릭타입을 제거한다.** 22 | 즉, 컴파일된 파일(*.class)에는 제네릭 타입에대한 정보가 없다. 23 | ```java 24 | // 컴파일 전 25 | List list = new ArrayList(); 26 | list.add(new ClassA()); 27 | ClassA a = list.get(0); 28 | 29 | // 컴파일 후 30 | List list = new ArrayList(); 31 | list.add(new ClassA()); 32 | ClassA a = (ClassA)list.get(0); 33 | ``` 34 | 35 | ### 3. 제네릭 배열은 허용되지않는다. 36 | 37 | #### 제네릭 배열이 허용된다면? 38 | 39 | ```java 40 | 41 | List[] stringLists = new List[1]; //제네릭 배열(컴파일 오류) 42 | List intList = List.of(42); // 제네릭 43 | 44 | // 제네릭 배열이 가능하다고 했을때, List[] 타입을 집어넣어, List[] 로 인식하게된다 45 | Object[] objects = stringLists; 46 | 47 | // List[]배열의 첫번째공간에 List를 집어넣음 48 | objects[0] = intList; 49 | 50 | // stringLists 에는 Integer값이 들어있어 런타임에 ClassCastException 발생 51 | String s = stringLists[0].get(0); 52 | ``` 53 | 54 | 이를 방지하기위해선 제네릭 배열의 컴파일을 막아야한다. 55 | 56 | 57 | ## 제네릭 사용하자 58 | #### 제네릭 사용 x 59 | 60 | ```java 61 | public class Chooser { 62 | private final Object[] choiceArray; 63 | 64 | public Chooser(Collection choices) { 65 | this.choiceArray = choices.toArray(); 66 | } 67 | 68 | public Object choose() { 69 | Random rnd = ThreadLocalRandom.current(); 70 | return choiceArray[rnd.nextInt(choiceArray.length)]; // 런타임오류 발생 71 | } 72 | 73 | public static void main(String[] args) { 74 | List list = new ArrayList<>(); 75 | 76 | Chooser chooser = new Chooser(list); 77 | Object choose = chooser.choose(); 78 | } 79 | } 80 | ``` 81 | 제네릭 미사용시 컴파일 타입체크가 안되기때문에 런타임 오류를 만날 가능성이 높아진다. 82 | 83 | #### 제네릭 사용 84 | - 타입안정성 체크가 가능하다 (런타임 오류를 만날일이 없다) 85 | - 경고나 컴파일오류를 만나면 배열을 리스트로 대체해라 86 | ```java 87 | public class Chooser { 88 | private final List choiceArray; 89 | 90 | public Chooser(Collection choices) { 91 | this.choiceArray = new ArrayList<>(choices); 92 | } 93 | 94 | public Object choose() { 95 | Random rnd = ThreadLocalRandom.current(); 96 | return choiceArray.get(rnd.nextInt(choiceArray.size())); 97 | } 98 | 99 | public static void main(String[] args) { 100 | List list = new ArrayList<>(); 101 | 102 | Chooser chooser = new Chooser(list); 103 | Object choose = chooser.choose(); 104 | } 105 | } 106 | ``` 107 | 108 | 109 | ### 정리 110 | 111 | - 배열대신 리스트 사용해라 112 | => 제네릭을 자연스럽게 같이 사용하게되어 타입안정성을 높힌다. 113 | => 불필요한 타입캐스팅을 줄인다. 114 | -------------------------------------------------------------------------------- /5장_제네릭/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |---------------------------------|-----|-----| 3 | | 26. 로 타입은 사용하지 말라 | 영준 | 세용 | 4 | | 27. 비검사 경고를 제거하라 | 지영 | 승재 | 5 | | 28. 배열보다는 리스트를 사용하라 | 영진 | 소정 | 6 | | 29. 이왕이면 제네릭 타입으로 만들라 | 윤희 | 소정 | 7 | | 30. 이왕이면 제네릭 메서드로 만들라 | 지영 | 승재 | 8 | | 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 | 사욱 | 영준 | 9 | | 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 | 영진 | 세용 | 10 | | 33. 타입 안전 이종 컨테이너를 고려하라 | 영준 | 지영 | 11 | -------------------------------------------------------------------------------- /5장_제네릭/item26.md: -------------------------------------------------------------------------------- 1 | # 아이템26. 로 타입은 사용하지 말라 2 | 3 | ## 0. 제네릭 용어 정리 4 | 5 | 제네릭 챕터에서 사용할 대표적인 용어 먼저 정리했다. 6 | 7 | ```java 8 | public class Sample { 9 | } 10 | ``` 11 | 12 | | 이름 | 설명 | 예제 | 13 | |------------------------------|------------------------------------------------|----------------| 14 | | 제네릭 클래스(or 인터페이스) | 클래스(인터페이스) 선언에 타입 매개변수(type parameter)가 사용된 경우 | Sample.class | 15 | | 제네릭 타입(Generic Type) | 제네릭 클래스(인터페이스)를 의미 | Sample | 16 | | 매개변수화 타입(Parameterized Type) | 각각의 제네릭 타입은 parameterizedType을 선언 | Sample | 17 | | 타입 매개 변수(Type parameter) | 제네릭 선언에 사용되는 매개변수 | | 18 | | formalType | 제네릭 표현 | Sample | 19 | | actualType | 실제 타입 | Sample | 20 | | 로타입(raw type) | 제네릭 타입에서 타입 매개변수를 사용하지 않는 경우 | Sample | 21 | 22 | 로 타입을 알아보기 전에 제네릭부터 살펴보자. 23 | 24 | ## 1. 제네릭의 장점 25 | 26 | ### 1-1. 타입 안정성 27 | 28 | - 컴파일 시점에서 타입을 체크함으로써 **타입 안정성**을 제공함 29 | 30 | ![image](https://user-images.githubusercontent.com/42997924/196436583-617e9637-434e-41f9-b187-84e23007c6fd.png) 31 | 32 | ### 1-2. 컴파일 타임에 타입 선언 33 | 34 | - **타입체크와 형변환을 생략**함으로써 코드가 간결해짐 35 | - 컴파일러가 타입선언에 대해 인지하고 있기 때문에, 아무런 경고 없이 컴파일되면 의도대로 동작할 것임을 보장 36 | 37 | ## 2. 로 타입의 단점 38 | 39 | ### 2-1. 컴파일 타임에 타입 정보를 알 수 없음 40 | 41 | 런타임 에러(ex: `ClassCastException`)를 만들기 쉽다. 42 | 43 | 로 타입은 매개변수를 사용하지 않기 때문에 컴파일러가 자동으로 형변환을 해주면서 런타임에 `ClassCastException`을 던진다. 44 | 45 | - 컴파일은 되지만 런타임에서 에러가 발생하여 좋지 않은 코드가 된다 46 | 47 | **예제 코드** 48 | 49 | ![image](https://user-images.githubusercontent.com/42997924/196436674-45614fa7-5b7a-4e51-a080-ea505aa841a5.png) 50 | 51 | ![image](https://user-images.githubusercontent.com/42997924/196436698-e1effd27-b4cd-4e23-b692-8416947fee3e.png) 52 | 53 | > 모든 타입을 받을 수 있도록 자유도를 주지만, 제네릭의 타입 안정성을 제공할 수 없다. 54 | > 55 | 56 | ## 3. 로 타입은 왜 만들어졌을까 57 | 58 | ### 3-1. 호환성 59 | 60 | 기존 코드를 모두 유지하면서 제네릭을 사용하는 새로운 코드도 동작하게 하기 위해 61 | 62 | - 호환성 : 로타입 지원 + 제네릭 구현 시 **소거(erasure)** 방식 63 | 64 | ## 4. 비한정 와일드카드 타입 65 | 66 | - 타입 안전을 보장하고 유연한 타입 67 | - 제네릭 타입을 쓰고 싶지만, 실제 타입 매개변수는 모두 받고 싶을 때 사용 68 | - `Set`의 비한정 와일드카드 타입은 `Set` 69 | 70 | ```dart 71 | 주의사항. 72 | List와 List은 공변관계가 아니다. 73 | Object가 String의 상위 클래스라 해서 매개변수타입에서도 그런 규칙을 따르는 것은 아니다. 74 | 이 관계에서는 서로 다른 타입이라고 생각하면 된다. 75 | 76 | ``` 77 | 78 | ### 4-1. 로 타입과 차이점 79 | 80 | > 와일드카드 타입은 안전하고 로타입은 타입 안전하지 않다. 81 | > 82 | - 로타입 : 아무 원소나 넣을 수 있어 타입 불변식을 훼손하지 쉽다. 83 | - 와일드카드 타입 : Collection에는 어떤 원소도 넣을 수 없음 (`null 제외`) 84 | - 컴파일 타임에 Error 확인 가능 85 | - 컬렉션의 타입 불변식을 훼손하지 못하게 막고, 컬렉션에서 꺼낼 수 있는 객체의 타입도 알수 없게 함 86 | 87 | ## 5. 로 타입 규칙 예외 88 | 89 | ### 5-1. 클래스 리터럴 90 | 91 | 자바 명세 자체가 class 리터럴에 매개변수화 타입을 사용하지 못하게 하였다. 92 | 93 | - 허용 : `List.class`, `String[].class`, `int.class` 94 | - 불가 : `List.class`, `List.class` 95 | 96 | ### 5-2. Instanceof 연산자 97 | 98 | instanceof 연산자는 비한정 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 99 | 100 | **런타임**에는 **제네릭 타입** 정보가 지워진다. 101 | 102 | 그래서 instanceof에서 리스트 타입인지 알고싶다면, 로타입이든 비한정적 와일드 타입이든 둘중 하나를 사용해라. 103 | 104 | > 로타입이든 비한정적 와일드카드 타입이든 instanceof는 똑같이 동작한다. : 코드만 지저분해지므로 로타입 런타임에는 제네릭 타입정보가 지워진다. 105 | > 106 | 107 | ## 6. 용어 정리 108 | 109 | | 한글 용어 | 영문 용어 | 예 | 110 | |---------------|-------------------------|----------------------------------| 111 | | 매개변수화 타입 | parameterized type | List | 112 | | 실제 타입 매개변수 | actual type parameter | String | 113 | | 제네릭 타입 | generic type | List | 114 | | 정규 타입 매개변수 | formal type parameter | E | 115 | | 비한정적 와일드카드 타입 | unbounded wildcard type | List | 116 | | 로 타입 | raw type | List | 117 | | 한정적 타입 매개변수 | bounded type parameter | | 118 | | 재귀적 타입 한정 | recursive type bound | > | 119 | | 한정적 와일드카드 타입 | bounded wildcard type | List | 120 | | 제네릭 메서드 | generic method | static List asList(E[] a) | 121 | | 타입 토큰 | type token | String.class | -------------------------------------------------------------------------------- /5장_제네릭/item27.md: -------------------------------------------------------------------------------- 1 | 제네릭을 사용하기 시작하면 수많은 컴파일러 경고를 볼 수 있음.
2 | 컴파일러 경고 예시) 비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등 3 | 4 | - 자바 7부터 지원하는 다이아몬드 연산자(<>)를 사용하면 타입 매개변수를 추론 가능. 5 | - 할 수 있는 한 비검사 경고를 제거해서 ClassCastException이 발생하지 않도록 하기. 6 | 7 | ## @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨기자. 8 | 9 | - 단, 타입이 안전한 지 확인한 후 달기. 10 | - @SuppressWarnings는 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에서도 달 수 있다. 11 | - 하지만 가능한 좁은 범위에서 적용하라 12 | - 한 줄이 넘는 메서드가 생성자에 이 애너테이션이 달린다면 지역 변수 선언쪽으로 옮겨라. 13 | - 참고) [@SuppressWarnings](https://www.ibm.com/docs/ko/developer-for-zos/9.5.1?topic=code-excluding-warnings) 14 | 15 | #### ArrayList의 toArray 메서드 16 | 17 | ``` 18 | 19 | public T[] toArray(T[] a) { 20 | if (a.length < size 21 | return (T[]) Arrays.copyOf(elementData, size, a.getClass()); 22 | System.arraycopy(elementData, 0, a, 0, size); 23 | if (a.length > size) 24 | a[size] = null; 25 | return a; 26 | } 27 | 28 | ``` 29 | 30 | #### 지역 변수를 추가해 @SuppressWarnings의 범위를 좁힌다 31 | 32 | ``` 33 | 34 | public T[] toArray(T[] a) { 35 | if (a.length < size) { 36 | // 생성한 배열과 매개변수로 받은 배열의 타입이 모두 T[]로 같다. 37 | @SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass()); 38 | return result; 39 | } 40 | System.arraycopy(elementData, 0, a, 0, size); 41 | if (a.length > size) 42 | a[size] = null; 43 | return a; 44 | } 45 | 46 | ``` 47 | 48 | - @SuppressWarnings("unchecked") 애너테이션을 사용할 땐 그 경고를 무시해도 안전한 이유를 항상 주석으로 남길 것. 49 | 50 | ### 핵심 정리 51 | 52 | - 비검사 경고는 중요하니 무시하지 말기. 53 | - 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있음. 54 | - 경고를 없앨 방법을 찾지 못하겠다면, 그 코드가 타입 안점함을 증명하고 가능한 한 범위를 좁혀 @SuppressWarnings("unchecked") 애터네이션으로 경고를 숨기기. 55 | - 숨기기로 한 이유를 주석으로 남기기. 56 | -------------------------------------------------------------------------------- /5장_제네릭/item29.md: -------------------------------------------------------------------------------- 1 | # item29 이왕이면 제네릭 타입으로 만들라 2 | 3 | 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다. 4 | 5 | ## Object 기반 스택 - 제네릭이 절실한 강력 후보 6 | ```java 7 | public class Stack { 8 | private Object[] elements; 9 | private int size = 0; 10 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 11 | 12 | public Stack() { 13 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 14 | } 15 | 16 | public void push(Object e) { 17 | ensureCapacity(); 18 | elements[size++] = e; 19 | } 20 | 21 | public Object pop() { 22 | if(size == 0) 23 | throw new EmptyStackException(); 24 | Object result = elements[--size]; 25 | elements[size] = null; 26 | return result; 27 | } 28 | 29 | public boolean isEmpty() { 30 | return size == 0; 31 | } 32 | 33 | private void ensureCapacity() { 34 | if(elements.length == size) 35 | elements = Arrays.copyOf(elements, 2 * size + 1); 36 | } 37 | } 38 | ``` 39 | 40 | 클라이언트가 스택에서 꺼낸 객체를 형변환할때 런타임 오류가 날 위험이 있다. 41 | 제네릭 타입으로 변경하면 컴파일타임에 타입검사를 해주어 런타임에 형변환 오류 발생을 방지해주므로 제네릭 타입으로 변경하는게 좋다. 42 | 기존 클래스를 제네릭으로 바꾼다고 해도 현재 버전을 사용하는 클라이언트에는 아무런 해가 없다. 43 | 44 | ## 일반 클래스를 제네릭 클래스로 변경하기 45 | 첫 단계는 클래스 선언에 타입 매개변수 추가 46 | Object를 타입 매개변수로 바꾸고 컴파일 해보자. 47 | ```java 48 | public class Stack { 49 | private E[] elements; 50 | private int size = 0; 51 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 52 | 53 | public Stack() { 54 | elements = new E[DEFAULT_INITIAL_CAPACITY]; 55 | } 56 | 57 | public void push(E e) { 58 | ensureCapacity(); 59 | elements[size++] = e; 60 | } 61 | 62 | public E pop() { 63 | if(size == 0) 64 | throw new EmptyStackException(); 65 | E result = elements[--size]; 66 | elements[size] = null; 67 | return result; 68 | } 69 | 70 | public boolean isEmpty() { 71 | return size == 0; 72 | } 73 | 74 | private void ensureCapacity() { 75 | if(elements.length == size) 76 | elements = Arrays.copyOf(elements, 2 * size + 1); 77 | } 78 | } 79 | ``` 80 | 81 | 여기서 컴파일 오류가 발생한다. E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다. 82 | ```java 83 | Stack.java:8: 84 | java: generic array creation 85 | elements = new E[DEFAULT_INITIAL_CAPACITY]; 86 | ``` 87 | 88 | 해결책은 두 가지가 있다. 89 | 첫번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다. 90 | ```java 91 | elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; 92 | // Unchecked cast: 'java.lang.Object[]' to 'E[]' 93 | ``` 94 | 컴파일러는 오류 대신 경고를 내보낼 것이다. 95 | 컴파일러는 이 프로그램이 타입 안전한지 증명할 방법이 없지만 우리는 할 수 있다. 이 비검사 형변환이 프로그램의 타입 안전성을 해치지 않음을 우리 스스로 확인해야 한다. 96 | elements는 private 필드에 저장되고, 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 전혀 없다. push 메서드를 통해 배열에 저장되는 원소의 타입은 항상 E다. 따라서 이 비검사 형변환은 확실히 안전하다. 97 | @SuppressWarnings 애너테이션으로 해당 경고를 숨긴다. 98 | ```java 99 | // 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다. 100 | // 따라서 타입 안전성을 보장하지만, 이 배열의 런타임 타입은 E[]가 아닌 Object[]다! 101 | @SuppressWarnings("unchecked") 102 | public Stack() { 103 | elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; 104 | } 105 | ``` 106 | 107 | 두번째 방법은 elements 필드의 타입을 E[] 에서 Object[]로 바꾸는 것이다. 108 | ```java 109 | public class Stack { 110 | private Object[] elements; 111 | ... 112 | 113 | public Stack() { 114 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 115 | } 116 | 117 | ... 118 | 119 | public E pop() { 120 | ... 121 | // java: incompatible types: java.lang.Object cannot be converted to E 122 | E result = elements[--size]; 123 | ... 124 | } 125 | ... 126 | } 127 | ``` 128 | 컴파일 오류가 발생한다. 129 | 배열이 반환한 원소를 E타입으로 형변환하면 오류 대신 경고가 뜬다. 130 | ```java 131 | // Unchecked cast: 'java.lang.Object' to 'E' 132 | E result = (E) elements[--size]; 133 | ``` 134 | E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변한이 안전한지 증명할 방법이 없다. 이번에도 우리가 직접 증명하고 경고를 숨길 수 있다. 135 | ```java 136 | // push에서 E타입만 허용하므로 이 형변환은 안전하다. 137 | @SuppressWarnings("unchecked") 138 | E result = (E) elements[--size]; 139 | ``` 140 | 141 | 첫 번째 방법은 가독성이 더 좋다. 배열의 타입을 E[]로 선언하여 오직 E 타입 인스턴스만 받음을 확실히 어필한다. 142 | 첫 번째 방식은 형변환을 배열 생성 단 한 번만 해주면 되지만, 두 번째 방식에서는 배열에서 원소를 읽을 때마다 해줘야 한다. 현업에서는 첫 번째 장식을 더 선호하며 자주 사용한다. 하지만 (E가 Object가 아닌 한) 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염(item32)을 일으킨다. (이 상황에서는 힙 오염을 일으키지 않는다.) 143 | 144 | 제네릭 Stack을 사용하는 클라이언트에서 명시적으로 형변환을 수행하지 않아도 된다. 145 | ```java 146 | public static void main(String[] args) { 147 | Stack stack = new Stack<>(); 148 | for (String arg : args) { 149 | stack.push(arg); 150 | } 151 | while (!stack.isEmpty()) 152 | System.out.println(stack.pop().toUpperCase()); 153 | } 154 | ``` 155 | 156 | 157 | ## 제네릭 타입 안에서 리스트를 사용하는게 항상 가능하지도, 꼭 좋은 것도 아니다. 158 | "배열보다는 리스트를 우선하라"는 아이템28과 모순돼 보인다. 159 | 자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국은 기본 타입인 배열을 사용해 구현해야 한다. 또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다. 160 | 161 | 제네릭 타입은 타입 매개변수에 기본 타입은 사용할 수 없다. 이는 자바 제네릭 타입 시스템의 근본적인 문제이나, 박싱된 기본 타입을 사용해 우회할 수 있다. 162 | 163 | ## 한정적 타입 매개변수를 이용해 타입 매개변수에 제약을 둘 수 있다. 164 | ex) `class DelayQueue implements BlockingQueue` 165 | DelayQueue는 Delayed의 하위 타입만 받는다는 뜻이다. 클라이언트는 형변환 없이 Delayed의 메서드를 호출 할 수 있다. 166 | 167 | ## heap pollution 참고 168 | - [[wiki] heap pollution](https://en.wikipedia.org/wiki/Heap_pollution) 169 | - [[GenericsFAQ] heap pollution](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Topic2) 170 | - [JLS, 4.12.2 Variables of Reference Type](https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.2) 171 | - [OBJ03-J. Prevent heap pollution](https://wiki.sei.cmu.edu/confluence/display/java/OBJ03-J.+Prevent+heap+pollution) -------------------------------------------------------------------------------- /5장_제네릭/item30.md: -------------------------------------------------------------------------------- 1 | 메서드도 제네릭으로 만들 수 있는데, 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다.
2 | 예) Collections의 '알고리즘' 메서드(binarySearch, sort 등 ...) 3 | 4 | - 제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다.
5 | 다음은 두 집합의 합집합을 반환하는 문제가 있는 메서드 예시이다. 6 | 7 | #### 로 타입 사용 - 수용 불가! (아이템 26) 8 | 9 | ``` 10 | public static Set union(Set s1, Set s2) { 11 | Set result = new HashSet<>(); 12 | result.addAll(s2); 13 | return result; 14 | } 15 | 16 | ``` 17 | 18 | - 컴파일은 되지만 2개의 경고가 발생한다. 19 | ``` 20 | Unchecked call to 'HashSet(Collection)' as a member of raw type 'java.util.HashSet' 21 | Unchecked call to 'addAll(Collection)' as a member of raw type 'java.util.Set' 22 | ``` 23 | - 경고를 없애려면 타입 안전하게 만들기 24 | - 메서드 선언에서의 세 집합(입력 2개, 반환 1개)의 원소 타입을 타입 매개변수로 명시 25 | - 메서드 안에서 이 타입 매개변수만 사용하게 수정 26 | - **(타입 매개변수들을 선언하는) 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다.** 27 | 28 | 타입 매개변수의 명명 규칙은 제네릭 메서드나 제네릭 타입이나 똑같다. (아이템 29, 68) 29 | 30 | #### 제네릭 메서드 31 | 32 | ``` 33 | public static Set union(Set s1, Set s2) { 34 | Set result = new HashSet<>(s1); 35 | result.addAll(s2); 36 | return result; 37 | } 38 | ``` 39 | 40 | 경고 없이 컴파일되며, 타입 안전하고, 쓰기도 쉬운 메서드이다. 41 | 42 | - 메서드의 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 한다. 43 | - 이를 한정적 와일드 카드 타입(아이템 31)을 사용해 더 유연하게 개선할 수도 있다. 44 | - 불변 객체를 여러 타입으로 활용할 수 있게 만들어야 할 때가 있다. 45 | - 제네릭은 런타임에 타입 정보가 소거(아이템 28)되므로 하나의 객체를 어떤 타입으로든 매개변수화할 수 있다. 46 | - 하지만 이렇게 하려면 요청한 타입 매개변수에 맞게 그 객체의 타입을 바꿔주는 정적 팩터리를 만들어야 한다. 47 | - 이 패턴을 **제네릭 싱글턴 팩터리**라고 하며, Collections.reverseOrder 같은 함수 객체나 Collections.emptySet 같은 컬렉션용으로 사용한다. 48 | 49 | - 항등함수(identity function)를 담은 클래스를 만들고 싶다면 자바 라이브러리의 Function.identity를 사용하면 된다. 50 | - 하지만 항등함수 객체는 상태가 없어서 요청할 때마다 새로 생성하는 것은 낭비다. 51 | - 제네릭은 소거 방식을 사용하기 때문에 제네릭 싱글턴 하나면 항등함수를 만들기에 충분하다. 52 | 53 | #### 제네릭 싱글턴 팩터리 패턴 54 | ``` 55 | private static UnaryOperator IDENTITY_FN = (t) -> t; 56 | 57 | @SuppressWarnings("unchecked") 58 | public static UnaryOperator identityFunction() { 59 | return (UnaryOperator) IDENTITY_FN; 60 | } 61 | ``` 62 | - IDENTITY_FN을 UnaryOperator로 형변환하면 비검사 형변환 경고가 발생한다. 63 | - T가 어떤 타입이든 UnaryOperator는 UnaryOperator가 아니기 때문이다. 64 | - 하지만 항등함수란 입력 값을 수정 없이 그대로 반환하는 특별한 함수이므로, T가 어떤 타입이든 UnaryOperator를 사용해도 타입 안전하다. 65 | 66 | #### 제네릭 싱글턴을 사용하는 예 67 | 68 | ``` 69 | public static void main(String[] args) { 70 | String[] strings = {"삼베", "대마", "나일론"}; 71 | UnaryOperator sameString = identityFunction(); 72 | for (String s : strings) { 73 | System.out.println(sameString.apply(s)); 74 | } 75 | 76 | Number[] numbers = {1, 2.0, 3L}; 77 | UnaryOperator sameNumber = identityFunction(); 78 | for (Number n : numbers) { 79 | System.out.println(sameNumber.apply(n)); 80 | } 81 | } 82 | ``` 83 | ### 재귀적 타입 한정(recursive type bound) 84 | 85 | - 자기 자신이 들어간 표현식을 사용해 타입 매개변수의 허용 범위를 한정할 수 있음. 86 | - 재귀적 타입 한정은 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰임. 87 | 88 | ``` 89 | public interface Comparable { 90 | int compareTo(T o); 91 | } 92 | ``` 93 | - 여기서는 타입 매개변수 T는 Comparable를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. 94 | - 실제로 거의 모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있다. 95 | 예) String은 Comparable을 구현하고 Integer는 Comparable를 구현하는 형식이다. 96 | - Comparable을 구현한 원소의 컬렉션을 입력받는 메서드들은 주로 그 원소들을 정렬, 검색, 비교하는 용도로 사용되며, 해당 용도로 사용하려면 컬렉션에 담긴 모든 원소가 상호 비교될 수 있어야 함. 97 | 98 | #### 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현 99 | 100 | ``` 101 | public class RecursiveTypeBoundEx { 102 | public static > E max(Collection c); 103 | } 104 | ``` 105 | - 타입 한정인 >는 "모든 타입 E는 자신과 비교할 수 있다"라고 읽을 수 있다. 즉, 상호 비교 가능하다는 뜻이다. 106 | 107 | #### 컬렉션에서 최댓값 반환한다. - 재귀적 타입 한정 사용 108 | 109 | ``` 110 | public static > E max(Collection collection) { 111 | if (collection.isEmpty()) { 112 | throw new IllegalArgumentException("컬렉션이 비어 있습니다."); 113 | } 114 | 115 | E result = null; 116 | for (E e : collection) { 117 | if (result == null || e.compareTo(result) > 0) { 118 | result = Objects.requireNonNull(e); 119 | } 120 | } 121 | return result; 122 | } 123 | ``` 124 | 125 | - 위 메서드에서는 빈 컬렉션인 경우 IllegalArgumentException을 던진다. 따라서 Optional를 반환하도록 하는 것이 더 낫다. 126 | 127 | ## 핵심 정리 128 | - 제네릭 타입과 마찬가지로, 클라이언트에서 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하고 사용하기도 쉽다. 129 | - 타입과 마찬가지로, 메서드도 형변환 없이 사용할 수 있는 편이 좋으며, 많은 경우 그렇게 하려면 제네릭 메서드가 되어야 한다. 130 | - 역시 타입과 마찬가지로, 형변환을 해줘야 하는 기존 메서드는 제네릭하게 만들자. 그렇다면, 기존 클라이언트는 그대로 둔 채 새로운 사용자의 삶을 훨씬 편하게 만들어 줄 것이다. (아이템 26) 131 | -------------------------------------------------------------------------------- /5장_제네릭/item31.md: -------------------------------------------------------------------------------- 1 | ## 한정적 와일드카드를 사용하여 API 유연성을 높혀라 2 | 3 | 매개 변수화 타입은 불공변이다. 즉 서로 다른 타입 TypeA 와 TypeB가 있을 때 List 는 List의 하위타입도 상위타입도 아니다. 4 | 5 | 하지만 이러한 불공변 방식보다, 유연한 타입체크가 필요할 수 있다. 6 | 7 | 아래 예시를 보자 8 | 9 | ```java 10 | public void pushAll(Iterable src){ 11 | for(E e:src){ 12 | push(e); 13 | } 14 | } 15 | 16 | public static void main(String[]args){ 17 | Stack stack = new Stack<>(); 18 | Iterable integers = new...; 19 | stack.pushAll(integers); // 컴파일 오류 발생 20 | } 21 | ``` 22 | 23 | Number와 Integer는 상속관계에 있지만, 불공변이기 때문에 컴파일 오류가 발생하게 된다. 24 | 25 | ### 상한 경계 와일드 카드 26 | 27 | ``` 28 | 상한경계 와일드 카드란? 29 | 30 | 31 | 들어오는 타입의 경우 T의 자식 객체 타입이여 한다. 32 | ``` 33 | 34 | 상한경계 와일드 카드를 이용하게 되면, 컴파일이 안되는 문제를 해결 할 수 있다. 35 | 36 | 37 | ```java 38 | // 성공 39 | public void pushAll(Iterable src) { 40 | for(E e:src){ 41 | push(e); 42 | } 43 | } 44 | 45 | ``` 46 | 47 | ### 하한경계 와일드 카드 48 | 49 | 그럼 하한경계 와일드 카드를 사용하는 예시도 봐보자. 50 | 51 | ``` 52 | 하한경계 와일드 카드란? 53 | 54 | 55 | 들어오는 타입의 경우 T or 부모 객체 타입이여 한다. 56 | ``` 57 | 58 | 59 | 60 | ```java 61 | public void popAll(Collection dst){ 62 | while(!isEmpty()){ 63 | dst.add(pop()); // compile error 64 | } 65 | } 66 | 67 | public static void main(String[]args) { 68 | Stack stack = new Stack<>(); 69 | Collection objects = new...; 70 | stack.popAll(objects); // 컴파일 오류발생 71 | } 72 | 73 | ``` 74 | 75 | 76 | ```java 77 | // 성공 78 | public void popAll(Collection dst){ 79 | while(!isEmpty()){ 80 | dst.add(pop()); 81 | } 82 | } 83 | ``` 84 | 85 | 86 | 위와 같이 하한경계 와일드 카드를 이용하면 컴파일 오류로부터 벗어나게 된다. 87 | 88 | ## PECS (producer - extends, consumer-super) 89 | 90 | 책에서는 extends와 super를 어떤 상횡에서 쓰면 좋읗지 PECS라는 말로 설명해주고 있다. 91 | 92 | ### PECS란? 93 | 94 | 매개변수 타입이 95 | T가 생산자(producer)인 경우 상한 경계 와일드 카드(extends) 를 사용하고, 96 | 97 | T가 소비자(consumer)이면 하한 경계 와일드 카드(super)를 사용해라 98 | 99 | ```java 100 | public void pushAll(Iterable src){ 101 | for(E e:src) { 102 | push(e); 103 | } 104 | } 105 | ``` 106 | 107 | 위 예제에서 src는 push를 하고 있으므로 생산자의 역할을 하고 있다. 108 | 109 | ```java 110 | public void popAll(Collectiondst){ 111 | while(!isEmpty()){ 112 | dst.add(pop()); 113 | } 114 | } 115 | ``` 116 | 117 | 위 예제에서 E 인스턴스를 소비하고 있기때문에 super를 사용하고 있다. 118 | 119 | 120 | ### 복잡한 불공변의 문제 121 | 122 | 아래 메서드를 와일드 카드 타입으로 다듬게 되면 123 | ```java 124 | public static > E max(List list) 125 | ``` 126 | 127 | 아래의 형대가 된다. 128 | ```java 129 | public static > E max(List list) 130 | ``` 131 | 이 메서드는 E 타입을 List 인자로 받은 후, 받은 List 중 에서 가장 큰 값을 리턴하는 메서드이다. 132 | 그리고 return 값은 comparable 이다. 133 | 134 | 과연이렇게 복잡한 와일드카드도 쓰일곳이 있을까? 135 | 136 | => 쓰일곳이 있다. 137 | 138 | 139 | 당 메서드에 아래의 값을 넣어보게 되면 140 | 141 | ```java 142 | List> scheduledFutures = new ArrayList<>(); 143 | ``` 144 | 145 | 수정전 메서드는 위의 값을 처리하지 못한다. 146 | 147 | 그 이유는 매개변수 타입의 불공변 때문에 처리하지 못하게 된다. 148 | 149 | ### 수정 전 150 | 151 | ![image](https://user-images.githubusercontent.com/43979984/197767795-e85bc100-ae02-41ab-bb7c-1cf02e06471e.png) 152 | 153 | 154 | ### 수정 후 155 | 156 | ![스크린샷 2022-10-25 오후 8 37 16](https://user-images.githubusercontent.com/43979984/197767536-9ac21deb-396e-439b-87f3-25df62c1a0fc.png) 157 | 158 | 그래서 비록 수정 후의 방법이 복잡하더라도 의미가 있는 방법이다. 159 | 160 | ```java 161 | // ScheduledFutuer의 상속형태 162 | 163 | interface Comparable 164 | interface Delayed extends Comparable 165 | interface ScheduledFuture extends Delayed, Future 166 | ``` 167 | 168 | 169 | 170 | ### 타입 매개변수 or 와일드 카드 171 | 172 | 타입매개변수와 와일드 카드는 공통되는 부분이 있어, 둘중 어느것을 사용해도 괜찮을 때가 있다. 173 | 174 | 아래 예시 메서드에 대해서 간단하게 설명하자면 리스트에 명시한 두인덱스와 아이템들을 swap하는 메서드이다. 175 | 176 | ```java 177 | public static void swap(List list,int i,int j); 178 | 179 | public static void swap(List list,int i,int j); 180 | ``` 181 | 182 | 1번의 경우 비한정적 타입매개변수이고, 183 | 184 | 2번의 경우 비한정적 와일드카드방식이다. 185 | 186 | 187 | 만약 publicAPI 라고 하면 비한정적 와일드 카드타입이 낫다. 188 | 189 | 그 이유는 신경써야 할 타입 매게변수가 없기 때문이다. 190 | 191 | 192 | 기본규칙으로는 타입 매개변수가 한번만 나오면 와일드 카드로 대체해라 193 | 194 | 1. 비한정적타입 매개변수라면 비한정적 와일드카드로, 195 | 2. 한정적타입매개변수 라면 한정적 와일드카드로 196 | 바꾸면 된다. 197 | 198 | 하지만 직관적으로 구현한 2번(swap)은 컴파일 되지 않는다. 199 | 200 | ```java 201 | public static void swap(List list,int i,int j){ 202 | list.set(i,list.set(j,list.get(i))); 203 | } 204 | ``` 205 | 206 | 방금 꺼낸 원소를 다시 리스트에 넣을 수 없는 상황이다 207 | 208 | 이유는 리스트 타입이 List 이기 때문이다, List 에는 null 외에는 어떤 값도 add 할 수 수 없기 때문이다. 209 | 210 | 211 | 이런 경우에는 ***와일드 카드 타입의 실제 타입을 알려주는 메서드를 private 도우미 메서드*** 로 따로 작성하는것이다. 212 | 213 | ```java 214 | public static void swap(List list, int i, int j) { 215 | swapHelper(list, i, j); 216 | } 217 | 218 | private static void swapHelper(List list, int i, int j) { 219 | list.set(i, list.set(j, list.get(i))); 220 | } 221 | 222 | ``` 223 | 224 | swapHelper 메서드는 리스트가 List임을 알고 있다. 225 | 226 | 즉 이 리스트에서 꺼낸 값은 항상 E이고, E 타입의 값이라면 이 리스트에 넣어도 안전함을 알고 있다. 227 | -------------------------------------------------------------------------------- /6장_열거_타입과_애너테이션/Item40.md: -------------------------------------------------------------------------------- 1 | ```java 2 | // 매개변수가 Object 타입이어야하는데 Bigram으로, 오버로딩 되었다 3 | public boolean equals(Bigram b) { 4 | return b.first == first && b.second == second; 5 | } 6 | ``` 7 | 8 | 9 | 10 | ![](https://velog.velcdn.com/images/rodlsdyd/post/d2dfa102-1fd2-47b3-a6d9-882480bd88e7/image.png) 11 | 12 | 위 메서드에 @Override를 붙이면 컴파일러가 오류를 찾아준다 13 | 14 | 15 | ### 메서드를 재정의하려거든 모든 메서드에 @Override 애너테이션을 달자 16 | 17 | - 컴파일시 버그를 발견할수있다 18 | 19 | - 구체 클래스에서 상위 클래스의 추상메서드를 재정의하는경우엔 굳이 @Override를 달지 않아도 된다. 20 | (다는게 구조파악에 좋다는 개인적인 생각.. ) 21 | -------------------------------------------------------------------------------- /6장_열거_타입과_애너테이션/README.md: -------------------------------------------------------------------------------- 1 | | Item | 발표 | 질문 | 2 | |--------------------------------------|-----|-----| 3 | | 34. int 상수 대신 열거 타입을 사용하라 | 세용 | 사욱 | 4 | | 35. ordinal 메소드 대신 인스턴스 필드를 사용하라 | 윤희 | 소정 | 5 | | 36. 비트 필드 대신 EnumSet을 사용하라 | 승재 | 영진 | 6 | | 37. ordinal 인덱싱 대신 EnumMap을 사용하라 | 지영 | 윤희 | 7 | | 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 | 사욱 | 승재 | 8 | | 39. 명명 패턴보다 Annotation을 사용하라 | 소정 | 지영 | 9 | | 40. @Override 애너테이션을 일관되게 사용하라 | 영진 | 사욱 | 10 | | 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 | 영준 | 소정 | 11 | -------------------------------------------------------------------------------- /6장_열거_타입과_애너테이션/item35.md: -------------------------------------------------------------------------------- 1 | # item35 ordinal 메서드 대신 인스턴스 필드를 사용하라 2 | 3 | 열거 타입은 해당 상수가 그 열거 타입에서 몇 번째 위치인지를 반환하는 ordinal이라는 메서드를 제공한다. 4 | 5 | ```java 6 | public enum Ensemble { 7 | SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET; 8 | 9 | public int numberOfMusicians() { 10 | return ordinal() + 1; 11 | } 12 | } 13 | ``` 14 | 15 | 동작은 하지만 유지보수하기 끔찍한 코드다. 상수 선언 순서를 바꾸는 순간 numberOfMusicians가 오동작하며, 이미 사용 중인 정수와 같은 상수는 추가할 방법이 없다. 16 | 또한 값을 중간에 비워둘 수도 없다. 17 | 18 | 열거 타입 상수에 연결된 값은 ordinal 메서드로 얻지 말고, 인스턴스 필드에 저장하자. 19 | ```java 20 | public enum Ensemble { 21 | SOLO(1), 22 | DUET(2), 23 | TRIO(3), 24 | QUARTET(4), 25 | QUINTET(5), 26 | SEXTET(6), 27 | SEPTET(7), 28 | OCTET(8), 29 | DOUBLE_QUARTET(8), 30 | NONET(9), 31 | DECTET(10), 32 | TRIPLE_QUARTET(12); 33 | 34 | private final int numberOfMusicians; 35 | 36 | Ensemble(int numberOfMusicians) { 37 | this.numberOfMusicians = numberOfMusicians; 38 | } 39 | 40 | public int numberOfMusicians() { 41 | return numberOfMusicians; 42 | } 43 | } 44 | ``` 45 | 46 | Enum API 문서를 보면 ordinal에 대해 이렇게 쓰여 있다. 47 | > 대부분 프로그래머는 이 메서드를 쓸 일이 없다. 이 메서드는 EnumSet과 EnumMap 같이 열거 타입기반의 범용 자료구조에 쓸 목적으로 설계되었다. 48 | 49 | 따라서 이런 용도가 아니라면 ordinal 메서드는 절대 사용하지 말자. -------------------------------------------------------------------------------- /6장_열거_타입과_애너테이션/item36.md: -------------------------------------------------------------------------------- 1 | # Item 36. 비트 필드 대신 EnumSet을 사용하라 2 | 3 | Created: 2022년 10월 30일 오후 3:16 4 | Last Edited Time: 2022년 11월 1일 오후 8:30 5 | Status: 정리 완료 6 | 7 | # 비트 필드 대신 EnumSet을 사용하라 8 | 9 | 열거한 값들이 주로 집합으로 사용되는 경우 각 상수에 서로 다른 2의 거듭제곱을 할당한 정수 열거패턴을 사용해왔다. 10 | 11 | ```java 12 | public class Text { 13 | public static final int STYLE_BOLD = 1 << 0; 14 | public static final int STYLE_ITALIC = 1 << 1; 15 | public static final int STYLE_UNDERLINE = 1 << 2; 16 | public static final int STYLE_STRIKETHROUGH = 1 << 3; 17 | } 18 | ``` 19 | 20 | `Text.applyStyles(STYLE_BOLD | STYLE_ITALIC);` 21 | 22 | 이런식으로 여러 상수를 하나의 집합으로 모을 수 있고 이렇게 만들어지는 집합을 비트필드라고 한다. 23 | 24 | 이 비트필드를 사용하면 비트별 연산을 사용하여 집합 연산을 효율적으로 수행할 수 있다. 25 | 26 | ## 문제점 27 | 28 | - 비트 필드 값이 그대로 출력되면 정수 열거 상수를 출력할때보다 해석하기가 훨씬 어렵다. 29 | - 비트필드 하나에 녹아 있는 모든 원소를 순회하기도 까다롭다. 30 | - 최대 몇 비트가 필요한지를 API작성 시 미리 예측하여 적절한 타입을 선택해야 한다. 31 | 32 | ## 해결책 33 | 34 | `EnumSet` 클래스는 열거타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다. 35 | 36 | Set 인터페이스를 완벽하게 구현해놓았고, 타입 안전 + 다른 Set 구현체와 함께 사용이 가능하다. 37 | 38 | EnumSet의 내부는 비트 벡터로 구현되었다. 39 | 40 | 총 원소개수가 64개 이하라면, 대부분의 경우에 EnumSet 전체를 long 변수 하나로 표현하여 비트 필드에 비견되는 성능을 보여준다. 41 | 42 | removeAll, retainAll 같은 대량의 작업은 비트를 효율적으로 처리할 수 있는 산술 연산을 써서 구현하였다. 43 | 44 | ```java 45 | public void applyStyles(Set