├── .github ├── scripts │ └── update_readme.py └── workflows │ └── update-readme.yml ├── .gitignore ├── .obsidian ├── app.json ├── appearance.json ├── themes │ ├── Blue Topaz │ │ ├── manifest.json │ │ └── theme.css │ └── Things │ │ ├── manifest.json │ │ └── theme.css └── workspace.json ├── 02장 ├── .gitkeep ├── 아이템_1 │ ├── .gitkeep │ ├── api문서.png │ ├── 생성자_대신_정적_팩터리_메서드를_고려하라_배키.md │ └── 생성자_대신_정적_팩터리_메서드를_고려하라_제우스.md ├── 아이템_2 │ ├── .gitkeep │ ├── pizza_상속관계.png │ ├── 생성자에_매개변수가_많다면_빌더를_고려하라_배키.md │ └── 생성자에_매개변수가_많다면_빌더를_고려하라_제우스.md ├── 아이템_3 │ ├── .gitkeep │ ├── private_생성자나_열거_타입으로_싱글턴임을_보증하라_켬미.md │ └── private_생성자나_열거_타입으로_싱글턴임을_보증하라_폰드.md ├── 아이템_4 │ ├── .gitkeep │ ├── item4.png │ ├── 인스턴스화를_막으려거든_private_생성자를_사용하라_프람.md │ └── 인스턴스화를_막으려거든_private_생성자를_사용하라_호티.md ├── 아이템_5 │ ├── .gitkeep │ ├── 자원을_직접_명시하지_말고_의존_객체_주입을_사용하라_프람.md │ └── 자원을_직접_명시하지_말고_의존_객체_주입을_사용하라_호티.md ├── 아이템_6 │ ├── .gitkeep │ ├── 불필요한_객체_생성을_피하라_켬미.md │ └── 불필요한_객체_생성을_피하라_폰드.md ├── 아이템_7 │ ├── .gitkeep │ ├── after_pop.png │ ├── gc.png │ ├── stack.png │ ├── 다_쓴_객체_참조를_해제하라_도비.md │ └── 다_쓴_객체_참조를_해제하라_초롱.md ├── 아이템_8 │ └── .gitkeep └── 아이템_9 │ ├── .gitkeep │ ├── exception.png │ ├── img.png │ ├── stack_frame.png │ ├── try-finally보다는_try-with-resources를_사용하라_도비.md │ └── try-finally보다는_try-with-resources를_사용하라_초롱.md ├── 03장 ├── .gitkeep ├── 아이템_10 │ ├── .gitkeep │ ├── equals는_일반_규약을_지켜_재정의하라_도비.md │ ├── equals는_일반_규약을_지켜_재정의하라_호티.md │ ├── item_10_1.png │ ├── item_10_2.png │ ├── item_10_3.png │ └── same.jpeg ├── 아이템_11 │ ├── .gitkeep │ ├── equals를_재정의하려거든_hashCode도_재정의하라_도비.md │ ├── equals를_재정의하려거든_hashCode도_재정의하라_호티.md │ ├── item_11_1.png │ └── item_11_2.png ├── 아이템_12 │ ├── .gitkeep │ ├── toString을_항상_재정의하라_켬미.md │ └── toString을_항상_재정의하라_폰드.md ├── 아이템_13 │ ├── .gitkeep │ ├── clone_재정의는_주의해서_진행하라_켬미.md │ └── clone_재정의는_주의해서_진행하라_폰드.md └── 아이템_14 │ ├── .gitkeep │ ├── Comparable을_구현할지_고려하라_초롱.md │ ├── Comparable을_구현할지_고려하라_프람.md │ ├── img.png │ └── img_1.png ├── 04장 ├── .gitkeep ├── 아이템_15 │ ├── .gitkeep │ ├── 클래스와_멤버의_접근_권한을_최소화하라_배키.md │ └── 클래스와_멤버의_접근_권한을_최소화하라_제우스.md ├── 아이템_16 │ ├── .gitkeep │ ├── public_클래스에서는_public_필드가_아닌_접근자_메서드를_사용하라_배키.md │ └── public_클래스에서는_public_필드가_아닌_접근자_메서드를_사용하라_제우스.md ├── 아이템_17 │ ├── .gitkeep │ ├── 변경_가능성을_최소화하라_초롱.md │ └── 변경_가능성을_최소화하라_프람.md ├── 아이템_18 │ ├── .gitkeep │ └── 상속보다는_컴포지션을_사용하라_켬미.md ├── 아이템_19 │ ├── .gitkeep │ ├── 상속을_고려해_설계하고_문서화하라._그러지_않았다면_상속을_금지하라_프람.md │ └── 상속을_고려해_설계하고_문서화하라._그러지_않았다면_상속을_금지하라_호티.md ├── 아이템_20 │ ├── .gitkeep │ ├── 추상_클래스보다는_인터페이스를_사용하라_도비.md │ ├── 추상_클래스보다는_인터페이스를_우선하라_도비.md │ └── 추상_클래스보다는_인터페이스를_우선하라_켬미.md ├── 아이템_21 │ ├── .gitkeep │ ├── 인터페이스는_구현하는_쪽을_생각해_설계하라_배키.md │ └── 인터페이스는_구현하는_쪽을_생각해_설계하라_폰드.md ├── 아이템_22 │ ├── .gitkeep │ ├── 인터페이스는_타입을_정의하는_용도로만_사용하라_배키.md │ └── 인터페이스는_타입을_정의하는_용도로만_사용하라_폰드.md ├── 아이템_23 │ ├── .gitkeep │ ├── 태그_달린_클래스보다는_클래스_계층구조를_활용하라_프람.md │ └── 태그_달린_클래스보다는_클래스_계층구조를_활용하라_호티.md ├── 아이템_24 │ ├── .gitkeep │ ├── InnerClass.png │ ├── test1.png │ ├── test2.png │ ├── test3.png │ ├── test4.png │ ├── test5.png │ ├── 멤버_클래스는_되도록_static으로_만들라_초롱.md │ └── 멤버_클래스는_되도록_static으로_만들라_호티.md └── 아이템_25 │ ├── .gitkeep │ ├── 톱_레벨_클래스는_한_파일에_하나만_담으라_초롱.md │ └── 톱_레벨_클래스는_한_파일에_하나만_담으라_호티.md ├── 05장 ├── .gitkeep ├── 아이템_26 │ ├── .gitkeep │ └── 로_타입은_사용하지_말라_폰드.md ├── 아이템_27 │ ├── .gitkeep │ ├── unchecked_exception.png │ ├── 비검사_경고를_제거하라_배키.md │ └── 비검사_경고를_제거하라_프람.md ├── 아이템_28 │ ├── .gitkeep │ ├── 배열보다는_리스트를_사용하라_배키.md │ └── 배열보다는_리스트를_사용하라_프람.md ├── 아이템_29 │ ├── .gitkeep │ ├── item29_zeus_01.png │ ├── item29_zeus_02.png │ ├── item29_zeus_03.png │ ├── item29_zeus_04.png │ ├── item29_zeus_05.png │ ├── item29_zeus_06.png │ ├── 이왕이면_제네릭_타입으로_만들라_제우스.md │ └── 이왕이면_제네릭_타입으로_만들라_켬미.md ├── 아이템_30 │ ├── .gitkeep │ ├── item30_zeus_01.png │ ├── item30_zeus_02.png │ ├── 이왕이면_제네릭_메서드로_만들라_제우스.md │ └── 이왕이면_제네릭_메서드로_만들라_켬미.md ├── 아이템_31 │ ├── .gitkeep │ └── 한정적_와일드카드를_사용해_API_유연성을_높이라_도비.md ├── 아이템_32 │ ├── .gitkeep │ └── 제네릭과_가변인수를_함께_쓸_때는_신중하라_초롱.md └── 아이템_33 │ ├── .gitkeep │ ├── 타입_안전_이종_컨테이너를_고려하라_초롱.md │ └── 타입_안전_이종_컨테이너를_고려하라_폰드.md ├── 06장 ├── .gitkeep ├── 아이템_34 │ ├── .gitkeep │ ├── int_상수_대신_열거_타입을_사용하라_배키.md │ └── int_상수_대신_열거_타입을_사용하라_제우스.md ├── 아이템_35 │ ├── .gitkeep │ ├── ordinal_메서드_대신_인스턴스_필드를_사용하라_배키.md │ └── ordinal_메서드_대신_인스턴스_필드를_사용하라_제우스.md ├── 아이템_36 │ ├── .gitkeep │ ├── 비트_필드_대신_EnumSet을_사용하라.md │ └── 비트_필드_대신_EnumSet을_사용하라_켬미.md ├── 아이템_37 │ ├── .gitkeep │ ├── ordinal_인덱싱_대신_EnumMap을_사용하라.md │ └── ordinal_인덱싱_대신_EnumMap을_사용하라_켬미.md ├── 아이템_38 │ ├── .gitkeep │ └── 확장할_수_있는_열거_타입이_필요하면_인터페이스를_사용하라_도비.md ├── 아이템_39 │ ├── .gitkeep │ ├── 명명_패턴보다_애너테이션을_사용하라_폰드.md │ └── 명명_패턴보다_애너테이션을_사용하라_호티.md ├── 아이템_40 │ ├── .gitkeep │ ├── @Override_애너테이션을_일관되게_사용하라_폰드.md │ └── @Override_애너테이션을_일관되게_사용하라_호티.md └── 아이템_41 │ ├── .gitkeep │ └── 정의하려는_것이_타입이라면_마커_인터페이스를_사용하라.md ├── 07장 ├── .gitkeep ├── 아이템_42 │ ├── .gitkeep │ ├── 익명_클래스보다는_람다를_사용하라_배키.md │ └── 익명_클래스보다는_람다를_사용하라_초롱.md ├── 아이템_43 │ ├── .gitkeep │ ├── 람다보다는_메서드_참조를_사용하라_배키.md │ └── 람다보다는_메서드_참조를_사용하라_초롱.md ├── 아이템_44 │ └── .gitkeep ├── 아이템_45 │ ├── .gitkeep │ └── 스트림은_주의해서_사용하라_제우스.md ├── 아이템_46 │ ├── .gitkeep │ └── 스트림에서는_부작용_없는_함수를_사용하라_배키.md ├── 아이템_47 │ ├── .gitkeep │ ├── stream_iterator.png │ └── 반환_타입으로는_스트림보다_컬렉션이_낫다_배키.md └── 아이템_48 │ ├── .gitkeep │ ├── 스트림_병렬화는_주의해서_적용하라_켬미.md │ └── 스트림_병렬화는_주의해서_적용하라_폰드.md ├── 08장 ├── .gitkeep ├── 아이템_49 │ ├── .gitkeep │ ├── 매개변수가_유효한지_검사하라_제우스.md │ └── 매개변수가_유효한지_검사하라_프람.md ├── 아이템_50 │ ├── .gitkeep │ ├── 적시에_방어적_복사본을_만들라.md │ └── 적시에_방어적_복사본을_만들라_제우스.md ├── 아이템_51 │ ├── .gitkeep │ ├── 1.png │ ├── 메서드_시그니처를_신중히_설계하라_초롱.md │ └── 메서드_시그니처를_신중히_설계하라_호티.md ├── 아이템_52 │ ├── .gitkeep │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── img.png │ ├── img_1.png │ ├── img_2.png │ ├── 다중_정의는_신중히_사용하라.md │ └── 다중정의는_신중히_사용하라_호티.md ├── 아이템_53 │ ├── .gitkeep │ ├── 가변_인수는_신중히_사용하라_폰드.md │ └── 가변인수는_신중히_사용하라_켬미.md ├── 아이템_54 │ ├── .gitkeep │ ├── null이_아닌,_빈_컬렉션이나_배열을_반환하라_도비.md │ └── null이_아닌,_빈_컬렉션이나_배열을_반환하라_호티.md ├── 아이템_55 │ ├── .gitkeep │ ├── 옵셔널_반환은_신중히_하라_도비.md │ └── 옵셔널_반환은_신중히_하라_호티.md └── 아이템_56 │ └── .gitkeep ├── 09장 ├── .gitkeep ├── 아이템_57 │ ├── .gitkeep │ └── 지역변수의_범위를_최소화하라_켬미.md ├── 아이템_58 │ ├── .gitkeep │ └── 전통적인_for_문보다는_for-each_문을_사용하라_배키.md ├── 아이템_59 │ ├── .gitkeep │ └── 라이브러리를_익히고_사용하라_배키.md ├── 아이템_60 │ └── .gitkeep ├── 아이템_61 │ └── .gitkeep ├── 아이템_62 │ └── .gitkeep ├── 아이템_63 │ └── .gitkeep ├── 아이템_64 │ └── .gitkeep ├── 아이템_65 │ └── .gitkeep ├── 아이템_66 │ └── .gitkeep ├── 아이템_67 │ └── .gitkeep └── 아이템_68 │ └── .gitkeep ├── 10장 ├── .gitkeep ├── 아이템_69 │ └── .gitkeep ├── 아이템_70 │ └── .gitkeep ├── 아이템_71 │ └── .gitkeep ├── 아이템_72 │ └── .gitkeep ├── 아이템_73 │ └── .gitkeep ├── 아이템_74 │ └── .gitkeep ├── 아이템_75 │ └── .gitkeep ├── 아이템_76 │ └── .gitkeep └── 아이템_77 │ └── .gitkeep ├── 11장 ├── .gitkeep ├── 아이템_78 │ └── .gitkeep ├── 아이템_79 │ └── .gitkeep ├── 아이템_80 │ └── .gitkeep ├── 아이템_81 │ └── .gitkeep ├── 아이템_82 │ └── .gitkeep ├── 아이템_83 │ └── .gitkeep └── 아이템_84 │ └── .gitkeep ├── 12장 ├── .gitkeep ├── 아이템_85 │ └── .gitkeep ├── 아이템_86 │ └── .gitkeep ├── 아이템_87 │ └── .gitkeep ├── 아이템_88 │ └── .gitkeep ├── 아이템_89 │ └── .gitkeep └── 아이템_90 │ └── .gitkeep ├── README.md └── 백지 복습.md /.github/workflows/update-readme.yml: -------------------------------------------------------------------------------- 1 | name: Update README 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**/*.md' 7 | - '**/*.py' 8 | 9 | jobs: 10 | update-readme: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 # Important: this allows fetching all history for all branches and tags 17 | 18 | - name: Set up Python Env 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: '3.10' 22 | 23 | - name: Install Dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install pyyaml 27 | 28 | - name: Run Python Script for Update README.md 29 | run: python .github/scripts/update_readme.py 30 | 31 | - name: Commit changes 32 | run: | 33 | git config --global user.email "kimdobby@g.skku.edu" 34 | git config --global user.name "Dobby-Kim" 35 | git add README.md 36 | git commit -m "Auto Updated README.md with latest contributions" || echo "No changes to commit" 37 | 38 | - name: Pull changes 39 | run: git pull --rebase origin main 40 | 41 | - name: Push changes 42 | run: git push 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea/** 2 | **/.gitkeep 3 | 4 | **/.github/** 5 | 6 | **/README.md 7 | -------------------------------------------------------------------------------- /.obsidian/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "showUnsupportedFiles": true 3 | } -------------------------------------------------------------------------------- /.obsidian/appearance.json: -------------------------------------------------------------------------------- 1 | { 2 | "accentColor": "", 3 | "interfaceFontFamily": "Gmarket Sans TTF", 4 | "textFontFamily": "Gmarket Sans TTF", 5 | "monospaceFontFamily": "Gmarket Sans TTF", 6 | "cssTheme": "Blue Topaz" 7 | } -------------------------------------------------------------------------------- /.obsidian/themes/Blue Topaz/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blue Topaz", 3 | "version": "2024042601", 4 | "minAppVersion": "1.0.0", 5 | "author": "WhyI & Pkmer", 6 | "authorUrl": "https://github.com/whyt-byte" 7 | } 8 | -------------------------------------------------------------------------------- /.obsidian/themes/Things/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Things", 3 | "version": "2.1.20", 4 | "minAppVersion": "1.0.0", 5 | "author": "@colineckert", 6 | "authorUrl": "https://twitter.com/colineckert" 7 | } 8 | -------------------------------------------------------------------------------- /02장/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_1/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_1/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_1/api문서.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_1/api문서.png -------------------------------------------------------------------------------- /02장/아이템_2/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_2/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_2/pizza_상속관계.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_2/pizza_상속관계.png -------------------------------------------------------------------------------- /02장/아이템_3/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_3/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_3/private_생성자나_열거_타입으로_싱글턴임을_보증하라_폰드.md: -------------------------------------------------------------------------------- 1 | ## 싱글턴 2 | *인스턴스를 오직 하나만 생성할 수 있는 클래스* 3 | - 무상태 객체(stateless) 4 | - 설계상 유일해야 하는 시스템 컴포넌트 5 | 6 | >클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다. 7 | > > 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면, 가짜 구현으로 대체할 수 없기 때문. 8 | 9 | ### private 생성자 사용 10 | private을 통해 생성자를 숨김. 11 | 인스턴스에 접근할 유일한 수단: public static 멤버 12 | #### public static 멤버가 final 필드인 방식 13 | ```java 14 | public class Coffee { 15 | public static final Coffee INSTANCE = new Coffee(); 16 | 17 | private Coffee() { 18 | if (INSTANCE != null) throw new IllegalStateException(); //리플렉션 API를 통한 생성자 호출 방지 19 | } 20 | } 21 | ``` 22 | 생성자는 Coffee.INSTANCE를 초기화 할 때 한 번만 호출된다. 23 | 인스턴스가 하나뿐임이 보장. 24 | - 해당 클래스가 싱글턴임이 명백히 드러남. 25 | - 간결하다. 26 | #### public static 멤버가 정적 팩터리 메서드인 방식 27 | ```java 28 | public class Coffee { 29 | private static final Coffee INSTANCE = new Coffee(); 30 | 31 | private Coffee() { 32 | } 33 | 34 | public static Coffee getInstance() { 35 | if (INSTANCE == null) {//리플렉션 API를 통한 생성자 호출 방지 36 | return new Coffee(); 37 | } 38 | return INSTANCE; 39 | } 40 | } 41 | ``` 42 | 항상 같은 객체의 참조를 반환하므로 인스턴스가 하나뿐임이 보장. 43 | - API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있음. 44 | - 제네릭 싱글턴 팩토리로 만들 수 있다. 45 | - 정적 팩터리의 메소드 참조를 supplier로 사용할 수 있다. 46 | - ```java 47 | Supplier supplier=Coffee::getInstance; 48 | ``` 49 | 위 장점들이 굳이 필요하지 않다면 public static 멤버가 final 필드인 방식이 낫다. 50 | > 예외 51 | > AccessibleObject.setAccessible() 사용해서 private 생성자에 접근하는 경우 52 | >> 두 번째 인스턴스가 생성되지 않도록 방어 가능 53 | 54 | **역직렬화 문제** 55 | - 직렬화: 자바에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용할 수 있도록 바이트 스트림 형태의 연속적인 데이터로 변환하는 포맷 변환 기술 56 | - 역직렬화: 바이트로 변환 데이터를 원래대로 변환하는 기술 57 | 58 | 직렬화된 싱글턴 인스턴스를 송신자가 역직렬화 하면 새로운 인스턴스가 생성됨. 59 | >모든 인스턴스 필드에 transient를 추가하고, readResolve 메서드 추가해서 해결 가능 60 | > ```java 61 | > private Object readResolve() { 62 | > return INSTANCE; 63 | > } 64 | >``` 65 | 66 | 67 | ### 원소가 하나인 열거타입 선언(바람직) 68 | ```java 69 | public enum Coffee { 70 | INSTANCE; 71 | } 72 | ``` 73 | - 간결함 74 | - 역직렬화 문제 없음. 75 | - 리플렉션 문제 없음. 76 | - 대부분의 상황에서 가장 좋은 방법이다. 77 | 단, 다른 클래스를 상속 받아야 한다면 이 방법은 사용 불가. 78 | -------------------------------------------------------------------------------- /02장/아이템_4/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_4/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_4/item4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_4/item4.png -------------------------------------------------------------------------------- /02장/아이템_4/인스턴스화를_막으려거든_private_생성자를_사용하라_프람.md: -------------------------------------------------------------------------------- 1 | # 아이템04: 인스턴스화를 막으려거든 private 생성자를 사용하라 2 | > 작성자: 프람
3 | > 작성일시: 2024_04_17 4 | 5 | 6 | ## 01. 인스턴화가 불필요한 경우 7 | 8 | 자바로 코드를 작성하다보면 종종 Util class[1]를 만드는 경우가 있다. 9 | 10 | 그 예시는 JDK 표준 API에서 볼 수 있는데, 대표적으로 `java.lang.Math`가 있다. 11 | 12 | ```java 13 | public final class Math { 14 | ...생략 15 | public static double sqrt(double a) { 16 | return StrictMath.sqrt(a); 17 | } 18 | 19 | public static int abs(int a) { 20 | return (a < 0) ? -a : a; 21 | } 22 | 23 | public static double floor(double a) { 24 | return StrictMath.floor(a); 25 | } 26 | 생략... 27 | } 28 | ``` 29 | 30 | 위 java.lang.Math의 예시 코드와 같이 별도의 인스턴스 변수를 가지지 않고 정적 메서드만을 가지는 class를 util라 한다(일종의 helper class). 31 | 따라서, util class의 경우에는 인스턴스화가 불필요하다 볼 수 있다. 32 | 33 | 가령 우리가 String를 파싱하는 기능을 가지는 Util class가 있다고 가정해보자. 34 | 35 | ```java 36 | public class ParseUtil { 37 | public static List parseByDelimiter(final String plaintext, final String delimiter) { 38 | return Arrays.stream(plaintext.split(delimiter)) 39 | .toList(); 40 | } 41 | } 42 | ``` 43 | 44 | 아래, 해당 Util class를 사용하는 코드를 사용하는 클라이언트를 보자
45 | ```java 46 | ParseUtil parseUtil = new ParseUtil(); 47 | List parsedNameTextList = parseUtil.parseByDelimiter("프람,호티,배키,켬미,제우스,폰드,도비,초롱", ","); 48 | System.out.println(parsedNameTextList); 49 | ``` 50 | 컴파일러가 ~~(코드 작성자가 생성자를 정의하지 않는 경우)~~ 자동으로 기본 생성자를 만들어주기 때문에, 51 | 위 코드 처럼 기본 생성자로 ParseUtils을 인스터스화할 수 있다.
52 | 53 | ```java 54 | List parsedNameTextList = ParseUtil.parseByDelimiter("프람,호티,배키,켬미,제우스,폰드,도비,초롱", ","); 55 | System.out.println(parsedNameTextList); 56 | ``` 57 | 이렇게 코드를 대체해도 기능적으로 똑같다.
58 | 따라서, 위 예시가 본 아이템에서 설명하는 불필요한 인스턴스화라고 할 수 있겠다. 59 | 60 | ## 02. 그래서 인스턴스화를 막으려거든? 61 | 62 | ### 02-01. 방법1: Util class를 `abstract class`로 만든다. 63 | 오라클의 공식 튜토리얼을[2] 살펴보면 `abstract class는 인스턴스화를 할수 없다.` 설명하고 있다. 64 | oracle-abstract-class-defination 65 | 66 | IDE에서도 컴파일 에러로 잡아주고 있는 모습이 아래와 같이 보인다. 67 | abstract-class-impl 68 | 69 | abstract-class-client 70 | 71 | 하지만, 이 방법에는 몇 가지 문제점이 존재한다. 72 | + 상속해서 사용하면 그만이다. 73 | + abstract class의 사용 용도를 생각하여, 다른 개발자가 상속을 해서 사용하라는 뜻으로 오해 할 여지가 생긴다. 74 | 75 | ### 02-02. 방법2: private 생성자를 사용하라. 76 | 아이템의 제목과 같이, 기본 생성자를 private으로 명시적으로 작성한다.
77 | ```java 78 | public class ParseUtil { 79 | 80 | private ParseUtil() {} 81 | 82 | public static List parseByDelimiter(final String plaintext, final String delimiter) { 83 | return Arrays.stream(plaintext.split(delimiter)) 84 | .toList(); 85 | } 86 | } 87 | 88 | ``` 89 | 이렇게 된다면 컴파일러가 묵시적으로 기본 생성자를 생성하는 것을 막을 수 있다.
90 | 또한, 해당 ***객체 외부에서는 생성자를 호출할 수 없기 때문에 인스턴스화 역시 막을 수 있다.*** 91 | 92 | 하지만! 이 방법에도 문제가 있다. 93 | 94 | + 객체 내부에서는 기본 생성자를 통한 인스턴스화 가능하지 않다. 95 | + '작성한 코드를 사용하지 않는다?' 논리적인 모순이 생긴다. 96 | 97 | 따라서 해당 문제를 해결하기 위한 방법은 2가지 있다. 98 | 1. private 생성자에 주석으로 **인스턴스가 불가능한 클래스**임을 주석으로 문서화하여 알린다. 99 | 2. 아래 예시와 같이 Error를 통해 접근을 불가능함을 알린다. 100 | ```java 101 | private ParseUtil() { 102 | throw new AssertionError("인스턴스화를 할 수 없는 클래스입니다."); 103 | } 104 | 105 | ``` 106 | 107 | 책의 저자는 1번 방법과 2번 방법을 같이 사용할 것을 권장하고 있다. 108 | 109 | ```java 110 | //기본 생성자가 만들어지는 것을 막는다(인스턴스 방지용). 111 | private ParseUtil() { 112 | throw new AssertionError("인스턴스화를 할 수 없는 클래스입니다."); 113 | } 114 | ``` 115 | 116 | ## 03. 결론 117 | 불필요한 인스턴스화를 막을려거든 private 생성자를 통해 생성자에 대한 접근을 막아줘라. 118 | 119 | --- 120 | #### 참고자료 121 | [[1]Java Helper vs. Utility Classes](https://www.baeldung.com/java-helper-vs-utility-classes)
122 | [[2] Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html) 123 | 124 | -------------------------------------------------------------------------------- /02장/아이템_4/인스턴스화를_막으려거든_private_생성자를_사용하라_호티.md: -------------------------------------------------------------------------------- 1 | 2 | # 아이템04: 인스턴스화를 막으려거든 private 생성자를 사용하라 3 | 4 | ## 정적 메서드와 정적 필드만을 담은 클래스 예시 5 | 6 | 정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때가 존재한다. 7 |
(객체지향적으로 사고하지 않는 사람들이 종종 남용하는 방식이기도 하다.) 8 | 9 | 10 | 11 | 12 | 하지만 `Math`와 `Arrays`처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 수 있다. 13 | 14 | ```java 15 | // java.lang.Math 16 | public static final double PI = 3.14159265358979323846; 17 | 18 | // java.util.Arrays.java 19 | public static void sort(int[] a, int fromIndex, int toIndex) { 20 | rangeCheck(a.length, fromIndex, toIndex); 21 | DualPivotQuicksort.sort(a, 0, fromIndex, toIndex); 22 | } 23 | ``` 24 | 25 | 또한, `java.util.Collections`처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(or 팩토리)를 모아놓을 수도 있다. 26 | 27 | ```java 28 | // java.util.Collections 29 | 30 | public static Collection unmodifiableCollection(Collection c) { 31 | if (c.getClass() == UnmodifiableCollection.class) { 32 | return (Collection) c; 33 | } 34 | return new UnmodifiableCollection<>(c); 35 | } 36 | 37 | /** 38 | * @serial include 39 | */ 40 | static class UnmodifiableCollection implements Collection, Serializable { 41 | @java.io.Serial 42 | private static final long serialVersionUID = 1820017752578914078L; 43 | 44 | @SuppressWarnings("serial") // Conditionally serializable 45 | final Collection c; 46 | 47 | UnmodifiableCollection(Collection c) { 48 | if (c==null) 49 | throw new NullPointerException(); 50 | this.c = c; 51 | } 52 | ... 53 | ``` 54 | 55 | 마지막으로 `final` 클래스와 관련한 메서드들을 모아놓을 때도 사용한다. 56 |
57 | **→ 유틸리티 클래스 생성을 위해** 58 |
59 | (`final` 클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가능하기 때문에) 60 | 61 | ## 정적 멤버 혹은 메서드만을 가진 클래스 인스턴스화 62 | 63 | 정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다. 64 | (`Collections` -> 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아놓은 것) 65 | 66 | 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다 67 |
68 | 매개변수를 받지 않는 `public` 생성자가 만들어지며 의도치 않게 인스턴스화 할 수 있게 된 클래스가 종종 있다 69 | 70 | 또한, 추상 클래스로 만드는 것으로는 인스턴스화를 막을 수는 없다 71 |
72 | → 하위 클래스를 만들어 인스턴스화 하면 그만이기 때문에 73 | 74 | ## 그러면 어떻게 해야 인스턴스화를 막을 수 있을까? 75 | 76 | **`private` 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다** 77 | 78 | - 상속을 불가능하게 하는 효과도 존재 79 | 80 | 모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데,
81 | 이를 `private`으로 선언했으니 하위 클래스가 상위 클래스의 생성자에 길이 막혀버린다 82 | 83 | 84 | ```java 85 | public class UilityClass { 86 | //기본 생성자가 만들어지는 것을 막는다 87 | private UtilityClass(){ 88 | throw new AssertionError(); 89 | } 90 | } 91 | ``` 92 | 93 | > **AssertionError**는 코드가 개발자의 기대대로 작동하지 않는 경우를 나타내는 데 사용된다. 94 |
**AssertionError**는 프로그램의 논리적 오류나 잠재적인 버그를 포착할 때 유용하다. 95 | 96 | 97 | 꼭 예외를 `AssertionError`를 던질 필요는 없지만, 클래스 안에서 실수로라도 생성자를 호출하지 않도록 해준다. 98 | 99 | 생성자가 분명 존재하는데 호출할 수 없다는 것은 그다지 직관적이지 않으니 적절한 주석 또한 달아두도록 하자. 100 | 101 | ## 핵심 정리 102 | - 인스턴스화를 막으려거든 `private` 생성자를 사용하라. 103 | - 생성자가 분명 존재하는데 호출할 수 없다는 것은 그다지 직관적이지 않으니 적절한 주석 또한 달아두도록 하자. 104 | -------------------------------------------------------------------------------- /02장/아이템_5/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_5/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_6/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_6/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_6/불필요한_객체_생성을_피하라_폰드.md: -------------------------------------------------------------------------------- 1 | **불필요한 객체** 생성을 피하라 2 | ### 똑같은 기능의 객체 3 | **똑같은 기능의 객체**를 매번 생성하기보다는 객체 하나를 재사용하는 것을 고려한다. 4 | 불변 객체는 언제든 재사용 가능 5 | 6 | - String Literal 7 | ```java 8 | 🤮String s= new String("abc"); //Heap 영역에 새로 생성 9 | ``` 10 | ```java 11 | String s= "abc"; //String Constant Pool 에서 재사용 12 | ``` 13 | 리터럴로 선언된 문자열을 사용함으로서 쓸데없는 문자열 인스턴스 생성을 방지한다. 14 |

15 | - 정적 팩토리 메서드 16 | ```java 17 | 🤮Boolean b = new Boolean("false"); //자바 9에서 deprecated API 지정 18 | ``` 19 | ```java 20 | Boolean b = Boolean.valueOf("true"); 21 | //정적 팩토리 메서드 22 | public static Boolean valueOf(String s) { 23 | return parseBoolean(s) ? TRUE : FALSE; 24 | } 25 | //미리 생성된 객체 26 | public static final Boolean TRUE = new Boolean(true); 27 | public static final Boolean FALSE = new Boolean(false); 28 | ``` 29 | Boolean은 valueOf 메서드를 통해 미리 생성된 객체를 재활용한다. 30 | 31 | 32 | 33 | ### 생성 비용이 비싼 객체 34 | - 캐싱 35 | ```java 36 | static boolean isRomanNumeral(String s) { 37 | return s.matches("정규표현식"); 38 | }//String.matches 메서드 사용의 반복이 성능에 영향을 줄 수 있다. 39 | 40 | public boolean matches(String regex) { 41 | return Pattern.matches(regex, this); 42 | } 43 | 44 | public static boolean matches(String regex, CharSequence input) { 45 | 🤮Pattern p = Pattern.compile(regex); 46 | Matcher m = p.matcher(input); 47 | return m.matches(); 48 | } 49 | ``` 50 | matches 내부에서 생성되는 Pattern 인스턴스(불변)는 한 번 쓰고 버려져, GC 대상이 된다. 51 | *Pattern은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높다* 52 | ```java 53 | public class RomanNumerals { 54 | private static final Pattern ROMAN = Pattern.compile("정규표현식"); 55 | 56 | static boolean isRomanNumeral(String s) { 57 | return ROMAN.matcher(s).matches(); 58 | } 59 | } 60 | ``` 61 | 정규표현식을 표현하는 Pattern 인스턴스를 정적초기화 과정에서 캐싱해두고, 나중에 문자열 형태를 확인하는 메서드가 호출 될 때마다 이 인스턴스를 재사용한다. 62 | ### 어댑터 63 | 객체 하나당 어댑터 하나씩만 만들어지면 충분하다. 64 | 65 | ```java 66 | Map map = new HashMap<>(); 67 | map.put(1,"a"); 68 | map.put(2,"b"); 69 | 70 | Set keys1=map.keySet(); 71 | 🤮Set keys2=map.keySet(); 72 | ``` 73 | keySet을 호출 할 때마다 새로운 Set 인스턴스가 만들어지는 것이 아님. 74 | 모두가 똑같은 Map 인스턴스를 대변하기 떄문에 여러개 만들 필요가 없다. 75 | 76 | ### 오토 박싱 77 | ```java 78 | private static long sum() { 79 | 🤮Long sum = OL; 80 | for (long i= 0; i <= Integer.MAX_VALUE; i++){ 81 | sum += i; //불필요한 Long 인스턴스 생성 82 | } 83 | return sum; } 84 | ``` 85 | 의도치 않은 오토박싱이 들어가지 않도록 주의 86 | 87 | ### 주의사항 88 | - 객체 생성은 비싸니 피해야한다는 의미가 아님. 요즘 JVM에서 작은 객체를 생성하고 회수하는 일은 큰 부담이 아니다. 89 | - 아주 무거운 객체가 아니라면, 객체 생성을 피하기 위해 객체 풀을 만들지 말자. 90 | 일반적으로 코드를 헷갈리게 만들고 메모리 사용량을 늘림. 91 | - 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해 >> 불필요한 객체를 반복 생성했을 때의 피해 92 | -------------------------------------------------------------------------------- /02장/아이템_7/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_7/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_7/after_pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_7/after_pop.png -------------------------------------------------------------------------------- /02장/아이템_7/gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_7/gc.png -------------------------------------------------------------------------------- /02장/아이템_7/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_7/stack.png -------------------------------------------------------------------------------- /02장/아이템_8/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_8/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_9/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_9/.gitkeep -------------------------------------------------------------------------------- /02장/아이템_9/exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_9/exception.png -------------------------------------------------------------------------------- /02장/아이템_9/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_9/img.png -------------------------------------------------------------------------------- /02장/아이템_9/stack_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/02장/아이템_9/stack_frame.png -------------------------------------------------------------------------------- /02장/아이템_9/try-finally보다는_try-with-resources를_사용하라_초롱.md: -------------------------------------------------------------------------------- 1 | # try-finally의 문제점 2 | 자바 라이브러리에는 `InputStream`, `OutputStream`, `java.sql.Connection`등 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. 3 | 자원 닫기는 놓치기 쉬워 예측할 수 없는 성능 문제로 이어지기도 한다. 4 | 5 | 전통적으로 이를 해결하기 위해 try-finally가 쓰였다. 6 | 다만 이는 몇 가지 문제점들이 있다. 7 | 8 | ## 코드의 지저분함 9 | try-finally 방식은 아래 코드와 같이 자원이 둘 이상일 경우 코드가 너무 지저분해지고, 실수를 저지를 가능성이 커진다. 10 | ```java 11 | static void copy(String src, String dst) throws IOException { 12 | Inputstream in = new FileInputStream(src); 13 | try { 14 | OutputStream out = new FileOutputStream(dst); 15 | try { 16 | byte[] buf = new byte[BUFFER_SIZE]; 17 | int n; 18 | while ((n=in.read(buf)) >= 0){ 19 | out.write(buf, 0, n); 20 | } 21 | } finally { 22 | out.close(); 23 | } 24 | } finally { 25 | in.close(); 26 | } 27 | } 28 | ``` 29 | 30 | ## 예외가 겹침 31 | 예외는 try 블록과 finally 블록 모두에서 발생할 수 있다. 32 | 33 | 아래 코드를 통해 살펴보자. 34 | ```java 35 | static String firstLineOfFile(String path) throws IOException { 36 | BufferedReader br = new BufferedReader(new FileReader(path)); 37 | try { 38 | return br.readLine(); 39 | } finally { 40 | br.close(); 41 | } 42 | } 43 | ``` 44 | 만약 기기에 물리적인 문제가 발생하여 readLine 메서드가 예외를 던진다고 하면, 같은 이유로 close 메서드도 실패하고 결국 try와 finally 모두에서 예외를 던지게 된다. 45 | 그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않고, finally 블록에서 발생한 예외만 체크하게 된다. 46 | 이는 실제 시스템에서의 디버깅을 몹시 어렵게 한다. 47 | 48 | # try-with-resources 49 | 이러한 문제들은 자바 7에서 등장한 try-with-resources 덕에 모두 해결되었다. 50 | 51 | 한 가지 주의할 점은 이 구조를 사용하기 위해서는 해당 자원이 `AutoCloseable` 인터페이스를 반드시 구현해야 한다. 52 | `AutoCloseable` 인터페이스는 void를 반환하는 close 메서드 하나만 덩그러니 정의한 인터페이스다. 53 | 54 | 다음은 try-with-resources를 사용하여 firstLineOfFile 메서드를 재작성한 예다. 55 | ```java 56 | static String firstLineOfFile(String path) throws IOException { 57 | // before 58 | BufferedReader br = new BufferedReader(new FileReader(path)); 59 | try { 60 | return br.readLine(); 61 | } finally { 62 | br.close(); 63 | } 64 | 65 | // after 66 | try (BufferedReader br = new BufferedReader(new FileReader(path))) { 67 | return br.readLine(); 68 | } 69 | } 70 | ``` 71 | 72 | 아래는 가장 위의 코드인 copy 메서드에 try-with-resources를 적용한 모습이다. 73 | ```java 74 | static void copy(String src, String dst) throws IOException { 75 | // before 76 | Inputstream in = new FileInputStream(src); 77 | try { 78 | OutputStream out = new FileOutputStream(dst); 79 | try { 80 | byte[] buf = new byte[BUFFER_SIZE]; 81 | int n; 82 | while ((n = in.read(buf)) >= 0) { 83 | out.write(buf, 0, n); 84 | } 85 | } finally { 86 | out.close(); 87 | } 88 | } finally { 89 | in.close(); 90 | } 91 | 92 | // after 93 | try (Inputstream in = new FileInputStream(src); 94 | OutputStream out = new FileOutputStream(dst)) { 95 | byte[] buf = new byte[BUFFER_SIZE]; 96 | int n; 97 | while ((n = in.read(buf)) >= 0) { 98 | out.write(buf, 0, n); 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | try-with-resources 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋다. 105 | 106 | 이전에 예시를 들었던 firstLineOfFile 메서드에서 readLine과 close 호출 양쪽에서 예외가 발생하는 경우를 떠올려보자. 107 | 만약 try-with-resources를 사용한다면 close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다. 108 | 109 | 또한, 이렇게 숨겨진 예외들도 그냥 버려지지 않고, 스택 추적 내역에 `숨겨졌다(suppressed)`는 꼬리표를 달고 출력된다. 110 | 자바 7에서 Throwable에 추가된 getSuppressed 메서드를 이용하면 프로그램 코드에서 가져올 수도 있다. 111 | 112 | 보통의 try-finally에서처럼 catch 절을 사용할 수 있고, 이 덕분에 try문을 더 중첩하지 않고도 다수의 예외를 처리할 수 있다. 113 | 114 | # 결론 115 | 꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자. 116 | 이는 권장이 아닌 **필수**이다. 117 | 코드는 더 짧고 분명하며, 만들어지는 예외 정보도 훨씬 유용하다. 118 | 사용하지 않을 이유가 전혀 없다. 119 | 120 | # 부록 121 | 몰라도 되지만 재미있는 사실이 하나 있다. 122 | 그건 바로 기존의 Closeable에 부모 인터페이스로 AutoCloseable을 추가했다는 점이다. 123 | 보통은 반대로 사용하는 것이 일반적인데 자바 개발자들은 먼저 만들어진 Closable 인터페이스에 부모 인터페이스인 AutoClosable을 추가함으로써 하위 호환성 100%를 달성했다. 124 | 125 | ![img.png](img.png) 126 | 127 | 만약 Closeable을 부모로 만들었다면 기존에 Closeable을 사용하던 모든 클래스를 AutoCloseable을 사용하도록 변경해야했을 것이다. 128 | 129 | 이로 인해 기존에 Closeable을 사용하던 모든 클래스들은 try-with-resources에서 사용할 수 있게 되었다. 130 | -------------------------------------------------------------------------------- /03장/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_10/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_10/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_10/item_10_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_10/item_10_1.png -------------------------------------------------------------------------------- /03장/아이템_10/item_10_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_10/item_10_2.png -------------------------------------------------------------------------------- /03장/아이템_10/item_10_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_10/item_10_3.png -------------------------------------------------------------------------------- /03장/아이템_10/same.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_10/same.jpeg -------------------------------------------------------------------------------- /03장/아이템_11/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_11/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_11/equals를_재정의하려거든_hashCode도_재정의하라_도비.md: -------------------------------------------------------------------------------- 1 | > KEYWORD: hashCode, equals, 자바, 해시 테이블, 일관성 2 | 3 | ## 들어가기에 앞서 Hash란 무엇일까? 4 | 5 | 6 | 해시 테이블은 효율적인 데이터 검색을 위해 널리 사용되는 데이터 구조 중 하나로, 키를 값에 매핑하여 데이터의 빠른 검색, 삽입, 삭제를 지원한다. 7 | git d 8 | 9 | 10 | ### 해시 테이블의 작동 원리 11 | 12 | #### 1. 해싱 함수 13 | 14 | - **정의**: 해시 테이블은 해싱 함수를 사용하여 키를 해시 코드로 변환한다. 해시 코드는 일반적으로 키를 배열의 인덱스로 변환하는 데 사용된다. 15 | - **목표**: 좋은 해싱 함수는 충돌을 최소화하고, 해시 테이블의 공간을 균일하게 사용하도록 해야 한다. 16 | 17 | #### 2. 충돌 해결 방법 18 | 19 | 해시 테이블에서는 두 개 이상의 키가 같은 해시 값을 가질 때 충돌이 발생한다. 충돌을 해결하는 두 가지 일반적인 방법은 다음과 같다. 20 | 21 | ##### a. 체이닝 (Chaining) 22 | 23 | - **원리**: 각 인덱스에 연결 리스트를 사용하여 같은 해시 값을 가진 키들을 저장한다. 24 | - **장점**: 구현이 간단하며, 해시 테이블의 로드 팩터가 클 때도 비교적 성능이 좋다. 25 | 26 | ##### b. 오픈 어드레싱 (Open Addressing) 27 | 28 | - **원리**: 충돌이 발생하면, 다른 해시 버킷을 찾아 데이터를 저장한다. 선형 조사(linear probing), 이차 조사(quadratic probing), 이중 해싱(double hashing) 등의 방법이 있다. 29 | - **장점**: 메모리를 더 효율적으로 사용할 수 있다. 30 | 31 | #### 3. 리사이징 (Resizing) 32 | 33 | - **필요성**: 로드 팩터(load factor, 테이블에 저장된 항목 수와 위치의 수 비율)가 높아지면, 충돌의 빈도가 증가하고 성능이 저하될 수 있다. 34 | - **동작**: 해시 테이블의 크기를 조정하고 모든 항목을 새로운 테이블에 재해싱하여 성능을 유지한다. 35 | 36 | 37 | ## 그래서 HashCode는 왜 Override 해야 하는 걸까? 38 | 39 | `hashCode` 메서드의 올바른 구현은 Java의 컬렉션 프레임워크, 특히 `HashMap`과 `HashSet`과 같은 해시 기반 컬렉션을 효율적으로 사용하기 위해 매우 중요하다. `equals` 메서드를 재정의한 클래스에서 `hashCode`도 올바르게 재정의하지 않으면, 해시 기반 컬렉션의 성능이 저하되거나 더 심각한 문제가 발생할 수 있다. 이를 위해 Java의 `Object` 클래스에서 제공하는 `hashCode` 일반 규약을 준수해야 한다. 40 | 41 | ### hashCode의 일반 규약 42 | 43 | Java의 `Object` 클래스 명세에 따르면 `hashCode` 메서드는 다음과 같은 규약을 지켜야 한다. 44 | 45 | #### 1. 일관성 46 | 47 | - **정의**: 객체의 `equals` 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 해당 객체의 `hashCode` 메서드는 몇 번을 호출해도 항상 같은 값을 반환해야 한다. 48 | - **예외**: 애플리케이션을 재실행할 때는 값이 달라질 수 있다. 49 | 50 | #### 2. 동등 객체의 동일 해시코드 51 | 52 | - **정의**: `equals(object)`가 두 객체를 같다고 판단했다면, 이 두 객체의 `hashCode`는 반드시 같은 값을 반환해야 한다. 53 | - **문제**: `Object`의 기본 `hashCode` 메서드는 물리적으로 다른 두 객체에 대해 서로 다른 값을 반환할 수 있어, 이 규약을 위반할 수 있다. 54 | 55 | #### 3. 동등하지 않은 객체의 해시코드 56 | 57 | - **정의**: `equals(object)`가 두 객체를 다르다고 판단했다면, 두 객체의 `hashCode`가 서로 다른 값을 반환할 필요는 없으나, 다른 객체에 대해서는 가능한 한 다른 값을 반환하는 것이 해시 테이블의 성능을 개선한다. 58 | 59 | ### hashCode 재정의 예시 60 | 61 | `Person` 클래스에 대한 `hashCode` 메서드의 올바른 구현 예를 살펴보자. 62 | 63 | 64 | ```java 65 | public class Person { 66 | private String name; 67 | private int age; 68 | 69 | ... 70 | 71 | @Override 72 | public int hashCode() { 73 | int result = 17; // 초기 값 74 | result = 31 * result + Short.hashCode(name) ; // int 필드의 해시 코드 계산 75 | result = 31 * result + Short.hashCode(age); // 문자열 필드의 해시 코드 76 | return result; 77 | } 78 | } 79 | 80 | 81 | ``` 82 | **설명**: 83 | 84 | - **일관성 보장**: `name`과 `age`가 변경되지 않는 한, `hashCode` 호출 결과는 항상 같다. 85 | - **동등 객체 동일 해시코드**: 동등한 `Person` 객체는 동일한 `name`과 `age` 값을 가지므로 동일한 해시코드 값을 반환한다. 86 | - **효율적인 해시 분포**: 다른 `name`과 `age`를 가진 객체는 가능한 다른 해시코드 값을 반환하도록 구현함으로써 해시 테이블의 성능을 최적화한다. 87 | 88 | 89 | ### 잠깐만요, 31 곱하는건 왜 하는거죠? 90 | 91 | ```java 92 | result = 31 * result + Short.hashCode(name) ; 93 | result = 31 * result + Short.hashCode(age); 94 | ``` 95 | 96 | 97 | 98 | (i << 5) - i = i * 31 99 | (i << 5) - i 100 | 101 | 102 | 잎서 작성한 hashCode() 메서드를 살펴보면, 31이라는 뜬금 없는 숫자를 곱하여 hashCode를 만든다. 103 | 104 | **왜 31이며, 이걸 왜 곱하는걸까?** 105 | 106 | 답부터 말해보자면, 31은 **홀수이면서 Prime Number**이기 때문이다. 107 | 108 | 글의 서두에 언급한 Hash를 이용한 자료구조는 Hash Function을 이용하여 Hash Tabale에 정보를 매핑하는데, 이때 Hash Code는 Table의 특정 위치를 가르키게 된다. 109 | 110 | 이때 Hash Code를 Hash Function에서 `%(mod)` 나누기 연산을 하여 테이블에서의 실질적 위치를 구하게 되는데, 위의 코드는 이 과정의 역과정인것이다. 111 | 112 | 그렇기에 아무런 약수가 없는 Prime Number인 31은 Hash Code의 역변환을 진행했을 때, Hash Table 위에 매핑되는 데이터의 Hash Key의 중복이 상대적으로 최소화될 수 있다. (Hash Table에서 충돌이 발생하면 이 충돌을 해소하기 위한 방법들은 추가적인 자원을 소모하기 때문에 충돌을 최대한 피해야한다) 113 | -------------------------------------------------------------------------------- /03장/아이템_11/item_11_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_11/item_11_1.png -------------------------------------------------------------------------------- /03장/아이템_11/item_11_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_11/item_11_2.png -------------------------------------------------------------------------------- /03장/아이템_12/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_12/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_12/toString을_항상_재정의하라_켬미.md: -------------------------------------------------------------------------------- 1 | # Item12. toString을 항상 재정의하라 2 | 3 |
4 | 5 | ## 🤔 toString은 언제 사용될까요? 6 | 7 | - toString을 호출할 때 ! `(수동)` 8 | - 객체를 println, printf, 문자열 연산자(+), assert 구문에 넘길 때 `(자동)` 9 | - 디버거가 객체를 출력할 때 `(자동)` 10 | - 객체를 참조하는 컴포넌트가 오류 메시지를 로깅할 때 `(자동)` 11 | 12 |
13 | 14 | ## 🤔 재정의 해야 돼. 왜? 15 | 16 | 앞서 말했듯이 **내가 직접 호출하지 않아도, 우리도 모르게 toString을 쓴다.** 17 | 18 | 즉, 제대로 재정의하지 않는다면, 쓸모없는 메시지가 로그나 프로그램에 남겨진다. 19 | 20 | **왜 쓸데없지?** Object의 기본 toString 메서드가 우리가 작성한 클래스에 적합한 문자열을 반환하는 경우는 거의 없다 21 | 22 | 그래서 toString을 재정의해서 '**간결하면서 사람이 읽기 쉬운 형태의 유익한 정보**'를 반환해야 한다. 23 | 24 | > **재정의 안 한 toString** 25 | > 26 | > PhoneNumber@adbbd : 클래스명@16진수로_표현한_해시코드 27 | > 28 | > 010-3322-1100 이라고 알려주면 더 좋지 않을까? 29 | 30 |
31 | 32 | ## 🤔 어떤 경우 해야 돼? 33 | 34 | ### 모든 하위 클래스 35 | 36 | >'모든 하위 클래스에서 이 메서드를 재정의해라' 37 | > 38 | > 상위 클래스에서 이미 알맞게 재정의한 경우는 예외 ~! 39 | 40 |
41 | 42 | toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅하기 쉽다. 43 | 44 |
45 | 46 | ### 하위 클래스들이 공유해야 할 문자열 표현이 있는 추상 클래스 47 | 48 | 대다수의 컬렉션 구현체는 추상 컬렉션 클래스들의 toString 메서드를 상속해 쓴다. 49 | 50 |
51 | 52 | ## 🤔 그럼 안해도 되는 건 없어? 53 | 54 | 정적 유틸리티 클래스나 대부분의 열거타입은 이미 완벽한 toString을 제공하니 따로 재정의하지 않아도 된다. 55 | 56 |
57 | 58 | ## 🤔 그럼 어떻게 재정의? 59 | 60 | ### 💡 1. 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 담아라 61 | 62 | 63 | > [!Example] 64 | > System.out.println(phoneNumber + "에 연결할 수 없습니다."); 65 | > 66 | > -> (재정의 X) PhoneNumber@adbbd에 연결할 수 없습니다. 67 | > -> (재정의 O) 010-3322-1100에 연결할 수 없습니다. 68 | 69 | > [!Example] 70 | > Map.of(("Jenney", new PoneNumber("010-3322-1100"))); 71 | > 72 | > -> (재정의 X) Jenney=PhoneNumber@adbbd 73 | > -> (재정의 O) Jenney=0103322-1100 74 | 75 |
76 | 77 | ### 💡 2. 그 객체가 가진 주요 정보 모두를 담아라 78 | 79 |
80 | 81 | #### 주요정보가 담기지 않았을 때 문제 82 | 83 | ```java 84 | class Data { 85 | private final int id; 86 | private final String name; 87 | private final int age; 88 | 89 | public Data(int id, String name, int age) { 90 | this.id = id; 91 | this.name = name; 92 | this.age = age; 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | return "{ %s , %d }".formatted(name, age); 98 | } 99 | 100 | // 3가지 정보가 다 동일하면 같은 객체하기로 함 101 | @Override 102 | public boolean equals(Object o) { 103 | if (this == o) return true; 104 | if (o == null || getClass() != o.getClass()) return false; 105 | Data data = (Data) o; 106 | return id == data.id && age == data.age && Objects.equals(name, data.name); 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | return Objects.hash(id, name, age); 112 | } 113 | } 114 | ``` 115 | 116 |
117 | 118 | Data 객체의 toString을 위와 같이 재정의했을 때 다음 테스트 결과 로그는 어떻게 될까? 119 | 120 | ```java 121 | @Test 122 | void test() { 123 | assertThat(new Data(1, "켬미", 24)) 124 | .isEqualTo(new Data(2, "켬미", 24)); 125 | } 126 | ``` 127 | 128 | 바로 다음과 같다. 129 | 130 | ![](https://i.imgur.com/1Y7lVHz.png) 131 | 132 |
133 | 134 | 135 | 해당 로그만 보면 우리는 이런 생각을 할 수도 있다 "**왜 실패했지, 지금 로그 상으로는 모든 값이 동일한데?!**" 136 | 137 | _**그러니 최대한 모든 객체를 toStirng에 담는 것이 좋다**_ 138 | 139 |
140 | 141 | ### 💡 + 요약 정보를 반환해라 (객체가 거대하거나, 객체 상태가 문자열로 표현하기에 적합하지 않다면 ?) 142 | 143 |
144 | 145 | 객체가 거대하거나, 객체 상태가 문자열로 표현하기에 적합하지 않다면, 요약 정보를 반환해라 146 | 147 | ```java 148 | class PhoneNumbers { 149 | private String cityName; 150 | private List phoneNumbers; 151 | 152 | public PhoneNumbers(String cityName, List phoneNumbers) { 153 | this.cityName = cityName; 154 | this.phoneNumbers = phoneNumbers; 155 | } 156 | 157 | @Override 158 | public String toString() { 159 | return "%s 거주자 전화번호부(총 %d개)".format(cityName, phoneNumber.size()); 160 | } 161 | } 162 | ``` 163 | 164 |
165 | 166 | > 이상적으로는 스스로를 완벽히 설명하는 문자열이어야 한다. 167 | 168 |
169 | 170 | ### 💡 3. 반환 값의 포맷을 문서화할지 정해라 171 | 172 | **장점** 173 | - 명확하고, 사람이 읽을 수 있게 된다 174 | - 값 그대로 입출력에 사용하거나, CSV 파일처럼 사람이 읽을 수 있는 데이터 객체로 저장할 수도 있다. 175 | - ex. BigInteger, BigDecimal과 대부분의 기본 타입 클래스 176 | 177 | 178 | **단점** 179 | - 포맷을 명시하면 계속 포맷에 얽매이게 된다. 180 | - 구체적인 만큼 유연성이 없다 181 | 182 |
183 | 184 | #### 포맷을 명시하든 아니든 여러분의 의도는 명확히 밝혀야한다. 185 | 186 |
187 | 188 | 189 | ```java 190 | // 전화번호: 포맷 명시 O 191 | // 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 192 | // XXX 는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호이다 193 | @Override 194 | public Strng toString() { 195 | return String.format("%03d-%03d-%04d", 196 | areaCode, prefix, lineNum); 197 | } 198 | 199 | // 약물: 포맷 명시 X 200 | // 그냥 멋지게 출력 ex) 유형=사랑, 냄새=테레빈유 201 | @Override 202 | public Strng toString() { 203 | } 204 | ``` 205 | 206 |
207 | 208 | 사실 어떻게 해도 상관 없음 209 | 210 | _**중요한 것은 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자**_ 211 | 212 | -------------------------------------------------------------------------------- /03장/아이템_12/toString을_항상_재정의하라_폰드.md: -------------------------------------------------------------------------------- 1 | **toString을 항상 재정의하라** 2 | 3 | ### toString의 규약 4 | - 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보 반환 5 | - 모든 하위 클래스에서 이 메서드를 재정의하라 6 | 7 | ### 당위성 8 | - toString을 잘 구현한 클래스는 **디버깅**하기 쉽다 9 | - 객체를 println,printf,문자열 연결 연산자, assert구문에 넘길 때, 디버거가 객체를 출력할 때 자동으로 불린다. 10 | - 직접 호출하진 않더라도 다른 어딘가에서 쓰일 테니 재정의 하라. 11 | 12 | ### 사용 방법 13 | - 객체가 가진 주요 정보 모두를 반환하는 게 좋다. 14 | - 객체가 너무 거대하거나, 객체의 상태가 문자열로 표현하기 적합하지 않다면 요약 정보를 담는다. 15 | 16 | ### 문서화 17 | - 반환값의 포맷을 **문서화** 할지 정한다. 18 | - 포맷에 맞는 문자열과 객체를 상호 전환 할 수 있는 정적 팩터리나 생성자를 함께 제공해주면 좋다. 19 | - 포맷을 한번 명시하면, 포맷 평생 얽매이게 된다는 단점도 있다. 20 | - 포맷을 명시하든 아니든 의도는 명확히 밝혀야 한다. 21 | ```java 22 | 포맷 명시 23 | /** 24 | * 이 전화번호의 문자열 표현을 반환한다. 25 | * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 26 | * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. 27 | * 각각의 대문자는 10진수 숫자 하나를 나타낸다. 28 | * 29 | * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, 30 | * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 31 | * 전화번호의 마지막 네 문자는 "0123"이 된다. 32 | */ 33 | @Override public String toString() { 34 | return String.format("%03d-%03d-%04d", 35 | areaCode, prefix, lineNum); 36 | } 37 | 38 | 포맷 명시 x 39 | /** 40 | * 이 약물에 대한 대략적인 설명을 반환한다 41 | * 다음은 이 설명의 일반적인 형태이나, 42 | * 상세 형식은 정해지지 않았으며 향후 변경될 수 있다. 43 | * 44 | * "[약물 #9: 유형=사랑, 냄새=테레빈유, 겉모습=먹물]" 45 | */ 46 | @Override public String toString() { ... } 47 | ``` 48 | - 포맷 명시 여부와 상관없이 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자. 49 | - 제공되지 않는다면, 반환값을 파싱해서 얻어야 하므로, 성능도 나빠지고 불필요한 작업을 하게 된다. 50 | 51 | ### 기타 52 | - 정적 유틸리티 클래스는 toString을 제공할 이유가 없다. 53 | - 열거 타입은 이미 완벽한 toString을 제공. 54 | -------------------------------------------------------------------------------- /03장/아이템_13/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_13/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_13/clone_재정의는_주의해서_진행하라_폰드.md: -------------------------------------------------------------------------------- 1 | **clone 재정의는 주의해서 진행하라** 2 | ## clone 메서드 특징 3 | - Cloneable 인터페이스: 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스 4 | - Object에 선언: clone 메서드는 Cloneable 인터페이스에 선언 되어 있지 않다. 5 | - native 코드로, 소스코드는 jvm.cpp에 위치 6 | ```java 7 | public interface Cloneable {} //마커 인터페이스 8 | ``` 9 | ```java 10 | public class Object { 11 | ... 12 | protected native Object clone() throws CloneNotSupportedException;// 실제 선언된 위치 13 | ... 14 | } 15 | ``` 16 | - protected 접근 제어자 17 | ## clone 사용 방법 18 | ### 동작 방식 19 | - Cloneable 구현 후, clone()을 호출하면, 객체의 필드들을 하나하나 복사(얕은 복사)한 객체를 반환 20 | - Cloneable을 구현하지 않고, clone() 호출 시 CloneNotSupportedException 발생 21 | >인터페이스를 이례적으로 사용한 방법🤮 22 | 23 | ### 사용 방법 24 | 실무에서는 clone메서드가 public으로 제공되고, 인스턴스 복제가 제대로 이뤄지길 기대함. 25 | 해당 기대를 만족하기 위한 모순적인 메카니즘(생성자 호출 없이 객체 생성)과 허술한 규약을 지켜야 함. 26 | >x.clone() != x //참 27 | x.clone().getClass() == x.getClass() //super.clone()을 통해 객체를 얻는다는 관례를 지킨다면 참 28 | x.clone().equals(x) // 일반적으로 참이지만, 필수 아님. 29 | 30 | **올바른 동작을 위한 clone 재정의** 31 | - 접근제어자 public 변경 32 | - super.clone()을 통한 객체 호출 33 | - try-catch 블록으로 검사 예외(checked exception) 처리 34 | throws절을 없애줘, 사용하기 편하게 만들어 준다. 35 | - 공변 반환 타이핑으로 클라이언트가 형변환 하지 않게끔 구현. 36 | - 모든 필드가 기본 타입이거나, 불변 객체 참조라면 더 수정할 것이 없다. 37 | ```java 38 | @Override 39 | public PhoneNumber clone() { 40 | try { 41 | return (PhoneNumber) super.clone(); 42 | } catch (CloneNotSupportedException e) { 43 | throw new AssertionError(); 44 | } 45 | } 46 | ``` 47 | **가변 객체를 참조한다면?** 48 | 49 | ```java 50 | public class Stack implements Cloneable{ 51 | private Object[] elements;🤮 52 | private int size=0; 53 | ... 54 | } 55 | ``` 56 | - **clone의 재귀적 호출** 57 | clone을 사용한다면, 원본 elements를 똑같이 참조하기 때문에 원본과 복제본이 서로 영향을 받음.👎 58 | 59 | ```java 60 | @Override 61 | public Stack clone() { 62 | try { 63 | Stack result = (Stack) super.clone(); 64 | result.elements = elements.clone(); 65 | return result; 66 | }catch(CloneNotSupportedException e){ 67 | throw new AssertionError(); 68 | } 69 | } 70 | ``` 71 | 스택 내부 정보를 복사→elements 배열의 clone을 재귀적으로 호출해서 해결 72 | >**배열의 clone** 73 | >형변환 필요 없음 74 | 런타임 타입과 컴파일 타임 타입 모두가 원본 배열과 똑같은 배열을 반환 75 | > 배열 복제할 때는 clone 사용 권장 76 | > 77 | elements가 final이었다면 앞선 방식은 작동하지 않음. 78 | Cloneable 아키텍처는 '가변 객체를 참조하는 필드는 final로 선언하라'는 일반 용법과 충돌🤮 79 | 때문에 일부 필드에서 final 한정자를 제거해야 할 수도 있다. 80 | - **clone의 재귀적 호출 만으로 충분하지 않을 때** 81 | ```java 82 | public class HashTable implements Cloneable { 83 | private Entry[] buckets =...; 84 | 85 | private static class Entry { 86 | final Object key; 87 | Object value; 88 | Entry next; 89 | 90 | Entry(Object key, Object value, Entry next) { 91 | this.key = key; 92 | this.value = value; 93 | this.next = next; 94 | } 95 | } 96 | 97 | @Override 98 | public HashTable clone(){ 99 | try{ 100 | HashTable result = (HashTable) super.clone(); 101 | result.buckets = buckets.clone();🤮 102 | return result; 103 | } catch (CloneNotSupportedException e){ 104 | throw new AssertionError(); 105 | } 106 | } 107 | } 108 | ``` 109 | Stack에서처럼 bucket 배열의 clone을 재귀적으로 호출하면, **원본과 같은 연결리스트를 참조하는 문제** 발생 110 | **깊은 복사**를 지원해서 각 bucket을 구성하는 연결리스트를 복사해야 한다. 111 | ```java 112 | @Override 113 | public HashTable clone(){ 114 | try{ 115 | HashTable result = (HashTable) super.clone(); 116 | result.buckets = new Entry[buckets.length]; 117 | for(int i = 0; i < buckets.length; i++){ 118 | if(buckets[i] != null){ 119 | result.buckets[i] = buckets[i].deepCopy(); 120 | } 121 | } 122 | return result; 123 | } catch (CloneNotSupportedException e){ 124 | throw new AssertionError(); 125 | } 126 | } 127 | ``` 128 | - 재귀 129 | - 반복자로 순회 130 | 131 | ### 주의 사항 132 | - clone에서는 하위 클래스에서 재정의 될 수 있는 메서드를 호출하지 않아야 한다. 133 | 원본과 복제본의 상태가 달라질 가능성이 크다.(동적 바인딩) 134 | - clone 메서드는 적절히 동기화해줘야 한다. 135 | 136 | ## 더 나은 객체 복사 방식 137 | ### 복사 생성자, 복사 팩터리 138 | - 모순적인 객체 생성 메커니즘 x 139 | - 엉성한 문서 규약 x 140 | - 불필요한 검사 예외 x 141 | - 형변환 필요 x 142 | - 인터페이스 타입의 인스턴스를 인수로 받을 수 있다. 143 | ```java 144 | public TreeSet(Collection c) { 145 | this(); 146 | addAll(c); 147 | } 148 | ``` 149 | -------------------------------------------------------------------------------- /03장/아이템_14/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_14/.gitkeep -------------------------------------------------------------------------------- /03장/아이템_14/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_14/img.png -------------------------------------------------------------------------------- /03장/아이템_14/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/03장/아이템_14/img_1.png -------------------------------------------------------------------------------- /04장/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_15/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_15/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_15/클래스와_멤버의_접근_권한을_최소화하라_배키.md: -------------------------------------------------------------------------------- 1 | # [item 15] 클래스와 멤버의 접근 권한을 최소화하라 2 | 3 | > **어설프게 설계된 컴포넌트 vs 잘 설계된 컴포넌트** 4 | > **어설프게 설계된 컴포넌트** : 클래스의 내부 데이터와 구현을 잘 숨기지 못함 5 | > **잘 설계된 컴포넌트** : 클래스의 모든 내부 구현을 완벽히 숨김 (구현과 API를 깔끔히 분리) 6 | > 핵심 : **정보 은닉**(캡슐화) 7 | 8 | 9 | 10 | ## 정보 은닉의 장점 11 | * 시스템의 개발 속도를 높임 : 컴포넌트를 병렬로 개발 12 | * 시스템 관리 비용을 낮춘다 : 더 빨리 디버깅하고 컴포넌트를 쉽게 교체 가능하다. 13 | * 성능 최적화에 도움을 준다 : 다른 컴포넌트에 영향을 주지 않고 최적화할 컴포넌트만 최적화한다. 14 | * 소프트웨어 재사용성을 높인다 15 | * 큰 시스템을 제작하는 난이도를 낮춰준다. 16 | 17 | 18 | ## 정보 은닉을 하는 법 19 | > **❗접근 제한자를 적극적으로 활용하자❗** 20 | 21 | 22 | ### 톱 레벨 클래스와 인터페이스에 접근 제한자를 사용하는 법 23 | 톱 레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준 : `package-private`, `public` 24 | 25 | 26 | #### public 접근 제한자 27 | `public` 톱 레벨 클래스, 인터페이스 : 공개 API가 된다. 28 | * 클라이언트가 사용하는 공개 API는 하위 호환을 위해 영원히 관리해줘야만 한다. 29 | 30 | #### private-package 접근 제한자 31 | `package-private` 톱 레벨 클래스, 인터페이스 : 내부 구현이 된다. 32 | * 패키지 안 에서만 사용할 수 있다. 때문에 클라이언트에 아무런 피해 없이 수정, 교체, 제거 등을 할 수 있다. 33 | 34 | > 한 클래스에서만 사용하는 `package-private` 톱 레벨 클래스나 인터페이스는 사용하는 클래스 안에 `private static`으로 중첩시켜보자. 35 | > 한 곳에서만 사용하니까 같은 패키지에 있는 모든 클래스가 접근할 필요가 없다. 36 | 37 | 38 | 39 | ### 멤버에 접근 제한자를 사용하는 법 40 | 멤버에 부여할 수 있는 접근 수준 : `private`, `package-private`, `protected`, `public` 41 | * `private` : 멤버를 선언한 톱 클래스에서만 접근 가능 42 | * `package-private` : 멤버가 소속된 패키지 안의 모든 클래스에서 접근 43 | * `protected` : `package-private` : 접근 범위를 포함하고 이 멤버를 선언한 클래스의 하위 클래스에도 접근 가능 44 | * `public` : 모든 곳에 접근 가능 45 | 46 | 47 | 48 | ## 접근 제한자를 적절하게 사용하는 법 49 | 가능한 접근 범위를 좁히자. 만약 접근 제한을 넓혀줘야 한다면, 클래스를 분리해야 하는 것은 아닌지 고민하기 50 | 51 | 52 | 53 | ## 접근 제한자를 사용할 때 주의할 점 54 | #### (1) 상위 클래스의 메서드 보다 접근제어자를 좁게 설정 불가 55 | 리스코프 치환 원칙에 의해 상위 클래스의 메서드를 재정의할 때는 메서드의 접근 수준을 상위 클래스에서 보다 좁게 설정할 수 없다. 56 | ```java 57 | public class Animal { // 상위 클래스 58 | 59 | protected void move() { 60 | System.out.println("걷는다."); 61 | } 62 | } 63 | ``` 64 | ```java 65 | public class Dog extends Animal { // 하위 클래스 66 | 67 | @Override 68 | public void move() { // 접근 제어자 확장 가능! 69 | System.out.println("4발로 걷는다."); 70 | } 71 | } 72 | 73 | public class Dog extends Animal { // 하위 클래스 74 | 75 | @Override 76 | private void move() { // 🤮 접근 제어자를 좁히면 컴파일 오류 발생! 77 | System.out.println("4발로 걷는다."); 78 | } 79 | } 80 | ``` 81 | > 리스코프 치환 원칙 : 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체할 수 있다. 82 | > `Animal animal = new Dog();` 83 | > `Dog dog = new Animal(); // 🤮 하위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체될 수 없다.` 84 | 85 | 86 | #### (2) 테스트를 위해 접근 제한자를 어느정도 바꿀 수 있다. 87 | 코드를 테스트하는 목적으로 접근 범위를 넓히는 것은 적당한 수준까지 괜찮다. 88 | * `public` 클래스의 `private` 멤버를 `package-private` 까지 풀어줄 수 있다. 그 이상은 안된다. 89 | * 테스트 만을 위해 클래스, 인터페이스, 멤버를 공개 API로 만들어서는 안된다. 90 | 91 | 92 | #### (3) public 클래스의 인스턴스 필드는 되도록 public이 되서는 안된다. 93 | 만약 public 클래스의 인스턴스가 public이면 아래와 같은 문제점이 발생한다. 94 | * 필드가 수정될 때, 다른 작업을 할 수 없게 된다. 이는 스레드 안전하지 않다. 95 | * 여러 클라이언트에서 필드를 수정할 때, 동시에 접근할 수 있다. 96 | * 해당 필드와 관련된 모든 것은 불변식을 보장할 수 없다. 97 | * 외부에서 바꿀 수 있다. 98 | 99 | 100 | > 그러나, 객체가 상수라면, p`ublic static final 필드`로 공개해도 좋다! 101 | > 예외로, `public static final`은 길이가 0이 아닌 배열은 모두 변경 가능하니 주의하자. 102 | > 아래와 같이 제공하면 안된다. 103 | ```java 104 | public class Things { 105 | public static final Thing[] VALUES = {new Thing("0"), new Thing("1")}; 106 | } 107 | ``` 108 | > 그 이유는 배열은 `final`로 제공해도 배열이 가리키는 객체에 대한 수정이 가능하기 때문이다. 109 | ```java 110 | public class Main { 111 | public static void main(String[] args) { 112 | Things.VALUES[1] = new Thing("2"); // Thing("1") => Thing("2") 113 | } 114 | } 115 | ``` 116 | > 해결책 117 | > `public` 배열을 `private`으로 만들고 복사본을 반환하는 `public` 메서드를 추가한다. 118 | ```java 119 | public class Things { 120 | private static final Thing[] PRIVATE_VALUES = {new Thing("0"), new Thing("1")}; 121 | public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); 122 | } 123 | ``` 124 | > 배열을 `private`으로 만들고 복사본을 반환하는 `public` 메서드 추가 125 | ```java 126 | public class Things { 127 | private static final Thing[] PRIVATE_VALUES = {new Thing("0"), new Thing("1")}; 128 | public static final Thing[] values() { 129 | return PRIVATE_VALUES.clone(); 130 | } 131 | } 132 | ``` 133 | [정리 내용 - 리스트를 방어적 복사하는 법](https://radical-pegasus-520.notion.site/d49af490373746ab8d66e45ec2f4df58?pvs=4) 134 | 135 | 136 | 137 | ## 암묵적 접근 수준 138 | java9 이후로 모듈 시스템이 도입되면서 암묵적 접근 수준이 추가되었다. 139 | > 모듈이란? 140 | > 클래스의 모음이 패키지인 것처럼, 패키지의 모음이 모듈이다. 141 | 142 | 143 | #### 모듈 시스템의 방식 144 | 외부에 공개할 모듈을 `module-info.java` 파일에 선언하는 것이다. 145 | 146 | 147 | #### 모듈 시스템이 암묵적 접근 수준이 된 이유 148 | 클래스가 `protected` 혹은 `public` 멤버라도 해당 패키지를 모듈 시스템을 통해 공개하지 않았다면 해당 클래스는 외부에서 접근할 수 없기 때문이다. 149 | 이런 모듈 시스템을 이용하면, 클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서는 자유롭게 공유할 수 있다. 150 | ex) `public` 클래스의 `public` 멤버 -> 이 멤버의 접근 수준은 `public`이나, 그 효과가 묘듈 내부로 한정된다. 151 | -------------------------------------------------------------------------------- /04장/아이템_15/클래스와_멤버의_접근_권한을_최소화하라_제우스.md: -------------------------------------------------------------------------------- 1 | # 아이템 16: 클래스와 멤버의 접근 권한을 최소화하라 2 | 3 | ## 캡슐화의 장점 4 | 5 | - 여러 컴포넌트를 병렬로 개발할 수 있기 때문에 개발 속도를 높인다. 6 | - 컴포넌트를 파악하고 교체하는 부담이 적기 때문에 관리 비용을 낮춘다. 7 | - 성능 최적화에 도움을 준다. 8 | - 외부에 의존하지 않기 때문에 재사용성을 높인다. 9 | - 전체가 완성되지 않아도 부분 동작을 검증할 수 있기 때문에 큰 시스템을 만드는 난이도를 낮춘다. 10 | 11 | ## 원칙 12 | 13 | > 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다. 14 | 15 | **즉, 항상 가장 낮은 접근 수준을 부여해야 한다.** 16 | 17 | 패키지 외부에서 사용할 이유가 없다면 package-private으로 선언하자. 18 | 19 | 그렇게하면 클라이언트에 아무런 피해 없이 수정, 교체, 제거할 수 있다. 20 | 21 | **반면 public으로 선언한 API는 호환을 위해 영원히 관리해야 한다.** 22 | 23 | ## 접근 제어자 종류 24 | 25 | 접근 제어자는 클래스와 멤버에 부여하는 접근 수준을 의미한다. 26 | 27 | 멤버는 필드, 메서드, 중첩 클래스, 중첩 인터페이스를 의미한다. 한마디로 클래스 안에 선언하는 것들. 28 | 29 | - private: 해당 멤버를 선언한 최상위 클래스에서만 접근 가능 30 | - (default) package-private 31 | - protected: package-private + 하위 클래스 32 | - public: 외부 접근 가능 33 | 34 | ### 설계 방법 35 | 36 | 1. public API를 세심히 설계한다. 37 | 2. 모든 멤버를 private으로 선언한다. 38 | 3. 같은 패키지의 다른 클래스가 접근해야 하는 멤버를 package-private으로 풀어준다. 39 | 40 | private과 package-private은 공개 API에 영향을 주지 않는다. 41 | 42 | protected의 멤버는 접근하기 쉬우므로 적을수록 좋다. 43 | 44 | ## 테스트를 위한 접근 제어자 45 | 46 | 테스트를 위해 private을 package-private으로 넓히는 것만 된다. 47 | 48 | 테스트와 태스트 대상을 같은 패키지에 위치시키자. 49 | 50 | ## public 클래스의 public 필드 51 | 52 | public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다. 53 | 54 | 왜? 55 | 56 | public 필드가 가변 객체를 참조하는 경우, final이 아닌 경우 해당 필드의 값을 제한할 수 없다. 57 | 58 | -> 누구나 값을 바꿀 수 있다. 59 | 60 | public 필드는 스레드 안전하지 않다. 61 | 62 | ### 예외 63 | 64 | 상수 `public static final` 65 | 66 | 이때도 반드시 primitive 타입 또는 불변 객체를 가져야 한다. 67 | 68 | #### 배열은 가변이므로 주의하라 69 | 70 | ```java 71 | public static final Thing[] VALUES = { ...}; 72 | ``` 73 | 74 | 위에서 언급한 문제를 그대로 갖는다. 75 | 76 | 만약 이런 코드를 사용해야 한다면 다음과 같이 해결하자. 77 | 78 | ##### 방법1 79 | 80 | ```java 81 | private static final Thing[] PRIVATE_VALUES = { ... }; 82 | public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); 83 | ``` 84 | 85 | ##### 방법2 86 | 87 | ```java 88 | private static final Thing[] PRIVATE_VALUES = { ... }; 89 | 90 | public static final Thing[] values() { 91 | return PRIVATE_VALUES.clone(); 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /04장/아이템_16/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_16/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_16/public_클래스에서는_public_필드가_아닌_접근자_메서드를_사용하라_배키.md: -------------------------------------------------------------------------------- 1 | # [item 16] public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 2 | 3 | --- 4 | 5 | ### public 클래스에서 가변 필드의 접근 제한자를 public으로 사용하는 경우 문제점 6 | 아래 클래스를 보자. 7 | ```java 8 | class Point { 9 | public double x; // 멤버 변수의 접근 제한자가 public! 10 | public double y; 11 | } 12 | ``` 13 | public 필드를 제공하면 아래와 같은 단점이 있다. 14 | * 불변식을 보장할 수 없다. 15 | * API(톱 레벨 클래스)를 수정하지 않고서 내부 구현을 바꿀 수 없다. 16 | 17 | > API를 수정하지 않고서 내부 구현을 바꿀 수 없다는 말은 무엇일까? 18 | > 아래 코드가 있다. 19 | ```java 20 | public class MyClass { 21 | public int myField; 22 | 23 | public MyClass(int myField) { 24 | this.myField = myField; 25 | } 26 | } 27 | ``` 28 | ```java 29 | public class AnotherClass { 30 | public void doSomething(MyClass obj) { 31 | System.out.println(obj.myField); 32 | } 33 | } 34 | ``` 35 | > 만약, 여기서 MyClass의 필드가 private으로 바뀐다면, 36 | > AnotherClass.doSomething(MyClass obj)의 obj.myField 사용에서 컴파일 오류가 난다. 37 | > 이는 두 클래스 모두를 수정해야하는 상황이 온다. 이런 상황을 말하는 것이다. 38 | 39 | --- 40 | 41 | ### 해결 방법 42 | 위에서 말한 문제점을 해결하기 위해 필드를 private으로 바꾸고 public 접근자를 추가한다. 43 | ```java 44 | public Point { 45 | private double x; 46 | private double y; 47 | 48 | public Point(double x, double y) { 49 | this.x = x; 50 | this.y = y; 51 | } 52 | 53 | public double getX() {return x;} 54 | public double getY() {return y;} 55 | public void setX(double x) { this.x = x; } 56 | public void setY(double y) { this.y = y; } 57 | ``` 58 | 이와 같이 코드를 작성하면 아래와 같은 장점이 있다. 59 | * 내부 표현을 마음대로 바꿀 수 있다. 이전에는 클라이언트가 필드를 직접 참조하여 내부 표현을 바꾸기가 어려웠지만 지금은 사용하지 않기 때문에 쉽다. 60 | 61 | --- 62 | 63 | ### 불변 객체를 참조하는 public 필드라면? 64 | 직접 노출시 단점이 조금은 줄어들지만, 좋은 생각이 아니다. 65 | * API를 변경하지 않고는 표현 방식을 바꿀 수 없다. 66 | * 필드를 읽을 때 부수작업을 수행한다. 67 | 단, 불변식은 보장할 수 있다. 68 | -------------------------------------------------------------------------------- /04장/아이템_16/public_클래스에서는_public_필드가_아닌_접근자_메서드를_사용하라_제우스.md: -------------------------------------------------------------------------------- 1 | # 아이템 16: public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 2 | 3 | 잘못된 코드 4 | 5 | ```java 6 | class Point { 7 | public double x; 8 | public double y; 9 | } 10 | ``` 11 | 12 | 개선된 코드 13 | 14 | ```java 15 | class Point { 16 | private double x; 17 | private double y; 18 | 19 | public Point(double x, double y) { 20 | this.x = x; 21 | this.y = y; 22 | } 23 | 24 | public double getX() { 25 | return x; 26 | } 27 | 28 | public double getY() { 29 | return y; 30 | } 31 | 32 | public double setX(double x) { 33 | this.x = x; 34 | } 35 | 36 | public double setY(double y) { 37 | return this.y = y; 38 | } 39 | } 40 | ``` 41 | 42 | 접근자를 제공함으로써 내부 표현 방식을 바꿀 유연성을 얻을 수 있다. 43 | 44 | 접근자란 getter, setter를 의미한다. 45 | 46 | ## package-private 또는 private 중첩 클래스 47 | 48 | 두 경우에는 오히려 좋을 수도 있다. 49 | 50 | 어차피 중첩 클래스의 클라이언트는 외부 클래스이기 때문이다. 51 | 52 | 아래는 Item 2의 피자 예제다. 53 | 54 | ```java 55 | public abstract class Pizza { 56 | 57 | public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } 58 | 59 | final Set toppings; 60 | 61 | abstract static class Builder> { 62 | 63 | EnumSet toppings = EnumSet.noneOf(Topping.class); 64 | 65 | public T addTopping(Topping topping) { 66 | toppings.add(topping); 67 | return self(); 68 | } 69 | 70 | abstract Pizza build(); 71 | 72 | protected abstract T self(); 73 | } 74 | 75 | Pizza(Builder builder) { 76 | toppings = builder.toppings.clone(); 77 | } 78 | } 79 | ``` 80 | 81 | 외부 클래스 Pizza와 내부 정적 클래스 Builder가 있다. 82 | 83 | Pizza 생성자에서 Builder의 toppings 필드에 직접 접근하는 게 보이는가? 84 | 85 | 이렇게 어떤 클래스가 단 하나의 클래스와만 관계를 맺는다면, 내부 클래스로 작성하는 게 좋다. 86 | 87 | 이때는 필드에 접근하는 게 아무런 문제가 없을 뿐더러, 오히려 두 클래스의 관계를 이해하기 좋다. 88 | -------------------------------------------------------------------------------- /04장/아이템_17/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_17/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_17/변경_가능성을_최소화하라_초롱.md: -------------------------------------------------------------------------------- 1 | # 불변 클래스 2 | - 인스턴스의 내부 값을 수정할 수 없는 클래스 3 | - 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다. 4 | - 자바 플랫폼 라이브러리에는 다양한 불변 클래스가 있다. 5 | - String, 기본 타입의 래핑된 클래스들, BigInteger, BigDecimal 6 | 7 | ## 불변 클래스를 만드는 규칙 8 | - 객체의 상태를 변경하는 메서드(setter)를 제공하지 않는다. 9 | - 클래스를 확장할 수 없도록 한다. 10 | - final로 선언한다. 11 | - 모든 생성자를 private으로 만든 후 정적 팩토리 메서드를 사용한다. 12 | - `super()`를 호출할 수 없어 상속이 불가능하다. 13 | - 모든 필드를 private final로 선언한다. 14 | - 클라이언트에서 인스턴스 내에 가변 객체의 참조를 얻을 수 없게 해야한다. 15 | - Collection에 대한 getter를 제공하면 안 된다. 16 | - 생성자, 접근자(getter), readObject 메서드 모두에서 방어적 복사를 수행하라. 17 | 18 | ## 불변 객체를 써야 하는 이유 19 | ### 1. 스레드 안전 20 | - 불변 객체는 근본적으로 thread-safe 하여 따로 동기화할 필요가 없다. 21 | - 여러 스레드가 동시에 사용해도 절대 훼손되지 않는다. 22 | - 따라서 **안심하고 공유할 수 있다.** 23 | - 불변 클래스라면 한 번 만든 인스턴스를 최대한 재활용하는 것을 권한다. 24 | - ex) 상수(public static final), 캐싱(정팩메) 25 | - 아무리 복사해봐야 원본과 똑같으니 방어적 복사가 필요없다. 26 | - 불변 클래스는 clone 메서드나 복사 생성자를 제공하지 않는 것이 좋다. 27 | 28 | ### 2. 내부 데이터 공유 29 | - 불변 객체끼리는 내부 데이터를 공유할 수 있다. 30 | ```java 31 | public class BigInteger extends Number implements Comparable { 32 | final int signum; // 값의 부호 33 | final int[] mag; //값의 크기 34 | 35 | public BigInteger negate() { 36 | return new BigInteger(this.mag, -this.signum); 37 | } 38 | } 39 | ``` 40 | - 위 코드는 BigInteger 클래스 내부의 코드이다. 41 | - `negate` 메서드는 크기가 같고 부호만 반대인 새로운 BigInteger을 생성한다. 42 | - 배열은 가변이지만 복사하지 않고 원본 인스턴스와 공유해도 된다. 43 | - 그 결과 `negate` 메서드를 통해 새로 만들어진 BigInteger 인스턴스도 원본 인스턴스가 가리키는 내부 배열을 그대로 가리킨다. 44 | 45 | ### 3. 구성요소로 사용 46 | - 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다. 47 | - 구조가 아무리 복잡하더라도 불변식을 유지하기 훨씬 수월하다. 48 | - ex) Set의 구성 요소나 Map의 key 49 | - Map이나 Set은 안에 담긴 값이 바뀌면 불변식이 허물어진다.(?) 50 | 51 | ### 4. 실패 원자성 52 | - 불변 객체는 그 자체로 실패 원자성을 제공한다. 53 | - 실패 원자성이란 `호출된 메서드가 실패하더라도 해당 객체는 호출 전 상태를 유지해야 한다`는 성질이다. 54 | 55 | ## 단점 56 | - 값이 다르면 반드시 독립된 객체로 만들어야 한다. 57 | - 값의 가짓수가 많으면 비용이 크다. 58 | - ex) BigInteger의 비트 하나를 바꾸는 경우 59 | - 원하는 객체를 완성하기까지의 단계가 많고, 그 중간 단계에서 만들어진 객체들이 모두 버려진다면 성능 문제가 더 불거진다. 60 | 61 | ## 해결방안 62 | ### 다단계 연산(multistep operation) 63 | - 흔하게 쓰일 다단계 연산들을 기본 기능으로 미리 제공하는 방법이다. 64 | - 이를 가변 동반 클래스(companion class)라고 한다. 65 | - 대표적인 예로 String의 가변 동반 클래스인 StringBuilder가 있다. 66 | 67 | ## 정리 68 | - 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. 69 | - 값 객체(VO)는 항상 불변으로 만들자. 70 | - 불변으로 만들 수 없는 클래스는 변경할 수 있는 부분을 최소한으로 줄이자. 71 | - 합당한 이유가 없다면 모든 필드는 private final이어야 한다. 72 | - 생성자는 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다. 73 | - 확실한 이유가 없다면 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안 된다. 74 | - 객체를 재활용할 목적으로 상태를 다시 초기화하는 메서드도 안 된다. 75 | 76 | 77 | -------------------------------------------------------------------------------- /04장/아이템_17/변경_가능성을_최소화하라_프람.md: -------------------------------------------------------------------------------- 1 | # 변경 가능성을 최소화하라 2 | > 작성자: 프람
3 | > 작성일시: 2024_04_25 4 | 5 | 본 아이템에서는 변경 가능성을 최소화라 내용의 주된 내용은
6 | 불변 객체에 대한 것이다. 7 | 8 | 자바를 시작한지 얼마 되지 않은 사람이라도 불변 객체를 권장하는 말이나 글을 많이 보았을 것이다. 9 | 이번 기회를 통해 불변 객체의 이점을 중심으로 글을 정리하고자 한다. 10 | 11 | ## 01. 불변 객체란? 12 | 본 책에서는 불변 객체는 아래의 5가지 규칙을 따라야 한다 말한다. 13 | 하나씩 살펴보자. 14 | 15 | 1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
16 | → 즉, Setter를 두지 말라는 것이다. 다시 말해 외부에서 객체의 상태를 변화하면 않된다는 것이다. 17 | 2. 클래스를 확장할 수 없도록 한다.
18 | → 즉, 불변 객체의 상속을 막으라는 것이다. 19 | 3. 모든 필드를 final로 선언한다.
20 | → 내부 상태의 변화를 막으라는 것이다. 21 | 4. 모든 필드를 private으로 선언한다.
22 | → 즉, 내부 상태를 은닉하라는 것이다. 23 | 5. 자신 외에는 내부의 가변 컴포넌트에 접근 할 수 없도록 한다.
24 | → 즉, 가변 컴포넌트는 방어적 복사를 해야한다. (다른 방법도 존재한다) 25 | 26 | 결과적으로 위 5가지 규칙을 만족하는 예시 클래스를 보자 27 | ``` java 28 | //2. 클래스를 확장할 수 없도록 한다. 29 | final class ColoredPoint { 30 | //3. 모든 필드를 final로 선언한다. 31 | //4. 모든 필드를 private으로 선언한다. 32 | private final int x; 33 | private final int y; 34 | private final Color color; //Color class는 가변 컴포넌트라 가정한다. 35 | 36 | public ColoredPoint(int x, int y, Color color) { 37 | this.x = x; 38 | this.y = y; 39 | this.color = color; 40 | } 41 | 42 | public Color getColor() { 43 | //5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 44 | return new Color(color); 45 | } 46 | 47 | //1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. 48 | 49 | } 50 | ``` 51 | 주석을 통해 5가지의 규칙을 모드 만족한 것을 확인할 수 있다. 52 | 53 | 사실 여기까지는 꽤 당연한 이야기이다.
54 | 그렇다면, 이런 불변 객체의 55 | 56 | ## 02. 불변 객체의 사용 57 | 조금 더 복잡한 예제를 보자 58 | ```java 59 | public final class Complex { 60 | private final double re; 61 | private final double im; 62 | 63 | public Complex(double re, double im) { 64 | this.re = re; 65 | this.im = im; 66 | } 67 | 68 | public double realPart() { 69 | return re; 70 | } 71 | 72 | public double imaginaryPart() { 73 | return im; 74 | } 75 | 76 | public Complex plus(Complex c) { 77 | return new Complex(re + c.re, im + c.im); 78 | } 79 | 80 | public Complex minus(Complex c) { 81 | return new Complex(re - c.re, im - c.im); 82 | } 83 | 84 | public Complex times(Complex c) { 85 | return new Complex(re*c.re - im*c.im, re*c.im - im*c.re); 86 | } 87 | 88 | public Complex dividedBy(Complex c) { 89 | double tmp = c.re * c.re + c.im * c.im; 90 | return new Complex((re*c.re + im*c.im)/ tmp,(im*c.re - re*c.im)/ tmp); 91 | } 92 | } 93 | 94 | ``` 95 | 96 | 위 코드를 보면 final로 필드의 변경의 여지가 없다.
97 | 따라서, 내부 상태를 통해 새로운 객체를 반환해주는 방법으로 연산들을 하는 모습을 볼 수 있다. 98 | 여기까진 미션을 진행하면서 많은 피드백으로 다들 알고 있는 내용이라 생각한다. 99 | 100 | 그렇다면, 이제부터 본격으로 불변 객체의 장점들에 대해 알아보자!! 101 | 102 | ## 03. 불변 객체의 이점 103 | 104 | **함수형 프로그래밍 패턴**
105 | 연산을 하더라도 피연산자의 값의 변경x
106 | 107 | **단순한 사용**
108 | 생성에서 파괴까지 변경의 여지가 없기 때문에 복잡한 상태에서도 안심하고 사용이 가능하다.
109 | 110 | **스레드 세이프**
111 | 당연한 이야기지만 final로 불변 보장하기 때문에 다중 스레드에서도 안전한 상태로 `synchronized` 키워드 필요X
112 | 113 | **자유로운 공유 가능**
114 | 변경의 여지가 없기 때문에 방어적 복사도 필요X
115 | 116 | **내부 데이터 공유 가능**
117 | 가변 컴포넌트에 대한 값도 변경 여지가 없기 때문에 내부 데이터의 공유 역시 자유롭다.
118 | 119 | **실패 원자성**
120 | 프로그램 오류 시에도 불일치 상태에 빠질 여지 X
121 | 122 | ## 04. 결론 123 | 앵간하면 불변 객체로 사용해라~~ 124 | 125 | -------------------------------------------------------------------------------- /04장/아이템_18/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_18/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_18/상속보다는_컴포지션을_사용하라_켬미.md: -------------------------------------------------------------------------------- 1 | ## Item18. 상속보다는 컴포지션을 사용하라 2 | 3 |
4 | 5 | > **고민해보자 !** 6 | > 코드 재사용 -> 상속 사용 : 재사용하기에 강력한 수단은 맞음! 7 | > But. 항상 최선일까? 8 | > 오히려 오류를 내기 쉬운 소프트웨어가 제작될 수 있다 9 | 10 | 11 | > 여기서 말하는 상속은 (클래스가 다른 클래스를 확장하는) 구현 상속을 의미한다 12 | 13 |
14 | 15 | ### 상속의 문제점 - 캡슐화를 깨트린다 16 | 17 | > [참조](https://unluckyjung.github.io/oop/2021/03/17/Inheritance-and-Encapsulation/) 18 | 19 | 메서드 사용과 다르게 상속은 캡슐화를 깨트린다 20 | 21 |
22 | 23 | #### 캡슐화가 뭐야? 24 | 25 | 캡슐화 : 외부에서 내부의 정보를 들여다 보지 못하게 하는것. 26 | 27 | 은닉화 : (캡슐화로 얻을 수 있는 효과) 28 | 객체를 사용하는 외부에서는, 객체가 어떻게 구성되어있는지 정확히 알 필요도 없고, 알 필요도 없다. 29 | 30 |
31 | 32 | #### 어떻게 캡슐화가 깨져? -> 메서드 재정의로 인해서 33 | 34 | 상위클래스가 어떻게 구현되느냐에 따라 `Override`한 하위 클래스 동작에 이상이 생길 수 있다 35 | 36 | 1. 상위 클래스는 릴리즈마다 내부 구현이 달라질 수 있다 37 | 2. 상위 클래스의 구현이 달라진 내용으로 인해 하위 클래스는 코드 한 줄 변경 안했는데 오작동할 수 있다 38 | 39 | 즉, 하위 클래스는 상위 클래스의 변화에 맞춰서 수정되어야 한다. 40 | 41 |
42 | 43 | #### 상속 자체가 캡슐화를 깨? 44 | 45 | - 상속을 하는 행위 자체가 캡슐화를 망가뜨릴 수 있는 **가능성** 이 생긴다 46 | - 즉 상속이 캡슐화를 깬다 는 좀 과하지만 **캡슐화에 손상을 준다!** 라고 하면 좀 더 맞는거 같다. 47 | 48 | > 이펙티브 자바 원문 49 | > but it is problematic because it violates encapsulation. 50 | > - 캡슐화를 **위반한다** (깬다 X ) 51 | 52 |
53 | 54 | #### 무슨 소리야? (예시) 55 | 56 | ```java 57 | public class InstrumentedHashSet extends HashSet { 58 | // 추가된 원소의 수 59 | private int addCount = 0; 60 | 61 | public InstrumentedHashSet() { 62 | } 63 | 64 | @Override 65 | public boolean add(E e) { 66 | addCount++; 67 | return super.add(e); 68 | } 69 | 70 | @Override 71 | public boolean addAll(Collection c) { 72 | addCount += c.size(); 73 | return super.addAll(c); 74 | } 75 | 76 | public int getAddCount() { 77 | return addCount; 78 | } 79 | } 80 | ``` 81 | 82 | 83 | 여기서 `addAll`과 `all`가 재정의되어서 하위클래스가 상위클래스의 메서드를 알고 있고, 캡슐화의 이점인 은닉화가 없어진다. 84 | 85 |
86 | 87 | #### 캡슐화가 깨지면 뭐가 안좋아? 88 | 89 | 90 | ```java 91 | InstrumentedHashSet languages = new InstrumentedHashSet<>(); 92 | languages.addAll(Arrays.asList("Java", "Ruby", "Scala")); 93 | languages.getAddCount(); // 몇개일까요? 94 | ``` 95 | 96 | **이상적으로는 3개여야 하지만, 6개가 된다!** 97 | 98 | why? `addAll()` 메서드 내부에서 `add()` 함수가 호출되기 때문 99 | 100 |
101 | 102 | #### 그럼 해결 방법이 없나? 103 | 104 | ##### 방법 1. addAll 다른 식으로 재정의 105 | ```java 106 | @Override 107 | public boolean addAll(Collection c) { 108 | for(value : c) { 109 | add(c); 110 | } 111 | } 112 | ``` 113 | 114 | addAll이 add를 사용하는지와 상관없이 결과가 좋다는 점에서 괜찮긴 함 115 | 116 | **BUT** 117 | 상위 클래스의 메서드 동작을 다시 구현하는 방식이 어렵고, 시간이 더 들고, 오류를 내거나 성능을 떨어트릴 수 있다. 118 | 119 | 하위 클래스에서는 접근할 수 없는 private 필드를 써야하는 상황이라면 구현 자체가 불가능 120 | 121 | 상위 클래스에 새로운 메서드가 추가된다면? 122 | 하위 클래스에서 재정의하지 못한 그 새로운 메서드를 사용해 '허용되지 않은' 원소를 추가할 수 있게 된다. 123 | 124 | > 실제로 컬렉션 프레임워크 이전부터 존재하던 HashTable과 Vector를 컬렉션 프레임워크에 포함시키 이와 관련한 보안 구멍들을 수정해야하는 사태가 벌어졌다. 125 | 126 |
127 | 128 | ##### 방법 2. 컴포지션을 사용 129 | 130 | > 컴포지션 : 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻 131 | 132 | 133 | ```java 134 | public class InstrumentedHashSet { 135 | private HashSet set = new HashSet<>(); 136 | 137 | // 추가된 원소의 수 138 | private int addCount = 0; 139 | 140 | public InstrumentedHashSet() { 141 | } 142 | 143 | @Override 144 | public boolean add(E e) { 145 | addCount++; 146 | return set.add(e); 147 | } 148 | 149 | @Override 150 | public boolean addAll(Collection c) { 151 | addCount += c.size(); 152 | return set.addAll(c); 153 | } 154 | 155 | public int getAddCount() { 156 | return addCount; 157 | } 158 | } 159 | ``` 160 | 161 |
162 | 163 | ### 상속은 언제 써? 164 | 165 | 반드시 하위 클래스가 상위 클래스의 '진짜' 하위 타입인 상황에서만 쓰여야 한다. 166 | 167 | > 클래스 B가 클래스 A와 is-a 관계일 때 -> 클래스 A를 상속 168 | 169 | 170 | **고민 1.** `B extend A` 하기 전에 고민해봐요 171 | 172 | `B가 정말 A인가?` 173 | 174 | -> "그렇다" : B는 A를 상속 175 | -> "아니다" : 컴포지션을 사용 176 | 177 | 178 | **고민 2.** 확장하려는 클래스의 API에는 아무런 결함이 없는가? 179 | 결합이 있다면 이 결함이 여러분 클래스의 API까지 전파돼도 괜찮은가? 180 | 181 | -> **컴포지션으로는 숨길 수 있지만, 상속은 결함까지도 승계한다.** 182 | 183 | -------------------------------------------------------------------------------- /04장/아이템_19/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_19/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_20/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_20/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_20/추상_클래스보다는_인터페이스를_우선하라_켬미.md: -------------------------------------------------------------------------------- 1 | # Item20. 추상 클래스보다는 인터페이스를 우선하라 2 | 3 |
4 | 5 | ### 공통점 6 | 7 | - 자바가 제공하는 다중 구현 메커니즘이다 8 | - (자바 8부터) 인터페이스도 디폴트 메서드를 제공할 수 있게 되어 이제는 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다 9 | 10 | 11 | ### 차이점 12 | 13 | - (추상 클래스) 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야한다. 14 | 15 | > 자바는 단일 상속만 지원 16 | > - 추상 클래스 방식은 새로운 타입을 정의하는데 커다란 제약을 안게 되는 셈 17 | 18 |
19 | 20 | ## 장점 21 | 22 | ### 장점 1. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 23 | 24 | - (인터페이스) 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 삭속했든 같은 타입으로 취급 25 | - 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 26 | 27 |
28 | 29 | ### 장점 2. 믹스인(mixin) 정의에 안성맞춤 30 | 31 | **인터페이스는 믹스인(mixin) 정의에 안성맞춤이다.** 32 | 33 | > 믹스인 : 클래스가 구현할 수 있는 타입으로, 믹스인을 구현한 클래스에 원래의 '주된 타입' 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다. 34 | > 대상 타입의 주된 기능에 선택적 기능을 혼합(mixed in)한다고해서 믹스인 35 | 36 | 37 | 클래스는 두 부모를 섬길 수 없고, 클래스 계층구조에는 믹스인을 삽입하기에 합리적인 위치가 없기 때문 38 | 39 | **인터페이스로는 계층 구조가 없는 타입 프레임워크를 만들 수 있다.** 40 | 41 |
42 | 43 | ### 장점 3. 계층 구조가 복잡하지 않다. 44 | 45 | 현실에는 계층을 엄격하게 구분하기 어려운 개념도 있다. 46 | 47 | ```java 48 | public interface Singer { 49 | AudioClip sing(Song s); 50 | } 51 | 52 | public interface Songwriter { 53 | Song compose(int chartPosition); 54 | } 55 | ``` 56 | 57 | 만약 작곡도하고 노래도 한다면 두 인터페이스 모두를 구현해도된다. 심지어 모두를 extend하고 새 제 3의 메서드도 추가 가능하다. 58 | 59 | ```java 60 | public interface SingerSongwriter extends Singer, Songwriter { 61 | AudioClp strum(); 62 | void actSensitive(); 63 | } 64 | ``` 65 | 66 | 만약 이걸 클래스로 만들려면 가능한 조합의 수를 모두 각각 클래스로 정의해야 한다. 속성이 n개라면 조합의 수는 2^n 개이다. 67 | 68 |
69 | 70 | ### 장점 4. 기능을 향상 시키는 안전하고 강력한 수단 71 | 72 | 73 | 타입을 추상 클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속 뿐 ! 74 | 상속해서 만든 클래스는 래퍼 클래스보다 활용도가 떨어지고 깨지기는 더 쉽다. 75 | 76 | 구현이 명백한 것이 있다면 해당 구현은 디폭트 메서드로 제공해 프로그래머들의 일감을 덜을 수 있다. 77 | 78 |
79 | 80 | ##### 디폴트 메서드는 제약 81 | 82 | - equals와 hashcode를 디폴트 메소드로 제공 안함 83 | - 인터페이스는 인스턴스ㄹ 필드를 가질 수 없고, private 정적 메소드 불가능 84 | - 본인이 만든 인터페이스가 아니면 디폴트 메소드 추가 불가능 85 | 86 | 87 | -------------------------------------------------------------------------------- /04장/아이템_21/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_21/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_21/인터페이스는_구현하는_쪽을_생각해_설계하라_폰드.md: -------------------------------------------------------------------------------- 1 | ## 인터페이스는 구현하는 쪽을 생각해 설계하라 2 | 자파 8 이전, 기존 구현체를 깨뜨리지 않고 인터페이스에 메서드를 추가할 방법이 없었다. 3 | 기존 인터페이스에 메서드를 추가할 수 있도록 **디폴트 메서드** 도입. 4 | **그러나 모든 기존 구현체들과 매끄럽게 연동되리라는 보장이 없다.** 5 | 6 | 자바 8에서는 **핵심 컬렉션 인터페이스**들에 다수의 디폴트 메서드가 추가 7 | ```java 8 | default boolean removeIf(Predicate filter) { //Collection 인터페이스에 추가된 디폴트 메서드 9 | Objects.requireNonNull(filter); 10 | boolean removed = false; 11 | final Iterator each = iterator(); 12 | while (each.hasNext()) { 13 | if (filter.test(each.next())) { 14 | each.remove(); 15 | removed = true; 16 | } 17 | } 18 | return removed; 19 | } 20 | ``` 21 | 위 코드는 범용적으로 구현되었지만, 현존하는 모든 Collection 구현체와 잘 어울러지는 것은 아니다. 22 | 대표적인 예가 아파치의 SynchronizedCollection 클래스이다. 23 | - Collection 인터페이스 구현 24 | - 클라이언트가 제공한 객체로 락을 거는 능력 제공 25 | - 모든 메서드에서 주어진 락 객체로 동기화한 후, 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스 26 | 27 | ```java 28 | public class SynchronizedCollection implements Collection, Serializable { 29 | private static final long serialVersionUID = 2412805092710877986L; 30 | private final Collection collection; 31 | protected final Object lock; 32 | ... 33 | 34 | protected SynchronizedCollection(Collection collection) { 35 | this.collection = (Collection) Objects.requireNonNull(collection, "collection"); 36 | this.lock = this; 37 | } 38 | ... 39 | 40 | public boolean add(E object) { 41 | synchronized (this.lock) { 42 | return this.decorated().add(object); 43 | } 44 | } 45 | ... 46 | } 47 | ``` 48 | 해당 클래스는 책이 쓰인 시점엔, removeIf 메서드를 **재정의** 하지 않았다. 49 | removeIf 구현은 동기화에 관해 아무것도 고려되지 않았으므로, 해당 메서드의 구현을 물려받으면 **모든 메서드 호출을 알아서 동기화**한다는 약속이 깨짐. 50 | → 멀티 스레드 환경에서 해당 메서드 호출하면 예외를 발생하거나, 예기치 못한 결과가 생길 수 있다. 51 | ### 해결책 52 | 인터페이스의 디폴트 메서드를 **재정의**하고, 다른 메서드에서 디폴트 메서드를 호출하기 전에 필요한 작업(동기화 등..)을 수행하도록 변경 53 | ```java 54 | public boolean removeIf(Predicate filter) { //동기화 하도록 재정의 55 | synchronized(this.lock) { 56 | return this.decorated().removeIf(filter); 57 | } 58 | } 59 | 60 | ``` 61 | ### 유의사항 62 | - 기존 인터페이스에 디폴트 메서드를 추가하는 것은 꼭 필요한 경우가 아니면 피해라. 63 | - 기존 구현체들과 충돌할 가능성을 고려해야 함. 64 | - 반면, 새로운 인터페이스를 만들 때, 표준적인 메서드 구현을 제공하는 데 디폴트 메서드는 유용한 수단. 65 | -------------------------------------------------------------------------------- /04장/아이템_22/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_22/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_22/인터페이스는_타입을_정의하는_용도로만_사용하라_배키.md: -------------------------------------------------------------------------------- 1 | # [item 22] 인터페이스는 타입을 정의하는 용도로만 사용하라 2 | 3 | ### 인터페이스의 사용 용도 4 | 인터페이스는 아래 용도로만 사용해야 한다! 5 | > 타입을 정의하는 것. 즉, 즉 인터페이스를 구현한 클래스는 자신의 인스턴스로 무엇을 할 수 있는지 인터페이스를 통해 클라이언트에게 알릴 수 있다. 6 | 7 | ### 인터페이스 사용 용도와 맞지 않는 예 8 | #### 상수 인터페이스 9 | 상수 인터페이스란 메서드 없이 static final 필드로만 가득 찬 인터페이스이다. 10 | ```java 11 | public interface PhysicalConstants { 12 | // 아보가드로 수 13 | static final double AVOGADROS_NUMBER = 6.022_140_857e23; 14 | 15 | // 볼츠만 상수 16 | static final double BOLTZMANN_CONSTANT = 1.380_648_52e23; 17 | 18 | // 전자 질량 19 | static final double ELECTRON_MASS = 9.109_383_56e-31; 20 | } 21 | ``` 22 | 상수 인터페이스는 아래와 같은 문제점이 있다. 23 | * 클래스 내부에서 사용하는 상수는 인터페이스가 아니라 내부 구현에 해당한다. 24 | * 상수 인터페이스를 구현한 하위의 모든 클래스가 상수 인터페이스의 모든 값에 접근할 수 있다. 이는 내부 구현을 외부에 노출하는 것이다. 25 | * final이 아닌 클래스가 상수 인터페이스를 구현한다면 모든 하위 클래스의 이름 공간이 그 인터페이스가 정의한 상수들로 오염되어 버린다. 26 | * 아래 코드를 보자. 27 | ```java 28 | public interface Constants { 29 | int VALUE = 10; 30 | } 31 | 32 | public class MyBaseClass implements Constants { 33 | // 클래스 내부 구현 34 | } 35 | 36 | public class MySubClass extends MyBaseClass { 37 | // 클래스 내부 구현 38 | } 39 | ``` 40 | * MyBaseClass가 final이 아니라면 MySubClass가 VALUE를 사용할 수 있게 된다. MySubClass는 VALUE를 사용하지 않아도 되는 상황인데, 이름 공간에 VALUE라는 상수 값이 존재하게 된다. 41 | 42 | 43 | ### 만약 상수를 공개할 목적이라면 선택할 수 있는 방법들 44 | * 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가하자. (위 코드 처럼 상수 인터페이스에 모아두지 말자!) 45 | * ex. Integer 클래스의 MIN_VALUE, MAX_VALUE; 46 | * 열거 타입으로 나타내기 적합한 상수라면 열거 타입으로 만들자. 47 | * 인스턴스화 할 수 없는 유틸리티 클래스에 담아 공개하자.(상수 유틸리티 클래스) 48 | * 아래 코드는 상수 인터페이스를 상수 유틸리티 클래스로 만든 예이다. 49 | ```java 50 | public class PhysicalConstants { 51 | 52 | private PhysicalConstants() {} // 객체를 생성시키 않도록 함 53 | 54 | // 아보가드로 수 55 | static final double AVOGADROS_NUMBER = 6.022_140_857e23; 56 | 57 | // 볼츠만 상수 58 | static final double BOLTZMANN_CONSTANT = 1.380_648_52e23; 59 | 60 | // 전자 질량 61 | static final double ELECTRON_MASS = 9.109_383_56e-31; 62 | } 63 | ``` 64 | ### 핵심 정리 65 | > 인터페이스는 타입을 정의하는 용도로만 사용하자. 상수 공개용 수단으로 사용하지 말자! 66 | -------------------------------------------------------------------------------- /04장/아이템_22/인터페이스는_타입을_정의하는_용도로만_사용하라_폰드.md: -------------------------------------------------------------------------------- 1 | ## 인터페이스는 타입을 정의하는 용도로만 사용하라 2 |
3 | 4 | > ### 인터페이스 5 | >인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할 6 | >클래스가 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지 클라이언트에 얘기해주는 것 7 | >인터페이스는 **타입을 정의하는 용도**로 사용해야 한다. 8 | 9 | **상수 인터페이스** 10 | '상수 인터페이스'는 인터페이스를 잘못 사용한 예 11 | ```java 12 | public interface PhysicalConstants { 13 | 14 | static final double AVOGADROS_NUMBER = 6.022_140_857e23; 15 | static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23; 16 | static final double ELECTRON_MASS = 9.109_383_56e-31; 17 | } 18 | ``` 19 | - 위 지침에 맞지 않음. 20 | - 클래스 내부에서 사용하는 상수를 인터페이스에 구현하는 것은 내부 구현을 클래스의 API로 노출하는 행위이다. 21 | - 인터페이스의 상수는 public static final 22 | - 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스가 인터페이스의 상수들로 오염된다. 23 | 24 | ### 합당한 선택지 25 | - 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해라. 26 | ```java 27 | public final class Integer extends Number 28 | implements Comparable, Constable, ConstantDesc { 29 | 30 | @Native public static final int MIN_VALUE = 0x80000000; 31 | @Native public static final int MAX_VALUE = 0x7fffffff; 32 | ... 33 | } 34 | ``` 35 | - 열거타입으로 만들어라. 36 | - 인스턴스화할 수 없는 유틸리티 클래스에 담아라. 37 | ```java 38 | public class PhysicalConstants { 39 | private PhysicalConstants() { } // 인스턴스화 방지 40 | 41 | public static final double AVOGADROS_NUMBER = 6.022_140_857e23; 42 | public static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23; 43 | public static final double ELECTRON_MASS = 9.109_383_56e-31; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /04장/아이템_23/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_23/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_23/태그_달린_클래스보다는_클래스_계층구조를_활용하라_프람.md: -------------------------------------------------------------------------------- 1 | # (아이템 23) 태그 달린 클래스보다는 클래스 계층구조를 활용하라 2 | >작성자: 프람 3 | > 4 | > 작성일시: 2024\_05\_03 5 | 6 | ## 인사 7 | 안녕하세요~. 프람입니다⛴️ 이번 주 이것 저것 한다고 많이들 바쁘셨죠? 그래도 어김없이 돌아오는 불금입니다. 8 | 9 | 일주일의 마무리를 이펙티브 자바를 함께 공부해보며 마무리해봅시다.💪💪💪 10 | 그럼 본격적으로 `태그 달린 클래스보다는 클래스 계층구조를 활용하라` 에 대해 알아봅시다. 11 | 12 | --- 13 | 14 | ## 태그란 무엇일까요?? 15 | 16 | 글을 읽다가 태그란 무엇을 일까?? 라고 생각을 해봤는데요. 17 | 18 | 아마 아래 어학사전에서 설명하는 사전적 의미가 아닐까 생각이 듭니다.
19 | 혹시, 정확한 뜻을 아신다면, 말씀해주세요~
20 | `네이버 어학사전피셜`: **(어떤 표시를 하기 위해 붙인) 꼬리표** 21 | 22 | 그렇다면, 같이 예제를 보고 태그 달린 클래스를 확인해봅시다. 23 | 24 | ``` java 25 | class Figure { 26 | enum Shape { RECTANGLE, CIRCLE } 27 | 28 | // 태그 필드 29 | final Shape shape; 30 | 31 | // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다. 32 | double length; 33 | double width; 34 | 35 | // 다음 필드는 모양 원(CIRCLE)일 때만 쓰인다. 36 | double radius; 37 | 38 | // 원용 생성자 39 | Figure (double radius) { 40 | shape = Shape.CIRCLE; 41 | this.radius = radius; 42 | } 43 | 44 | // 사각형용 생성자 45 | Figure (double length, double width) { 46 | shape = Shape.RECTANGLE; 47 | this.length = length; 48 | this.width = width; 49 | } 50 | 51 | double area() { 52 | switch (shape) { 53 | case RECTANGLE: 54 | return length * width; 55 | case CIRCLE: 56 | return Math.PI * (radius * radius); 57 | default: 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | 우리 친구들~~ 이 코드만 봐도 토가 쏠리지 않나요? 🤮 64 | 어떤 문제점들이 있는지 하나씩 짚어보아요🤗 65 | 66 | ## 태그의 문제점 67 | 68 | 특히, 위 코드는 객체지향의 꽃인 `OCP`를 위반하고 있어 매우 매우 불편한데요. 69 | 70 | FIGURE에 Shape이 추가될 때마다 불필요한 인스턴스 변수 추가, 생성자 추가 그리고 area메서드의 switch문에 넓이 구현 로직이 추가 되어야한다는 것이죠. 71 | 72 | 이렇게 된다면 Shape이 추가되면 될수록 코드가 장황해지겠죠?
73 | 그렇게 된다면, 오류를 찾기도 힘들거에요. 74 | 75 | 그래서 우리는 자바에서 지원하는 서브 타이핑(확장, 구현)을 대안으로 사용해봅시다. 76 | 77 | 78 | ## 대안 79 | 80 | 대안으로 계층 구조를 활용하라고 본 아이템에서 설명 하고 있습니다.
81 | 한번 같이 코드로 살펴봅시다. 82 | 83 | ```java 84 | abstract class Figure { 85 | abstract double area(); 86 | } 87 | 88 | class Circle extends Figure { 89 | final double radius; 90 | 91 | Circle(double radius) { this.radius = radius; } 92 | 93 | @Override 94 | double area() { return Math.PI * (radius * radius) } 95 | } 96 | 97 | class Rectangle extends Figure { 98 | final double length; 99 | final double width; 100 | 101 | Rectangle(double length, double width) { 102 | this.lenght = length; 103 | this.width = width; 104 | } 105 | 106 | @Override 107 | double area() { return length * width } 108 | } 109 | ``` 110 | 위 코드와 같이 상속을 사용해서 계층 구조를 만들어 확장하는 방식을 책에서는 소개하고 있다.
111 | 112 | 이렇게 된다면, 태그를 사용한 클래스의 문제점인 확장이 닫힌 문제점을 해결하고 각 클래스에서 쓸데 없는 코드도 없어진 모습을 볼 수 있다. 113 | 114 | 115 | --- 116 | 117 | ### 결론 118 | 119 | 클래스의 적절한 분리(확장, 구현)를 통해 유지보수성을 증가 시켜주자. 120 | 121 | -------------------------------------------------------------------------------- /04장/아이템_23/태그_달린_클래스보다는_클래스_계층구조를_활용하라_호티.md: -------------------------------------------------------------------------------- 1 | # 아이템 23 : 태그 달린 클래스보다는 클래스 계층구조를 활용하라 2 | 3 | > **태그 달린 클래스** 4 | > 5 | 6 | ```java 7 | public class Figure { 8 | 9 | enum Shape {RECTANGLE, CIRCLE} 10 | 11 | // 태그 필드 -> 현재 모양을 나타낸다 12 | final Shape shape; 13 | 14 | String color; 15 | 16 | double width; 17 | double length; 18 | double radius; 19 | 20 | // 원 21 | Figure(double radius, String color) { 22 | shape = Shape.CIRCLE; 23 | this.radius = radius; 24 | this.color = color; 25 | } 26 | 27 | // 직사각형 28 | Figure(double width, double length, String color) { 29 | shape = Shape.RECTANGLE; 30 | this.width = width; 31 | this.length = length; 32 | this.color = color; 33 | } 34 | 35 | double area() { 36 | switch (shape) { 37 | case RECTANGLE: 38 | return width * length; 39 | case CIRCLE: 40 | return Math.PI * (radius * radius); 41 | default: 42 | throw new AssertionError(shape); 43 | } 44 | } 45 | 46 | String getColor() { 47 | return color; 48 | } 49 | } 50 | ``` 51 | 52 | - 열거 타입 선언, 태그 필드, `switch`문 등 쓸데없는 코드가 많다. 53 | - 여러 구현이 한 클래스에 혼합되어 있어 가독성도 나쁘다. 54 | - 다른 의미를 위한 코드도 언제나 함께하니 메모리도 많이 사용한다. 55 | - 필드들을 final로 선언하려면 해당 의미에 쓰이지 않는 필드들까지 생성자에서 초기화 해야 한다. 56 | - 또 다른 의미를 추가하려면 쓸데없는 코드가 늘어난다. 57 | 58 | 66 | 67 | ### 태그 달린 클래스를 클래스 계층구조로 바꾸는 방법 68 | 69 | --- 70 | 71 | ### 1. 계층구조의 루트가 될 추상클래스를 선언 72 | 73 | - `class Figure` 74 | 75 | ### 2. 태그 값에 따라 동작이 달라지는 메서드를 추상 메서드로 선언 76 | 77 | - `double area()` 78 | 79 | ### 3. 태그 값과 상관없이 동작이 일정한 메서드는 일반 메서드로 선언 80 | 81 | - `String getColor(){}` 82 | 83 | ### 4. 공통으로 사용하는 데이터 필드는 모두 추상 클래스(루트)에 선언 84 | 85 | - `String color` 86 | 87 | ### 5. 추상 클래스(루트)를 확장한 구체클래스를 의미별로 하나씩 정의 88 | 89 | - `Shape.Rectangle` 90 | - `Shape.Circle` 91 | 92 | --- 93 | 94 | ```java 95 | public abstract class Figure { 96 | protected final String color; 97 | 98 | public Figure(String color) { 99 | this.color = color; 100 | } 101 | 102 | public final String getColor() { 103 | return color; 104 | } 105 | 106 | abstract double area(); 107 | } 108 | 109 | public final class Circle extends Figure { 110 | 111 | private final double radius; 112 | 113 | Circle(String color, double radius) { 114 | super(color); 115 | this.radius = radius; 116 | } 117 | 118 | @Override 119 | double area() { 120 | return Math.PI * (radius * radius); 121 | } 122 | } 123 | 124 | public class Rectangle extends Figure { 125 | 126 | private final double width; 127 | private final double length; 128 | 129 | Rectangle(String color, double width, double length) { 130 | super(color); 131 | this.width = width; 132 | this.length = length; 133 | } 134 | 135 | @Override 136 | double area() { 137 | return width * length; 138 | } 139 | } 140 | ``` 141 | 142 | ### 장점 143 | 144 | - 각 의미를 독립된 클래스에 담아 관련 없던 데이터 필드를 모두 제거했다. 145 | - 살아남은 필드들은 모두 `final`로 선언함으로써 146 | 각 클래스의 생성자가 모든 필드를 남김없이 초기화하고, 추상메서드를 모두 구현했는지 컴파일러가 확인해준다. 147 | - 실수로 빼먹은 `case`문 때문에 런타임 오류가 발생할 일도 없다. 148 | - 루트 클래스의 코드를 건드리지 않고 독립적으로 계층구조를 확장하고, 함께 사용할 수 있다. 149 | - 타입 사이의 자연스러운 계층관계를 반영할 수 있어서 유연성은 물론 컴파일타임 타입 검사 능력을 높여준다 150 | 151 | --- 152 | 153 | ## 결론 154 | 155 | - 태그 달린 클래스를 써야 하는 상황은 거의 없다. 156 | - 새로운 클래스를 작성하는데 태그 필드가 등장한다면 태그를 없애고 157 | 계층 구조로 대체하는 방법을 생각해보자 158 | - 태그 달린 클래스 대신 **계층 구조를** 사용하자. -------------------------------------------------------------------------------- /04장/아이템_24/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_24/InnerClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/InnerClass.png -------------------------------------------------------------------------------- /04장/아이템_24/test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/test1.png -------------------------------------------------------------------------------- /04장/아이템_24/test2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/test2.png -------------------------------------------------------------------------------- /04장/아이템_24/test3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/test3.png -------------------------------------------------------------------------------- /04장/아이템_24/test4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/test4.png -------------------------------------------------------------------------------- /04장/아이템_24/test5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_24/test5.png -------------------------------------------------------------------------------- /04장/아이템_25/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/04장/아이템_25/.gitkeep -------------------------------------------------------------------------------- /04장/아이템_25/톱_레벨_클래스는_한_파일에_하나만_담으라_초롱.md: -------------------------------------------------------------------------------- 1 | # 톱레벨 클래스 2 | - 소스파일에서 가장 바깥에 존재하는 클래스 3 | - ex) Sample.java 의 경우 Sample 클래스가 톱 레벨 클래스다. 4 | 5 | # 톱레벨 클래스는 한 파일에 하나만 담으라 6 | - 다음 소스 파일은 Main 클래스 하나를 담고 있다. 7 | - Main 클래스는 다른 톱레벨 클래스 2개(Utensil과 Dessert)를 참조한다. 8 | ```java 9 | // Main.java 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(Utensil.NAME + Dessert.NAME); 13 | } 14 | } 15 | 16 | // Utensil.java 17 | class Utensil { 18 | static final String NAME = "pan"; 19 | } 20 | 21 | class Dessert { 22 | static final String NAME = "cake"; 23 | } 24 | 25 | // Dessert.java 26 | class Utensil { 27 | static final String NAME = "pot"; 28 | } 29 | 30 | class Dessert { 31 | static final String NAME = "pie"; 32 | } 33 | ``` 34 | - 이렇게 작성하는 경우 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라진다. 35 | 1. `javac Main.java` or `javac Main.java Utensil.java` 36 | - `pancake`을 출력한다. 37 | 2. `javac Dessert.java Main.java` 38 | - `potpie`를 출력한다. 39 | 3. `javac Main.java Dessert.java` 40 | - `Utensil`과 `Dessert` 클래스를 중복 정의했다고 알려주면서 컴파일 오류가 난다. 41 | - 컴파일러는 가장 먼저 `Main.java`를 컴파일한다. 42 | - 그 안에서 `Dessert` 참조보다 먼저 나오는 `Utensil` 참조를 만나면 `Utensil.java` 파일을 살펴 `Utensil`과 `Dessert`를 모두 찾아낸다. 43 | - 2번째 인수로 넘어온 `Dessert.java`를 컴파일 하려고 할 때 같은 클래스가 이미 정의되어 있는 것을 알게 된다. 44 | 45 | # 해결 방법 46 | - 톱레벨 클래스들 (`Untensil`과 `Dessert`)를 서로 다른 소스 파일로 분리한다. 47 | ```java 48 | // Utensil.java 49 | class Utensil { 50 | static final String NAME = "pan"; 51 | } 52 | 53 | // Dessert.java 54 | class Dessert { 55 | static final String NAME = "cake"; 56 | } 57 | ``` 58 | 59 | # 굳이굳이 한 파일에 담고 싶을 때 60 | - 아이템 24를 참고하여 정적 멤버 클래스를 사용하는 방법을 고려한다. 61 | ```java 62 | public class Main { 63 | public static void main(String[] args) { 64 | System.out.println(Utensil.NAME + Dessert.NAME); 65 | } 66 | 67 | private static class Utensil{ 68 | static final String NAME = "pan"; 69 | } 70 | 71 | private static class Dessert{ 72 | static final String NAME = "caks"; 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /04장/아이템_25/톱_레벨_클래스는_한_파일에_하나만_담으라_호티.md: -------------------------------------------------------------------------------- 1 | # 아이템 25 : 톱 레벨 클래스는 한 파일에 하나만 담으라 2 | 3 | 11 | 12 | ### 소스 파일 하나에 톱 레벨 클래스를 여러개 선언하더라도 자바 컴파일러는 에러를 일으키지 않는다. 13 | 14 | **→ 하지만 아무런 득이 없는 구성이다** 15 | 16 | ### 파일 하나에 모두 담으면, 한 클래스를 여러가지로 정의하게 된다 17 | 18 | **→ 즉, 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일 하냐에 따라 달라진다.** 19 | 20 | ```java 21 | public class Main{ 22 | public static void main(String[] args) { 23 | System.out.println(Utensil.NAME + Dessert.NAME); 24 | } 25 | } 26 | ``` 27 | 28 | ```java 29 | // Utensil.java 30 | class Utensil{ 31 | static final String NAME = "pan"; 32 | } 33 | 34 | class Dessert{ 35 | static final String NAME = "cake"; 36 | } 37 | 38 | // Dessert.java 39 | class Utensil{ 40 | static final String NAME = "pot"; 41 | } 42 | 43 | class Dessert{ 44 | static final String NAME = "pie"; 45 | } 46 | ``` 47 | 48 | - `javac Main.java Dessert.java` → 컴파일 오류(클래스를 중복 정의 했다는 오류) 49 | - `Main` → `Utensil`(이 때 `Utensil`과 `Dessert` 모두 찾음) → `Dessert` (중복 발견) 50 | - `javac Main.java` → pancake 51 | - `javac Main.java Utensil.java` → pancake 52 | - `javac Dessert.java Main.java` → potpie 53 | 54 | ### 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라진다는 것은 큰 문제 55 | 56 | ### 소스파일 하나에는 반드시 톱레벨 클래스를 하나만 담자 -------------------------------------------------------------------------------- /05장/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_26/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_26/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_26/로_타입은_사용하지_말라_폰드.md: -------------------------------------------------------------------------------- 1 | ## 로 타입은 사용하지 말라 2 | ### 로 타입(raw type) 3 | - 제네릭 타입((제네릭 클래스/제네릭 인터페이스)에서 타입 매개변수를 전혀 사용하지 않을 때를 의미```List -> List``` 4 | - 제네릭 타입을 하나 정의하면 로 타입도 함께 정의된다. 5 | - 로 타입은 오류를 컴파일 할 때 발견하지 못하고, 런타임에야 알아채는 문제가 발생할 수 있다.
(런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 떨어져있을 가능성이 커짐.) 6 | ```java 7 | public static void main(String[] args) { //제네릭을 지원하기 이전 예시 8 | List rawAnimals = new ArrayList(); //Animal을 담기 위한 로 타입 리스트🤮 9 | rawAnimals.add(new Table()); 10 | rawAnimals.add(new Animal()); 11 | 12 | Iterator iterator = rawAnimals.iterator(); 13 | while (iterator.hasNext()) { 14 | Animal animal = (Animal) iterator.next();//런타임에 ClassCastException 발생 15 | } 16 | } 17 | ``` 18 | ```java 19 | public static void main(String[] args) { 20 | List animals = new ArrayList<>();//제네릭 사용으로 타입 안정성 확보🥳 21 | animals.add(new Animal()); 22 | animals.add(new Table());//컴파일 에러 발생 23 | } 24 | ``` 25 | 26 | - 로 타입은 절대 써서는 안되지만, 제네릭이 도입되기 전 기존 코드와 호환성을 위해 지원 27 | 28 | 29 | **임의 객체를 허용하는 매개변수화 타입과 로 타입** ex)```List, List``` 30 | 둘은 비슷해보이지만, 타입 안정성때문에 매개변수화 타입을 사용해야 한다. 31 | ```java 32 | public static void main(String[] args) { 33 | List strings = new ArrayList<>(); 34 | unsafeAdd(strings, Integer.valueOf(42)); 35 | String s = strings.get(0); 36 | } 37 | ``` 38 | ```java 39 | private static void unsafeAdd(List list, Object o) { 40 | list.add(o); 41 | } 42 | ``` 43 | ```List```은 List의 하위 타입이므로 들어 갈 수 있다. 44 | strings.get(0)에서 형변환하려고 할 떄 ClassCastException 발생 45 |

46 | ```java 47 | private static void unsafeAdd(List list, Object o) { //인자로 strings 전달 불가 48 | list.add(o); 49 | } 50 | ``` 51 | ```List```은 ```List```의 하위 타입이 아니므로 컴파일 에러 발생 52 |

53 | 54 | **비한정적 와일드카드 타입과 로 타입** ex)```Set, Set``` 55 | 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 땐 비한정적 와일드카드 타입을 사용하자. 56 | ```java 57 | private static int numElementsInCommon(Set s1, Set s2) { 58 | int result = 0; 59 | s1.add(null); 60 | for (Object o1 : s1) 61 | if (s2.contains(o1)) 62 | result++; 63 | return result; 64 | } 65 | ``` 66 | ```java 67 | private static int numElementsInCommon2(Set s1, Set s2) { 68 | int result = 0; 69 | for (Object o1 : s1) 70 | if (s2.contains(o1)) 71 | result++; 72 | return result; 73 | } 74 | 75 | ``` 76 | 두 메서드 모두 정상적으로 동작하지, 로 타입은 안전하지 않다. 77 | - 비한정적 와일드카드에는 null 제외 어떠한 인스턴스도 들어갈 수 없다. 78 | - 어떠한 타입인지는 모르지만 한가지 타입만 들어올 수 있다. 79 | 80 | 81 | -------------------------------------------------------------------------------- /05장/아이템_27/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_27/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_27/unchecked_exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_27/unchecked_exception.png -------------------------------------------------------------------------------- /05장/아이템_27/비검사_경고를_제거하라_배키.md: -------------------------------------------------------------------------------- 1 | # item 27 비검사 경고를 제거하라 2 | 3 | > 그 전에, 비검사 경고란? 4 | > `warning : [unchecked]` 를 말하며, casting 할 때 검사를 하지 않았다고 말하는 것이다. 5 | 6 | 제네릭을 사용하면 수많은 비검사 경고가 뜬다. 7 | ![unchecked](unchecked_exception.png) 8 | 비검사 경고의 예는 아래와 같다. 9 | * 비검사 형변환 경고 10 | * 비검사 메서드 호출 경고 11 | * 비검사 매개변수화 가변인수 타입 경고 12 | * 비검사 변환 경고 13 | 14 | ### 비검사 경고를 가능한 제거해야 하는 이유 15 | 비검사 경고를 제거하는 건 타입 안전성을 보장한다는 의미이기 때문이다. 16 | 즉, ClassCastException이 발생할 일이 없는 것을 보장한다. 17 | 18 | ### @SuppressWarnings("unchecked")를 사용하자. 19 | 비검사 경고를 최대한 제거해도 남아있는 경고가 있다. 20 | 경고는 제거할 수 없지만 타입이 안전하다는 확신이 들면 @SuppressWarnings를 사용하여 경고를 숨기자. 21 | 22 | #### @SuppressWarnings 사용 규칙 23 | * 타입 안전하지 않은 대상에 해당 어노테이션을 사용하지 않는다. 24 | * 검증이 안된 대상에 사용하는 것은 경고를 무시하는 것과 같다. 25 | * 안전하다고 검증된 비검사 경고에는 해당 어노테이션을 꼭 사용한다. 26 | * 진짜 문제를 알리는 새로운 경고가 나와도 인지하기 어려워진다. 27 | * 클래스, 필드, 메서드까지 모두 달 수 있다. 28 | * 그러나 항상 좁은 범위에 적용하자. 29 | * 해당 어노테이션을 사용하면 그 경고를 무시해도 안전한 이유를 주석으로 남기자. 30 | 31 | 32 | #### @SuppressWarnings 사용 예시 33 | 해당 어노테이션은 좁은 범위에서 사용해야 한다. 다음 예시를 보자. 34 | 아래는 ArrayList의 toArray 메서드다. 35 | ```java 36 | public T[] toArray(T[] a) } { 37 | if(a.length < size) 38 | return (T[]) Arrays.copyOf(elements, size, a.getClass()); 39 | if(a.length > size) 40 | a[size] = null; 41 | return a; 42 | } 43 | ``` 44 | 이 코드에서는 return 문에서 비검사 예외가 발생한다. 45 | 하지만 타입 안전함을 보장하고 때문에 @SuppressWarnings를 어떻게 사용해야 할까? 46 | 47 | 아래 처럼 메서드 상단에 해당 어노테이션을 두면 범위가 너무 넓어진다. 48 | ```java 49 | @SuppressWarnings("unchecked") 50 | public T[] toArray(T[] a) } { 51 | if(a.length < size) 52 | return (T[]) Arrays.copyOf(elements, size, a.getClass()); 53 | if(a.length > size) 54 | a[size] = null; 55 | return a; 56 | } 57 | ``` 58 | 59 | 범위를 더 좁히기 위해 return문 사용하고 싶지만 어노테이션은 선언문만 가능하다. 60 | 때문에 아래처럼 지역 변수를 선언하고 그 변수에 어노테이션을 달자. 61 | ```java 62 | public T[] toArray(T[] a) } { 63 | if(a.length < size) 64 | @SuppressWarnings("unchecked") 65 | T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); 66 | return result; 67 | if(a.length > size) 68 | a[size] = null; 69 | return a; 70 | } 71 | ``` 72 | 위와 같은 코드로 깔끔하게 컴파일되고 비검사 경고도 숨길 수 있다. 73 | 74 | -------------------------------------------------------------------------------- /05장/아이템_27/비검사_경고를_제거하라_프람.md: -------------------------------------------------------------------------------- 1 | # 비검사 경고를 제거하라 2 | 3 | > 작성자: 프람 4 | > 5 | > 작성 일시: 2024.05.06 6 | > 7 | > 내용: Effective java 5장 아이템27 8 | 9 | 10 | 11 | ## 인사 12 | 이번 주제는 "비검사 경고를 제거하라"입니다. 13 | 비검사 경고가 뭔데? 14 | 이번 아이템도 여김없이 제목이 담고 있는 의미가 무엇인지를 먼저 파악해볼까 합니다. 15 | 16 | --- 17 | 18 | ## 비검사 경고가 뭔데? 19 | 20 | 우선(Unchecked Exception과 상관관계가 없습니다!!) 21 | 비검사 경고[[1]](https://www.baeldung.com/java-warning-unchecked-cast)가 등장하는 코드를 만들어 보겠습니다.👍 22 | image 23 | 24 | 25 | 마구 마구 노란줄이 뜨는게 보이시나요?? 26 | 27 | 실행을 해보면 경고에 대한 디테일 들이 출력되는데요. 28 | 29 | 경고가 있다고해서 프로그램이 작동하지 않는 것은 아닙니다. 30 | 31 | 그렇다면 실행 결과를 봐 볼까요? 32 | 33 | image 34 | 35 | 36 | 콘솔 로그와 같이 warning: [unchecked] unchecked conversion 이라는 경고 문구가 있네요?? 37 | 38 | Unchecked 경고는 이와 같이 제네릭 매개변수를 명시적으로 표현하지 않아주었을때 볼 수 있는 경고입니다. 39 | 40 | 컴파일러 내부에서 타입 캐스팅을 해줄때 런타임 에러를 뿜을 수 있다는 아주 강력한 경고를 하고 있는 겁니다!! 41 | 42 | --- 43 | 44 | ## 경고를 무시한 댓가 45 | 그렇다면, 이러한 오류를 간과하면 어떤 대가를 치루는지도 함께 봅시다. 46 | 47 | 기념일에 오늘을 추가해주었습니다.(다만 LocalDate 타입이 아닌, Date 타입으로 말이죠) 48 | 49 | 각 기념일을 하루씩 빼고 출력하도록 간단하게 로직을 추가해보았습니다. 50 | 51 | image 52 | 53 | 결과는 예상한대로 ClassCastExecption예외가 발생합니다. 54 | image 55 | 56 | 57 | 간단한, 코드라 바로 오류를 찾을 수 있겠지만, 프로젝트 크기가 커진다면 찾는것도 엄청나게 힘들겠죠?? 58 | 59 | 그러니까 명시적으로 제네릭 매개변수를 알려줍시다. 그러면 아래와 같이 컴파일 시점에서 오류를 잡아줍니다👍 60 | image 61 | 62 | --- 63 | 64 | ## 의도적으로 경고를 끄려면? 65 | 그럼에도 불구하고 unchecked 경고를 표시해야하는 경우가 있을 수 있습니다. 66 | 67 | 이런 경우에는 확실히 제어할 수 있는 경우이므로 경고를 꺼주라고 합니다. 68 | 69 | 어떻게하면 끌 수 있는지 확인해봅시다.😊 70 | image 71 | 72 | 73 | 74 | 75 | 76 | 77 | @SupressWarnings 라는 애노테이션을 자바에서 지원해주는데요. 78 | 79 | 경고가 발생하는 스코프내에 "unchecked"를 매개변수로 넣어주면 아래와 같이 80 | 81 | 경고 없이 실행할 수 있습니다!! 82 | 83 | image 84 | 85 | 86 | 87 | 88 | @SupressWarnings는 unchecked 경고만을 잡아주는 것이 아니고 어려가지 경고를 잡아주는 데 89 | 90 | 자세한 내용은 SuppressWarning(IBM)[[2]](https://www.ibm.com/docs/ko/radfws/9.6.1?topic=code-excluding-warnings)을 참고해주세요😊 91 | 92 | 단, 당연한 이야기지만 책에서 또 강조하는 것은 최대한 쫍은 스코프에 적용해주라네요 😅 93 | 94 | --- 95 | 96 | ## 결론 97 | 컴파일이 뿌려주는 모든 오류는 추후에 댓가를 치루기 싫으면 모두 잡아줘라!!! 98 | 99 | 100 | ## 참고 자료 101 | [[1] "Unchecked Cast"](https://www.baeldung.com/java-warning-unchecked-cast) 102 | [[2] "Code Excluding Warnings(IBM)"](https://www.ibm.com/docs/ko/radfws/9.6.1?topic=code-excluding-warnings) 103 | -------------------------------------------------------------------------------- /05장/아이템_28/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_28/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_28/배열보다는_리스트를_사용하라_배키.md: -------------------------------------------------------------------------------- 1 | # [item 28] 배열보다는 리스트를 사용하라 2 | 3 | ### 배열과 제네릭 타입의 차이 4 | ! 배열은 공변이지만 제네릭은 불공변이다. 5 | 6 | > 공변과 불공변이란? 7 | > 공변은 '함께 변환이 가능하다'라는 뜻이다. 8 | > 예를 들어, Sub가 Super의 하위 타입이라면 배열은 공변이기 때문에 Sub[]는 Super[]의 하위 타입이 된다. 9 | > 하지만 제네릭은 불공변이기 때문에 List와 List는 서로 어떠한 관계도 없다. 10 | 11 | ### 배열보다 제네릭이 좋다! 12 | #### 이유1 : 런타임이 아닌 컴파일에 오류를 잡을 수 있다. 13 | 아래 코드는 Object 배열이라 컴파일시, 배열의 원소에 모든 값이 들어올 수 있다. 14 | 그러나 런타임시 ArrayStoreException을 던진다. 15 | ```java 16 | Object[] objectArray = new Long[1]; 17 | ObjectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다. 18 | ``` 19 | 20 | 하지만 제네릭을 사용하면 애초에 컴파일 오류가 발생한다. 21 | ```java 22 | List ol = new ArrayList(); // 호환되지 않는 타입이다. 23 | ol.add("타입이 달라 넣을 수 없다"); 24 | ``` 25 | 26 | #### 이유2: 배열은 실체화 타입이지만 제네릭은 실체화 불가 타입이다. 27 | 28 | > 실체화란? 29 | > 런타임 시에 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 30 | > 배열은 실체화 타입이기 때문에 Long 배열에 String 원소를 넣으려 하면 ArrayStoreException이 발생한다. 31 | > 하지만 제네릭은 런타임시에 어떤 타입을 가지는 소거된다. 즉, 컴파일 타임에만 원소 타입을 검사하며 런타임에는 알 수 없다. 32 | > (제네릭에서 실체화되는 타입은 비한정 와일드 카드 뿐이다.) 33 | 34 | 35 | ### 제네릭과 배열은 서로 어울릴 수 없다. 36 | 위에서 말한 2가지 이유로 봤을 때도 제네릭과 배열은 서로 상반된 특징을 지닌다. 37 | 때문에 아래처럼 배열과 제네릭은 잘 어우러지지 못한다. 38 | * 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. 39 | * `new List[]` 와 같이 사용할 수 없다. 40 | 41 | > 제네릭 배열을 만들지 못하게 막은 이유는 무엇일까? 42 | > : 타입 안전하지 않기 때문이다. 아래 예를 들어보자. 43 | ```java 44 | List[] stringLists = new List[1]; // (1) 45 | List intList = List.of(42); // (2) 46 | Object[] objects = stringLists; // (3) 47 | objects[0] = intList; // (4) 48 | String s = stringLists[0].get(0); // (5) 49 | ``` 50 | (1) 제네릭 배열이 허용된다고 가정 51 | (2) List 에 원소가 하나인 리스트를 초기화함 52 | (3) Object 타입의 배열에 List 타입의 배열을 할당(배열은 공변이므로 가능함) 53 | (4) Object 타입의 배열의 첫 번째 원소에 List 타입의 원소를 가지게 함(리스트 배열 가능) 54 | : 이미 List 타입의 원소를 가지게 했는데 List 값을 원소로 넣음 55 | (5) List 배열의 첫번째 값(intList)을 꺼내서 꺼낸 리스트의 첫번째 원소(42)를 꺼냄 56 | 57 | -> 5번 과정에서 꺼내야할 타입은 String인데 원소는 Integer이므로 ClassCastException이 발생한다. 58 | 59 | 60 | ### 배열을 제네릭 리스트로 바꾸는 법 61 | 배열을 어떻게 제네릭 리스트로 바꾸면 될까? 62 | 63 | 아래와 같이 컬렉션 안의 원소 하나를 무작위로 선택해서 반환하는 Chooser 클래스가 있다고 해보자. 64 | ```java 65 | public class Chooser { 66 | private final Object[] choiceArray; 67 | 68 | public Chooser(Collection choices) { 69 | choiceArray = choices.toArray(); 70 | } 71 | 72 | public Object choose() { // 여기서 클라이언트 코드가 형변환 해야 함 73 | Random rnd = ThreadLocalRandom.current(); 74 | return choiceArray[rnd.nextInt(choiceArray.length)]; 75 | } 76 | } 77 | ``` 78 | 위 클래스에서 무작위로 값을 반환하는 choose 메서드는 Object를 반환해야 한다. 때문에 클라이언트가 형변환을 시켜줘야 한다. 79 | 80 | 이를 해결하고자 제네릭을 사용해 보자. 81 | ```java 82 | public class Chooser { 83 | private final T[] choiceArray; 84 | 85 | public Chooser(Collection choices) { 86 | choiceArray = (T[]) choices.toArray(); 87 | } 88 | 89 | public T choose() { 90 | Random rnd = ThreadLocalRandom.current(); 91 | return choiceArray[rnd.nextInt(choiceArray.length)]; 92 | } 93 | } 94 | ``` 95 | 위와 같이 구현하면 Object를 형변환 하고있지 않다. 96 | 하지만 여전히 배열을 쓰고 있다. 97 | 98 | 또한, T가 무슨 타입인지 알 수 없으니, 컴파일러는 형변환이 런타임에도 안전한지 보장할 수 없다. 99 | 즉, 컴파일러가 안전을 보장하지 못한다. 그래서 비검사 경고를 알린다. 이를 해결하기 위해 아래와 같이 제네릭 리스트를 쓰자. 100 | ```java 101 | public class Chooser { 102 | private final List choiceList; 103 | 104 | public Chooser(Collection choices) { 105 | choiceArray = new ArrayList<>(choices); 106 | } 107 | 108 | public Object choose() { 109 | Random rnd = ThreadLocalRandom.current(); 110 | return choiceList.get(rnd.nextInt(choiceList.size())); 111 | } 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /05장/아이템_29/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_01.png -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_02.png -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_03.png -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_04.png -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_05.png -------------------------------------------------------------------------------- /05장/아이템_29/item29_zeus_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_29/item29_zeus_06.png -------------------------------------------------------------------------------- /05장/아이템_29/이왕이면_제네릭_타입으로_만들라_제우스.md: -------------------------------------------------------------------------------- 1 | # 아이템 29 이왕이면 제네릭 타입으로 만들라 2 | 3 | ## 스택 예제 4 | 5 | ### Before 6 | 7 | ```java 8 | import java.util.Arrays; 9 | import java.util.EmptyStackException; 10 | 11 | public class Stack { 12 | private Object[] elements; 13 | private int size = 0; 14 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 15 | 16 | public Stack() { 17 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 18 | } 19 | 20 | public void push(Object e) { 21 | ensureCapacity(); 22 | elements[size++] = e; 23 | } 24 | 25 | public Object pop() { 26 | if (size == 0) { 27 | throw new EmptyStackException(); 28 | } 29 | Object result = elements[--size]; 30 | elements[size] = null; 31 | return result; 32 | } 33 | 34 | public boolean isEmpty() { 35 | return size == 0; 36 | } 37 | 38 | private void ensureCapacity() { 39 | if (elements.length == size) { 40 | elements = Arrays.copyOf(elements, 2 * size + 1); 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | ### After 47 | 48 | ```java 49 | import java.util.EmptyStackException; 50 | 51 | public class Stack { 52 | private E[] elements; 53 | private int size = 0; 54 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 55 | 56 | public Stack() { 57 | elements = new E[DEFAULT_INITIAL_CAPACITY]; 58 | } 59 | 60 | public void push(E e) { 61 | ensureCapacity(); 62 | elements[size++] = e; 63 | } 64 | 65 | public E pop() { 66 | if (size == 0) { 67 | throw new EmptyStackException(); 68 | } 69 | E result = elements[--size]; 70 | elements[size] = null; 71 | return result; 72 | } 73 | 74 | // ensureCapacity() 메서드 생략 75 | } 76 | ``` 77 | 78 | ![](item29_zeus_01.png) 79 | 80 | E는 타입 파라미터이므로 new 키워드 뒤에 사용할 수 없다. 81 | 82 | 즉, E가 결정되지 않았으므로 인스턴스화할 수 없다. 83 | 84 | ![](item29_zeus_02.png) 85 | 86 | 직접 형변환하면 경고를 띄운다. 87 | 88 | 그러나 push 메서드를 통해 배열에 저장되는 원소의 타입은 항상 E라는 것이 보장된다. 89 | 90 | 그러므로 위 형변환은 안전하다. 91 | 92 | ![](item29_zeus_03.png) 93 | 94 | 타입 안전이 확보되었으므로 `@SuppressWarnings` 애너테이션으로 경고를 숨기자. 95 | 96 | --- 97 | 98 | 제네릭 배열 생성 오류를 해결하는 이런 방법도 있다. 99 | 100 | ```java 101 | // Before 102 | private E[] elements; 103 | 104 | // After 105 | private Object[] elements; 106 | ``` 107 | 108 | 인스턴스 변수의 타입을 `Object[]`로 바꾸면 다음의 에러가 발생하는데, 109 | 110 | ![](item29_zeus_04.png) 111 | 112 | 직접 형변환하면 된다. 그러면 경고만 뜬다. 113 | 114 | ![](item29_zeus_05.png) 115 | 116 | ## 추가자료 117 | 118 | ![](item29_zeus_06.png) 119 | 120 | ## 요약 121 | 122 | 형변환하지 말고 제네릭 타입을 써라. 123 | -------------------------------------------------------------------------------- /05장/아이템_29/이왕이면_제네릭_타입으로_만들라_켬미.md: -------------------------------------------------------------------------------- 1 | # Item29. 이왕이면 제네릭 타입으로 만들라 2 | 3 | 제네릭 타입을 새로 만드는 법에 대해 배워보자 ! 4 | 5 | 다음 Stack 코드를 살펴보자 6 | 7 | ```java 8 | public class Stack { 9 | private Object[] elements; // 🤮 10 | private int size = 0; 11 | private static final int DEFAULT = 16; 12 | 13 | public Stack() { 14 | elements = new Object[DEFAULT]; 15 | } 16 | 17 | public void push(Object e) { 18 | ensureCapacity(); 19 | elements[size++] = e; 20 | } 21 | 22 | public Object pop() { 23 | if (size == 0) 24 | throw new EmptyStackException(); 25 | Object result = elements[--size]; 26 | return result; 27 | } 28 | 29 | // ... 추가 메서드 ~~ 30 | } 31 | ``` 32 | 33 |
34 | 35 | 해당 클래스는 원래 제네릭 타입이어야 마땅하다. 36 | 37 | >WHY? 38 | >클라이언트가 스택에서 꺼낸 객체를 형변환 해야하는데, 이때 런타임 오류가 날 위험이 있다. 39 | 40 | 그리고 추가적으로 제네릭으로 바꾼다고 해도 현재 사용하는 클라이언트에는 아무런 해가 없다. 41 | 42 |
43 | 44 | > [!note] 정리 45 | > 클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다. 46 | > 그러니 새로운 타입을 설꼐할 때 형변환 없이도 사용할 수 있도록 제네릭 타입으로 만들어라~! 47 | > 48 | > 기존 클라이언트에는 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해준다. 49 | 50 |
51 | 52 | ## 제네릭으로 만들기 53 | 54 |
55 | 56 | ### 1. 클래스 선언에 타입 매개 변수를 추가 57 | 58 | 59 | ```java 60 | public class Stack 61 | ``` 62 | ↓ 63 | ```java 64 | public class Stack 65 | ``` 66 | 67 | > 타입 이름은 보통 E를 사용한다. 68 | 69 |
70 | 71 | ### 2. 코드에 쓰인 Object를 적절한 타입 매개변수로 바꾼다. 72 | 73 | 74 | 쉽게 말해서 Object를 제네릭 타입 E로 변경한다. 75 | 전체 코드를 보면 다음과 같다. 76 | 77 | ```java 78 | public class Stack { 79 | private E[] elements; 80 | private int size = 0; 81 | private static final int DEFAULT = 16; 82 | 83 | public Stack() { 84 | elements = new E[DEFAULT]; 85 | } 86 | 87 | public void push(E e) { 88 | ensureCapacity(); 89 | elements[size++] = e; 90 | } 91 | 92 | public E pop() { 93 | if (size == 0) 94 | throw new EmptyStackException(); 95 | E result = elements[--size]; 96 | return result; 97 | } 98 | 99 | // ... 추가 메서드 ~~ 100 | } 101 | ``` 102 | 103 |
104 | 105 | #### 여기서 잠깐 ! 위에 코드에 문제가 있습니다 ! 106 | 107 | 이 단계에서 보통 하나 이상의 오류나 경고가 발생한다. 108 | 이 클래스에서는 다음 하나의 오류가 발생했다. 109 | 110 | ```java 111 | Stack.java 8 : generic array creation 112 | elements = new E[DEFAULT]; 113 | ``` 114 | 115 | **WHY??** 116 | E와 같은 실체화 불과 타입으로는 배열을 만들 수 없다. 117 | 118 |
119 | 120 | ##### 그럼 배열을 사용하는 코드를 제네릭으로 만들 때 어떻게 해야해? 121 | 122 |
123 | 124 | ###### 1) Object 배열을 생성한 다음 제네릭으로 형변환 125 | 126 | : 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법 127 | 128 | ```java 129 | private E[] elements; 130 | 131 | public Stack() { 132 | elements = (E[]) new Object[DEFAULT]; 133 | } 134 | ``` 135 | 136 | > 이제 컴파일러는 오류 대신 경고를 내보낼 것이다. 137 | > 138 | > 여전히 (일반적으로) 타입 안전하지 않다. 139 | 140 | 141 | 하지만 우리가 안전하게 만들면 된다. 142 | private 필드로 두고, push되는 값은 제네릭으로 항상 E도록 하면 된다. (비검사 형변환) 143 | 144 | 안전함을 증명했다면, `@Suppresswarnings` 으로 해당 경고룰 숨긴다. 145 | 146 | ```java 147 | private E[] elements; 148 | 149 | @Suppresswarnings("unchecked") 150 | public Stack() { 151 | elements = (E[]) new Object[DEFAULT]; 152 | } 153 | ``` 154 | 155 |
156 | 157 | ###### 2) elements 필드의 타입을 E[] 에서 Object[]로 변경 158 | 159 | ```java 160 | private Object[] elements; 161 | 162 | public Stack() { 163 | elements = new Object[DEFAULT]; 164 | } 165 | ``` 166 | 167 | 이 경우도 하나의 오류가 발생한다. 168 | 169 | ```java 170 | Stack.java:19: incompatible types 171 | found Object, required: E 172 | E result = elements[--size]; 173 | ``` 174 | 175 | 오류를 해결하기 위해서는 원소 E로 형변환하면 경고가 뜬다. 176 | 177 | ```java 178 | E result = (E) elements[--size]; 179 | ``` 180 | 181 | E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다. 182 | 183 | 우리가 직접 증명하고 경고를 숨길 순 있다. 184 | pop 메서드 전체에서 경고를 숨기지 말고, 비검사 형변환을 수행하는 할당문에서만 숨겨보자. (아이템 27) 185 | 186 | ```java 187 | public E pop() { 188 | if(size == 0) 189 | throw new EmptyStackException(); 190 | 191 | // push에서 E 타입ㅂ만 허용하므로 안전 192 | @SuppressWarnings("uncheckd") E result = (E) elements[--size]; 193 | 194 | elements[size] = null; // 참조 해제 195 | return result; 196 | } 197 | ``` 198 | 199 |
200 | 201 | > [!note] 뭐를 추천하나? 202 | > 두 방법 다 나름의 지지를 얻고 있지만, **첫번째 방법**을 주로 사용한다. 203 | > - 가독성이 더 좋다 ! 204 | > - 코드도 더 짧다. 205 | > - 첫번째는 생성시에만, 두번째 방식에서는 배열에서 원소를 읽을 때마다 해줘야한다. 206 | > 207 | > but 208 | > 배열의 런타임 타입이 컴파일 타임 타입과 달라 힙 오염을 일으킨다. (아이템 32) 209 | > 힙 오염이 걸리면 두번째 방식 고고링~ 210 | 211 |
212 | 213 | ## 근데 배열보다 리스트를 우선하라면서욧? 214 | 215 | 사실 제네릭 타입 안에서 리스트를 사용하는게 항상 가능하지도, 꼭 더 좋은 것도 아니다. 216 | 217 | 자바가 리스트를 기본타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국은 기본 타입인 배열을 사용해 구현해야한다. 218 | 219 | 또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다. 220 | 221 |
222 | 223 | ## 제네릭 타입 매개 변수에 제약은? 224 | 225 | 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다. 226 | 어떤 참조타입으로도 Stack을 만들 수 있다. 227 | 228 | > 단 기본 타입은 사용할 수 없다. ! (int, double) 229 | 230 | ### 제약을 주고 싶으면? 231 | 232 | 한정적 타입 매개변수로 줄 수 있다. 233 | 234 | > ex. : Delayed의 하위 타입만 받는다는 뜻 235 | 236 | -------------------------------------------------------------------------------- /05장/아이템_30/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_30/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_30/item30_zeus_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_30/item30_zeus_01.png -------------------------------------------------------------------------------- /05장/아이템_30/item30_zeus_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_30/item30_zeus_02.png -------------------------------------------------------------------------------- /05장/아이템_30/이왕이면_제네릭_메서드로_만들라_제우스.md: -------------------------------------------------------------------------------- 1 | # 아이템 30 이왕이면 제네릭 메서드로 만들라 2 | 3 | ## Set union() 정적 메서드 예제 4 | 5 | ### Before 6 | 7 | ```java 8 | import java.util.HashSet; 9 | 10 | public static Set union(Set s1, Set s2) { 11 | Set result = new HashSet(s1); 12 | result.addAll(s2); 13 | return result; 14 | } 15 | ``` 16 | 17 | ### After 18 | 19 | ```java 20 | import java.util.HashSet; 21 | 22 | public static Set union(Set s1, Set s2) { 23 | Set result = new HashSet<>(); 24 | result.addAll(s2); 25 | return result; 26 | } 27 | ``` 28 | 29 | ### 제네릭 싱글턴 팩터리 패턴 30 | 31 | ```java 32 | private static UnaryOperator IDENTITY_FUNCTION = o -> o; 33 | 34 | public static UnaryOperator identityFunction() { 35 | return (UnaryOperator) IDENTITY_FUNCTION; 36 | } 37 | ``` 38 | 39 | 비검사 형변환 경고가 발생한다. 그러나 안전하다. 실제로 사용하면 다음과 같다. 40 | 41 | ```java 42 | public static void main(String[] args) { 43 | String[] strings = {"삼베", "대마", "나일론"}; 44 | UnaryOperator sameString = identityFunction(); 45 | for (String s : strings) { 46 | System.out.println(sameString.apply(s)); 47 | } 48 | 49 | Number[] numbers = {1, 2.0, 3L}; 50 | UnaryOperator sameNumber = identityFunction(); 51 | for (Number n : numbers) { 52 | System.out.println(sameNumber.apply(n)); 53 | } 54 | } 55 | ``` 56 | 57 | 사용할 땐 어떤 경고나 에러도 발생하지 않는다. 58 | 59 | ## 재귀적 타입 한정 60 | 61 | ```java 62 | public static > E max(Collection c); 63 | ``` 64 | 65 | "모든 타입 E는 자신과 비교할 수 있다." 66 | 67 | -> 타입 E의 인스턴스들은 서로 비교할 수 있다. 68 | 69 | ## 생각해볼만한 주제 70 | 71 | 생성자를 제네릭 메서드로 선언 vs 파라미터에 와일드카드 사용 72 | 73 | ![](item30_zeus_02.png) 74 | 75 | ## 요약 76 | 77 | 형변환하지 말고 제네릭 메서드로 만들라. 78 | -------------------------------------------------------------------------------- /05장/아이템_30/이왕이면_제네릭_메서드로_만들라_켬미.md: -------------------------------------------------------------------------------- 1 | # Item30. 이왕이면 제네릭 메서드로 만들라 2 | 3 | 메서드도 제네릭으로 만들 수 있다 ! 4 | 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 5 | 6 | 이번 예시는 두 집합의 합집합을 반환하는 문제가 있는 메서드이다. 7 | 8 | ```java 9 | public static Set union(Set s1, Set s2) { 10 | Set result = new HashSet(s1); 11 | result.addAll(s2); 12 | return result; 13 | } 14 | ``` 15 | 16 | 17 | 컴파일은 된다. BUT `unchecked` 경고 두 개 발생 ! 18 | 19 | ```java 20 | Set result = new HashSet(s1); // 경고 발생 21 | result.addAll(s2); // 경고 발생 22 | ``` 23 | 24 | 경고를 없애기 위해, 메서드를 타입 안전하게 만들어야한다. 25 | 26 |
27 | 28 | ## 제네릭 메서드로 만들기 29 | 30 |
31 | 32 | ### 1. 메서드 선언에서의 세 집합의 원소 타입을 타입 매개변수로 명시 33 | 34 | 35 | ### 2. 메서드 안에서 타입 매개변수만 사용하게 수정 36 | 37 | > (타입 매개변수들을 선언하는) 타입 매개변수 목록은 메서드와 제한자와 반환 타입 사이에 온다. 38 | > `public static Set union(...) { }` 39 | 40 | 41 | ```java 42 | public static Set union(Set s1, Set s2) { 43 | Set result = new HashSet<>(s1); 44 | result.addAll(s2); 45 | return result; 46 | } 47 | ``` 48 | 49 | 50 | 단순 제네릭 메서드라면 이정도로 충분하다. 51 | 이 메서드는 경고 없이 컴파일 되며, 타입 안전하고, 쓰기도 쉽다. 52 | 53 | 54 | > 추가적으로 와일드 카드 타입을 사용하면 더 유연하게 개선 가능 ! 55 | 56 |
57 | 58 | ## 불변 객체를 여러 타입으로 활용하고 싶어요 ! 59 | 60 | 제네릭은 런타임에 타입 정보가 소거되므로 하나의 객체를 어떤 타입으로든 매개변수화 할 수 있다. 61 | 62 | **BUT** 63 | 이렇게 하려면, 요청한 타입 매개 변수에 맞게 매번 그 객체의 타입을 바꿔주는 **정적 팩터리**를 만들어야 한다. 64 | 65 | 이 패턴을 **제네릭 싱글턴 팩터리**라 한다. 66 | 67 | > `Collections.reverseOrder` 같은 함수 객체나 68 | > `Collection.emptySet` 과 같은 컬렉션 용으로 사용한다. 69 | 70 |
71 | 72 | ## 제네릭 싱글턴 팩터리 73 | 74 | 이해를 돕기 위해 항등 함수를 담는 클래스를 만들고 싶다고 해보자. 75 | 76 | >항등 함수 77 | >![항등함수](https://i.imgur.com/ul0TiAQ.png) 78 | 79 | 항등함수 객체는 상태가 없으니, 요청할 때마다 새로 생성하는 것은 낭비다. 80 | 81 | 자바의 제네릭이 실체화된다면 항등함수를 타입별로 하나씩 만들어야 했겠지만, 소거 방식을 사용한 덕에 제네릭 싱글턴 하나면 충분하다. 82 | 83 | ```java 84 | private static UnaryOperator IDENTITY_FN = (t) -> t; 85 | 86 | @SuppressWarnings("uncheckd") 87 | public static UnaryOperator identityFunction() { 88 | return (UnaryOperator) IDENTITY_FN; 89 | } 90 | ``` 91 | 92 | 93 | 해당 함수를 사용한 예제 94 | ```java 95 | String [] strings = {"켬미", "제우스", "배키"}; 96 | UnaryOperator sameString = identityFunction(); 97 | for (String s: strings) 98 | System.out.println(sameString.apply(s)); 99 | 100 | Number [] numbers = {1, 2.0, 3L}; 101 | UnaryOperator sameNumbers = identityFunction(); 102 | for (Number s: numbers) 103 | System.out.println(sameNumbers.apply(s)); 104 | ``` 105 | 106 |
107 | 108 | ## 타입 매개변수 허용 범위 한정 109 | 110 | **재귀적 타입 한정**을 통해 타입 매개변수의 허용 범위를 한정할 수 있다. 111 | 112 | 재귀적 타입 한정은 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰인다. 113 | 114 | ```java 115 | public interface Comparable { // 자신과 같은 타입의 원소와만 비교할 수 있다. 116 | int compareTo(T o); 117 | } 118 | ``` 119 | 120 | 121 | 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현할 수 있다. 122 | 123 | ```java 124 | public static > E max(Collection c); 125 | ``` 126 | 127 | `>` : 모든 타입 E는 자신과 비교할 수 있다. 128 | 129 | > 상호 비교 가능하다는 뜻을 아주 정확하게 표현 130 | 131 | -------------------------------------------------------------------------------- /05장/아이템_31/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_31/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_32/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_32/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_32/제네릭과_가변인수를_함께_쓸_때는_신중하라_초롱.md: -------------------------------------------------------------------------------- 1 | # 가변인수 메서드 2 | ```java 3 | void sum(String... str) { 4 | for(String a:str) { 5 | System.out.println(a); 6 | } 7 | } 8 | ``` 9 | - 가벼인수 메서드를 호출하면, 가변인수를 담기 위한 배열이 자동으로 만들어진다. 10 | - 내부로 감춰야 했을 이 배열을 클라이언트에 노출하는 문제가 발생한다. 11 | - 따라서 제네릭과 함께 사용하게 되면 제네릭 배열이 만들어지고, 아이템 28에서 본 힙 오염이 발생하며, 아래와 같은 컴파일 경고를 낸다. 12 | ``` 13 | warning : [unchecked] Possible heap pollution from parameterized vararg type List 14 | ``` 15 | 16 | # 힙 오염 예시 17 | ```java 18 | static void dangerous(List... stringLists) { 19 | List intList = List.of(42); 20 | Object[] objects = stringLists; 21 | objects[0] = intList; // 힙 오염 발생 22 | String s = stringLists[0].get(0); // ClassCastException 23 | } 24 | ``` 25 | - `List[] stringLists` 생성된다. 26 | - 해당 배열은 공변이기 때문에 `Object[]`로 참조가 가능하다. 27 | - 제네릭은 런타임 시 타입정보가 소거되기 때문에 타입이 다른 `List intList`의 할당이 가능하다. -> 힙오염 발생 28 | - 이후 `stringLists`의 0번째 객체를 호출하면 `ClassCastException`이 발생하게 된다. 29 | 30 | 따라서 제네릭 가변인수 배열 매개변수에 값을 저장하는 것은 안전하지 않다. 31 | 32 | > 제네릭 배열을 프로그래머가 직접 생성하는 것은 허용하지 않는다. 33 | > 그런데 왜 제네릭 가변인수 매개변수를 받는 메서드를 선언하는 것은 가능할까? 34 | 35 | 제네릭이나 매개변수화 타입의 가변인수 매개변수를 받는 메서드가 실무에서 매우 유용하기 때문이다. 36 | - `Arrays.asList(T... a)` 37 | - `Collections.addAll(Collection c, T... elements)` 38 | - `EnumSet.of(E first, E... rest)` 39 | 40 | # @SafeVarargs 41 | - 자바 7 전에는 호출하는 곳마다 `@SuppressWarnings("unchecked")`을 달아 경고를 숨겨야 했다. 42 | - 자바 7에서는 `@SafeVarargs`이 추가되어 제네릭 가변인수 메서드 작성자가 클라이언트 측에서 발생하는 경고를 숨길 수 있게 되었다. 43 | ```java 44 | @SafeVarargs 45 | @SuppressWarnings("varargs") 46 | public static List asList(T... a) { 47 | return new ArrayList<>(a); 48 | } 49 | ``` 50 | - `@SafeVarargs`은 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치다. 51 | 52 | ### 안전한 경우 53 | 1. 메서드가 가변인수 배열에 아무것도 저장하지 않는 경우 54 | 2. 배열의 참조가 밖으로 노출되지 않는 경우 (신뢰할 수 없는 코드가 배열에 접근할 수 없는 경우) 55 | > 가변인수 매개변수 배열이 호출자로부터 그 메서드로 순수하게 인수들을 전달하는 일만 하는 경우 56 | 57 | # 제네릭 가변인수 매개변수를 안전하지 않게 사용하는 경우 58 | - 가변인수 매개변수 배열에 아무것도 저장하지 않고도 타입 안전성을 깰 수 있다. 59 | ```java 60 | public class PickTwo { 61 | 62 | static T[] toArray(T ... args) { // 자신의 제네릭 매개변수 배열의 참조를 노출 63 | return args; 64 | } 65 | 66 | static T[] pickTwo(T a, T b, T c) { 67 | switch(ThreadLocalRandom.current().nextInt(3)) { 68 | case 0: return (T[]) toArray(String.valueOf(a), String.valueOf(b)); 69 | case 1: return (T[]) toArray(String.valueOf(a), String.valueOf(c)); 70 | case 2: return (T[]) toArray(String.valueOf(b), String.valueOf(c)); 71 | } 72 | throw new AssertionError(); 73 | } 74 | 75 | public static void main(String[] args) { 76 | String[] tmp = pickTwo("좋은","빠른","저렴한"); 77 | } 78 | } 79 | ``` 80 | - 컴파일러는 `toArray`에 넘길 T 인스턴스 2개를 담을 가변인수 매개변수 배열을 만드는 코드를 생성한다. 81 | - 이 코드가 만드는 배열의 타입은 `Object[]`이다. 82 | - `toArray` 메서드가 만든 이 배열을 그대로 `pickTwo`를 호출한 클라이언트까지 전달된다. 83 | - 즉, `pickTwo`는 항상 `Object[]` 타입 배열을 반환한다. 84 | - `Object[]`는 `String[]`의 하위 타입이 아니므로 형변환에 실패하며 `ClassCastException`을 던진다. 85 | 86 | > 제네릭 가변인수 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다. 87 | 88 | # 제네릭 가변인수 매개변수를 안전하게 사용하는 경우 89 | ```java 90 | @SafeVarargs 91 | static List flatten(List... lists) { 92 | List result = new ArrayList<>(); 93 | for (List list : lists) 94 | result.addAll(list); 95 | return result; 96 | } 97 | ``` 98 | - 임의 개수의 리스트를 인수로 받아, 받은 순서대로 그 안의 모든 원소를 하나의 리스트로 옮겨 담아 반환한다. 99 | - 이 메서드에는 `@SafeVarargs`가 달려 있으므로 선언하는 쪽과 사용하는 쪽 모두에서 경고를 내지 않는다. 100 | 101 | # 제네릭 가변인수 매개변수를 List로 대체한 예 102 | ```java 103 | static List flatten(List> lists) { 104 | List result = new ArrayList<>(); 105 | for (List list : lists) { 106 | result.addAll(list); 107 | } 108 | return result; 109 | } 110 | ``` 111 | - 정적 팩토리 메서드인 `List.of`를 활용하면 임의 개수의 인수를 넘길 수 있다. 112 | - 이렇게 사용 가능한 이유는 `List.of`에도 `@SafeVarargs`가 달려있기 때문이다. 113 | - 이 방식의 장점은 컴파일러가 이 메서드의 타입 안전성을 검증할 수 있다는 데 있다. 114 | - `@SafeVarargs`를 직접 달지 않아도 되고, 실수로 안전하다고 판단할 걱정도 없다. 115 | - 이 방식은 위의 `toArray`처럼 가변인수 메서드를 안전하게 작성하는 것이 불가능한 상황에서도 사용할 수 있다. 116 | ```java 117 | static List pickTwo(T a, T b, T c){ 118 | switch(ThreadLocalRandom.current().nextInt(3)){ 119 | case 0: return List.of(a,b); 120 | case 1: return List.of(a,c); 121 | case 2: return List.of(b,c); 122 | } 123 | throw new AssertionError(); 124 | } 125 | 126 | public static void main(String[] args) { 127 | String[] tmp = pickTwo("좋은","빠른","저렴한"); 128 | } 129 | ``` 130 | 131 | # 결론 132 | - 메서드에 제네릭 가변인수 매개변수를 사용하고 싶다면 `@SafeVarargs`를 달자. 133 | - 아니라면 List를 사용하자. -------------------------------------------------------------------------------- /05장/아이템_33/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/05장/아이템_33/.gitkeep -------------------------------------------------------------------------------- /05장/아이템_33/타입_안전_이종_컨테이너를_고려하라_초롱.md: -------------------------------------------------------------------------------- 1 | # 타입 안전 이종 컨테이너 2 | ## 타입 안전 이종 컨테이너란 3 | - 타입 안전 이종 컨테이너는 영어로 `type safe heterogeneous container`이다. 4 | - `heterogeneous`는`여러 다른 종류들로 이뤄진` 이라는 뜻이다. 5 | - 즉, 타입 안전 이종 컨테이너란 `여러 다른 종류들로 이루어진 값을 저장하는 타입 안전한 객체`를 의미한다. 6 | 7 | ## 타입 안전 이종 컨테이너 패턴 8 | - 제네릭은 컬렉션과 단일원소 컨테이너에 흔히 쓰인다. 9 | - 이 때 매개변수화되는 대상은 원소가 아닌 컨테이너 자신이다. 10 | - 따라서 하나의 컨테이너에서 매개변수화할 수 있는 타입의 수가 제한된다. 11 | - ex) Set, Map 12 | - 하지만 유연한 수단이 필요할 때도 있다. 13 | - ex) 데이터베이스의 모든 열을 타입 안전하게 이용하고 싶다. 14 | - 컨테이너 대신 키를 매개변수화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공한다. 15 | 16 | ## 타입 안전 이종 컨테이너 패턴 예제 17 | ```java 18 | public class Favorites { 19 | private Map, Object> favorites = new HashMap<>(); 20 | 21 | public void putFavorite(Class type, T instance) { 22 | favorites.put(Objects.requireNonNull(type), instance); 23 | } 24 | 25 | public T getFavorite(Class type) { 26 | return type.cast(favorites.get(type)); 27 | } 28 | } 29 | 30 | public static void main(String[] args) { 31 | Favorites f = new Favorites(); 32 | 33 | f.putFavorite(String.class, "Java"); 34 | f.putFavorite(Integer.class, 0xcafebabe); 35 | f.putFavorite(Class.class, Favorites.class); 36 | 37 | String favoriteString = f.getFavorite(String.class); 38 | int favoriteInteger = f.getFavorite(Integer.class); 39 | Class favoriteClass = f.getFavorite(Class.class); 40 | 41 | System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName()); 42 | } 43 | ``` 44 | ## Class 45 | - 각 타입의 Class 객체를 매개변수화한 키로 사용하며, 이러한 Class 객체를 `타입 토큰`이라고 한다. 46 | - 타입 토큰 : 컴파일타임 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴 47 | - 키 타입에 와일드카드 타입을 사용하여, 모든 키가 서로 다른 매개변수화 타입일 수 있다. 48 | 49 | ## Object 50 | - 키와 값 사이의 타입 관계를 보증하지 않는다. 51 | - 하지만 우리는 이 관계가 성립함을 알고 있다. 52 | 53 | ## pubFavorite 54 | - 주어진 Class 객체와 즐겨찾기 인스턴스를 favorites에 추가하여 관계를 짓는다. 55 | - 이 때 키와 값 사이의 '타입 링크' 정보는 버려진다. 56 | - 즉, 그 값이 그 키 타입의 인스턴스라는 정보가 사라진다. 57 | 58 | ## getFavorite 59 | - 주어진 Class 객체에 해당하는 값을 favorites 맵에서 꺼낸다. 60 | - 이 객체가 반환해야할 타입 객체는 맞지만, 잘못된 컴파일타임 타입(Object)를 가지고 있어 T 타입으로 바꿔야 한다. 61 | - Class의 `cast()` 메서드를 사용해 객체 참조를 Class 객체가 가리키는 타입으로 동적 형변환한다. 62 | 63 | ### cast 메서드 64 | - cast 메서드의 시그니처가 Class 클래스가 제네릭이라는 이점을 완벽히 활용한다. 65 | - cast의 반환 타입은 Class 객체의 타입 매개변수와 같다. 66 | ```java 67 | public class Class { 68 | @SuppressWarnings("unchecked") 69 | public T cast(Object obj) { 70 | if (obj != null && !isInstance(obj)) 71 | throw new ClassCastException(cannotCastMsg(obj)); 72 | return (T) obj; 73 | } 74 | } 75 | ``` 76 | - 따라서 Favorites를 T로 비검사 형변환하지 않고도 타입 안전하게 만들 수 있다. 77 | 78 | # Favorites 클래스의 제약 사항 79 | 1. Class 객체를 제네릭이 아닌 로 타입으로 넘기면 Favorites 인스턴스의 타입 안전성이 쉽게 깨진다. 80 | - 예를 들어 아래의 코드는 동작한다. 81 | ```java 82 | HashSet tmp = new HashSet<>(); 83 | ((HashSet)tmp).add("하이"); 84 | ``` 85 | - 해당 문제는 동적 형변환을 통해 해결할 수 있다. 86 | 2. 실체화 불가 타입은 사용할 수 없다. 87 | - 예를 들어 String 이나 String[ ]은 저장할 수 있어도 즐겨찾는 List은 저장할 수 없다. 88 | - List용 Class 객체를 얻을 수 없기 때문이다. 89 | - 이 제약에 대한 완벽한 우회로는 없다. 90 | 91 | ## 동적 형변환 92 | - 동적 형변환을 통해 인수로 주어진 인스턴스의 타입이 type으로 명시한 타입과 같은지 확인하면 된다. 93 | ```java 94 | public void putFavorite(Class type, T instance) { 95 | favorites.put(Objects.requireNonNull(type), type.cast(instance)); 96 | } 97 | ``` 98 | 99 | # 한정적 타입 토큰 100 | - Favorites가 사용하는 타입 토큰은 비한정적이다. 101 | - 즉 getFavorite과 putFavorite은 어떤 Class든 받아들인다. 102 | - 때로는 이 메서드들이 허용하는 타입을 제한하고 싶을 수 있는데, 이 때는 `한정적 타입 토큰`을 사용하면 된다. 103 | - 한정적 타입 토큰이란, `한정적 타입 매개변수`나 `한정적 와일드카드`를 사용하여 표현 가능한 타입을 제한하는 타입 토큰이다. 104 | - 어노테이션 API는 한정적 타입 토큰을 적극적으로 사용한다. 105 | ```java 106 | public T getAnnotation(Class annotationType); 107 | ``` 108 | - annotationType 인수는 애너테이션 타입을 뜻하는 한정적 타입 토큰이다. 109 | - 애너테이션된 요소는 키가 애너테이션 타입인 타입 안전 이종 컨테이너다. 110 | 111 | > Q. Class 타입의 객체가 있고, 이를 한정적 타입 토큰을 받는 메서드에 넘기려면 어떻게 해야 할까? 112 | 113 | - 메서드에 넘기기 위해서는 객체를 Class으로 형변환해야 한다. 114 | - 운 좋게도, Class 클래스는 이런 형변환을 안전하고 동적으로 수행해주는 메서드를 제공한다. 115 | - `asSubclass` 메서드 116 | - 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환한다. 117 | - 형변환에 성공하면 인수로 받은 클래스 객체를 반환하고, 실패하면 `ClassCastException을 던진다. 118 | ```java 119 | static Annotation getAnnotation(AnnotatedElement element, 120 | String annotationTypeName) { 121 | Class annotationType = null; // 비한정적 타입 토큰 122 | try { 123 | annotationType = Class.forName(annotationTypeName); 124 | } catch (Exception ex) { 125 | throw new IllegalArgumentException(ex); 126 | } 127 | return element.getAnnotation( 128 | annotationType.asSubclass(Annotation.class)); 129 | } 130 | ``` -------------------------------------------------------------------------------- /05장/아이템_33/타입_안전_이종_컨테이너를_고려하라_폰드.md: -------------------------------------------------------------------------------- 1 | **타입 안전 이종 컨테이너** 2 | - 키를 매개변수화 하여 여러 타입을 담을 수 있는 타입 안전한 컨테이너 ex) 데이터베이스 3 | - 매개변수화 할 수 있는 타입의 수 제한없이 사용할 수 있도록 설계 4 | 5 | ### 구현 6 | 각 타입의 Class 객체를 매개변수화한 키 역할로 사용 ```Class``` 7 | class의 클래스는 제네릭이기 때문에 동작 가능 ```ex) String.class의 타입은 Class``` 8 | - 타입 토큰: 컴파일타임 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드 들이 주고받는 class 리터럴 ```ex) String.class``` 9 | ```java 10 | public class Favorites { 11 | private Map, Object> favorites = new HashMap<>(); 12 | 13 | public void putFavorite(Class type, T instance) { 14 | favorites.put(Objects.requireNonNull(type), instance); 15 | } 16 | 17 | public T getFavorite(Class type) { 18 | return type.cast(favorites.get(type)); 19 | } 20 | 21 | public static void main(String[] args) { 22 | Favorites f = new Favorites(); 23 | 24 | f.putFavorite(String.class, "Java"); 25 | f.putFavorite(Integer.class, 11); 26 | f.putFavorite(Class.class, Favorites.class); 27 | 28 | String favoriteString = f.getFavorite(String.class); 29 | int favoriteInteger = f.getFavorite(Integer.class); 30 | Class favoriteClass = f.getFavorite(Class.class); 31 | 32 | System.out.printf("%s %d %s%n", favoriteString, favoriteInteger, favoriteClass.getName()); // Java 11 Favorites 33 | } 34 | } 35 | 36 | ``` 37 | Map, Object> 38 | - 키를 와일드카드 타입으로 두어 다양한 타입 지원 39 | - 값 타입은 Object로, 키와 값 사이의 타입 관계 보증하지 않음. 40 | - 값을 맵에서 꺼내올 때 이 관계를 되살려야 한다.(T로 바꿔 반환 필요) 41 | - cast 메서드를 사용해 Class 객체가 가리키는 타입으로 동적 형변환 42 | - cast 반환 타입이 Class 객체의 타입 매개변수와 같으므로 비검사 형변환 없이 타입 안전하게 만들 수 있음. 43 | ```java 44 | public final class Class { 45 | 46 | @SuppressWarnings("unchecked") 47 | @HotSpotIntrinsicCandidate 48 | public T cast(Object obj) { 49 | if (obj != null && !isInstance(obj)) 50 | throw new ClassCastException(cannotCastMsg(obj)); 51 | return (T) obj; 52 | } 53 | } 54 | ``` 55 | ### 제약 56 | - 클라이언트가 Class 객체를 로타입으로 넘기면 타입 안정성 쉽게 깨진다. 57 | ```java 58 | f.put((Class) Integer.class, "Integer의 인스턴스가 아닙니다."); 59 | int value = f.getFavorite(Integer.class); 60 | ``` 61 | - 인스턴스 추가 메서드에서 동적형변환 사용해 타입 안정성 검증 62 | - 실체화 불가 타입에는 사용 불가 63 | - class 리터럴에는 로타입을 사용해야 하기 때문. 64 | 65 | ### 한정적 타입 토큰 66 | - 허용하는 타입을 제한하고 싶을 때 사용 ex) 애너테이션 API... 67 | -------------------------------------------------------------------------------- /06장/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/06장/.gitkeep -------------------------------------------------------------------------------- /06장/아이템_34/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/06장/아이템_34/.gitkeep -------------------------------------------------------------------------------- /06장/아이템_35/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/06장/아이템_35/.gitkeep -------------------------------------------------------------------------------- /06장/아이템_35/ordinal_메서드_대신_인스턴스_필드를_사용하라_배키.md: -------------------------------------------------------------------------------- 1 | # [item 35] ordinal 메서드 대신 인스턴스 필드를 사용하라 2 | 3 | 4 | > 대부분 열거 타입 상수는 하나의 정수값에 대응된다. 5 | 6 | > ordinal 메서드는 각 타입에 해당하는 상수를 반환하는 역할을 한다. 7 | 8 | 9 | ### ordinal 사용 예시 10 | 11 | 아래 예시를 보자. 12 | 아래 열거 타입은 합주단 종류를 나타낸 것이다. numberOfMusicians()를 호출하면 ordinal을 이용하여 합주자의 수를 반환한다. 13 | ```java 14 | public enum Ensemble { 15 | SOLO, DUET, TRIO, QUARTET, QUINTET, 16 | SEXTET, SEPET, OCTET, NONET, DECTET; 17 | 18 | public int numberOfMusicians() { return ordinal() + 1; } 19 | } 20 | ``` 21 | 22 | ### ordinal을 사용하면 안된다. 23 | * 상수 선언 순서를 바꾸는 순간 각 상수에 해당하는 정수 값이 바뀌기 때문이다. 24 | * 만약 8중주가 하나 더 존재한다면 어떻게 추가해야 할까? 할 수 없다. 25 | * 값을 중간에 비워둘 수도 없다. 26 | 27 | 28 | ### 그럼 어떻게 할까? 29 | 아래와 같이 인스턴스 필드를 사용하자. 30 | ```java 31 | public enum Ensemble { 32 | SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), 33 | SEXTET(6), SEPET(7), OCTET(8), DOUBLE_QUARTET(8), 34 | NONET(9), DECTET(10), TRIPLE_QUARTET(12); 35 | 36 | private final int numberOfMusicians; 37 | Ensemble(int numberOfMusicians) { 38 | this.numberOfMusicians = numberOfMusicians; 39 | } 40 | public int numberOfMusicians() { return this.numberOfMusicians; } 41 | } 42 | ``` 43 | 44 | ### 그럼 ordinal은 왜 존재하는가? 45 | EnumSet과 EnumMap과 같이 열거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다. 46 | -------------------------------------------------------------------------------- /06장/아이템_35/ordinal_메서드_대신_인스턴스_필드를_사용하라_제우스.md: -------------------------------------------------------------------------------- 1 | # 아이템 35: ordinal 메서드 대신 인스턴스 필드를 사용하라 2 | 3 | ## Before 4 | 5 | ```java 6 | public enum Ensemble { 7 | 8 | SOLO, 9 | DUET, 10 | TRIO, 11 | QUARTET, 12 | QUINTET, 13 | SEXTET, 14 | SEPTET, 15 | OCTET, 16 | NONET, 17 | DECTET 18 | ; 19 | 20 | public int numberOfMusicians() { 21 | return ordinal() + 1; 22 | } 23 | } 24 | ``` 25 | 26 | ## After 27 | 28 | ```java 29 | public enum Ensemble { 30 | 31 | SOLO(1), 32 | DUET(2), 33 | TRIO(3), 34 | QUARTET(4), 35 | QUINTET(5), 36 | SEXTET(6), 37 | SEPTET(7), 38 | OCTET(8), 39 | NONET(9), 40 | DECTET(10) 41 | ; 42 | 43 | private final int numberOfMusicians; 44 | 45 | Ensemble(int numberOfMusicians) { 46 | this.numberOfMusicians = numberOfMusicians; 47 | } 48 | 49 | public int numberOfMusicians() { 50 | return numberOfMusicians; 51 | } 52 | } 53 | ``` 54 | 55 | ## 결론 56 | 57 | - `ordinal()`은 `EnumSet`, `EnumMap`과 같이 열거 타입 기반 범용 자료구조에 사용될 목적으로 설계되었다. 58 | - `ordinal()` 쓰지 마라. 59 | -------------------------------------------------------------------------------- /06장/아이템_36/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2024-effective-java/955837862266e7fc8f0ea53107a12ba7ac9d3f76/06장/아이템_36/.gitkeep -------------------------------------------------------------------------------- /06장/아이템_36/비트_필드_대신_EnumSet을_사용하라.md: -------------------------------------------------------------------------------- 1 | > 작성자: 프람 2 | > 3 | > 작성 일시: 2024.05.16 4 | > 5 | > 내용: Effective java 3/E 아이템-36 6 | 7 | 8 | ## 비트 필드란? 9 | 과거 컴퓨터 메모리 자원 귀했을 때, 효율적으로 데이터를 저장하기 위해 등장한 개념입니다. 10 | 11 | 아래 예시를 한번 봅시다. 12 | 13 | ```java 14 | public class Text { 15 | 16 | public static final int STYLE_BOLD = 1 << 0; //1 17 | public static final int STYLE_ITALIC = 1 << 1; //2 18 | public static final int STYLE_UNDERLINE = 1 << 2; //3 19 | public static final int STYLE_STRIKETHROUGH = 1 << 3; //4 20 | 21 | //매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다. 22 | public void applyStyles(int styles) { 23 | //... 24 | } 25 | } 26 | ``` 27 | 28 | STYLE_BOLD(1), STYLE_ITALIC(2), STYLE_UNDERLINE(4), STYLE_STRIKETHROUGH(8) 4가지의 클래스 변수를 가지고 있습니다. 차례대로 2의 제곱수로 구성되어있습니다. 29 | 30 | 31 | 32 | 그렇게 된다면, 해당 클래스 변수를 통해 아래 예시 method인 applyStyles와 같은 메스드의 파라미터에 Bit-wise operation을 통해 클래스의 속성을 찾아주는 형식입니다. 33 | 34 | 35 | 36 | 상수를 2의 제곱수로 두고 재활용하게 된다면, 불필요한 메모리 낭비를 줄일 수 있는 메커니즘인거죠. 37 | 38 | ```java 39 | //사용 예시 (OR 연산) 40 | text.applyStyles(STYLE_BOLD | STYLE_ITALIC); 41 | ``` 42 | 43 | 44 | 45 | 이러한 비트 필드을 이용한다면, 비트별 연산을(합집합, 교집합, 집합 연산)을 효율적으로 수행할 수 있습니다. 46 | 47 | 48 | 49 | 다만, 현재는 메모리의 발전으로 이러한 코드를 찾아보기는 들물 것입니다. 50 | 51 | 52 | 53 | 그럼에도, 비드 필드가 가지는 문제점을 몇가지 짚어보겠습니다. 54 | 55 | 56 | 57 | 1. 해석이 어렵다. 58 | 2. 모든 원소를 순회하기 까다롭다. 59 | 3. 최대 몇 비트가 필요한지 예측하여야 한다. (API 확장의 어려움) 60 | 61 | 62 | 63 | 대안 64 | 이런 난해한 코드를 없애주고, 성능 역시 큰 차이가 없는 EnumSet을 사용할 수 있겠습니다. 65 | 66 | ```java 67 | public class Text { 68 | public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } 69 | 70 | // 어떤 Set을 넘겨도 되나, EnumSet이 가장 좋다. 71 | public void applyStyles(Set