├── README.md └── handbook ├── 10 ├── 69. 예외는 진짜 예외 상황에서만 사용하라.md ├── 70. 복구할 수 있는 상황에는 검사 예외, 프로그래밍 오류에는 런타임 예외를 써라.md ├── 71. 필요 없는 검사 예외 사용은 피하라.md ├── 72. 표준예외를 사용하라.md ├── 73. 추상화 수준에 맞는 예외를 던지라.md ├── 74. 메서드가 던지는 모든 예외를 문서화하라.md ├── 75. 예외의 상세 메시지에 실패 관련 정보를 담으라.md ├── 76. 가능한 한 실패원자적으로 만들라.md ├── 77. 예외를 무시하지 말라.md └── images │ ├── 75-1.png │ ├── 75-2.png │ ├── 75-3.png │ ├── checkedCrapMethod.png │ ├── checkedInStream.png │ ├── checkedTryCatch.png │ ├── crapResult.png │ ├── ptsd.png │ ├── uncheckedMethod.png │ ├── uncheckedPermitted.png │ └── uncheckedStream.png ├── 11 ├── 78. 공유 중인 가변 데이터는 동기화해 사용하라.md ├── 79. 과도한 동기화는 피하라.md ├── 80. 스레드보다는 실행자, 태스크, 스트림을 애용하라.md ├── 82. 스레드 안전성 수준을 문서화하라.md ├── 83. 지연 초기화는 신중히 사용하라.md └── 84. 프로그램의 동작을 스레드 스케줄러에 기대지 말라.md ├── 12 ├── 85. 웬만하면 자바 직렬화 쓰지마라.md ├── 86. Serializable을 구현할지는 신중히 결정하라.md ├── 87.커스텀 직렬화 형태를 고려하라.md ├── 88. readObject 메서드는 방어적으로 작성하라.md ├── 89. 인스턴스 수를 통제해야 한다면 readResolve 보다는 열거 타입을 사용하라.md ├── 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라.md └── images │ ├── 90-1.png │ └── annyeong.jpeg ├── 02 ├── 01.정적 팩토리 메서드로 생성 목적에 맞는 객체를 생성하라.md ├── 02.매개변수가 많으면, 빌더로 효율적이고 안전하게 생성할 수 있다.md ├── 03.Enum(열거타입) 방식, 또는 PRIVATE 생성자로서 인스턴스가 오직 하나뿐인 싱글턴임을 보증하라.md ├── 04.인스턴스화를 막으려거든 private 생성자를 사용하라.md ├── 05.자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.md ├── 06.불필요한 객체 생성을 피하라.md ├── 07.메모리 누수가 발생할 가능성이 있는 객체는 미리 처리하라.md ├── 08.finalizer와 cleaner 사용을 피하라. (그냥 쓰지마라).md └── 09.try-finally 보다는 try-with-resources를 사용하라.md ├── 03 ├── 10.equals 만들 때 일반규약을 지켜라 귀찮으면 IDE가 만들어주는거 써라.md ├── 11.equals 를 재정의 하려거든 hashCode 도 재정의하라.md ├── 12.유용한 정보를 가지도록 toString을 재정의하자.md ├── 13.clone 재정의는 주의해서 진행.md └── 14.Comparable을 구현할지 고려하라.md ├── 04 ├── 15.클래스와 멤버의 접근 권한을 최소화하라.md ├── 16.public 클래스의 필드를 외부로 직접 노출하지 마라.md ├── 17.변경 가능성을 최소화하라. 불변 객체를 만들자.md ├── 18.상속보다는 컴포지션과 전달을 사용하자. 래퍼클래스가 더 강력하다.md ├── 19.상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속은 금지하라.md ├── 20.추상 클래스보다는 인터페이스를 우선하라.md ├── 21.인터페이스는 구현하는 쪽을 생각해 설계하라.md ├── 22.상수 인터페이스 절대 쓰지마라.md ├── 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라.md ├── 24. 멤버 클래스는 되도록 static으로 만들라.md └── 25. 톱레벨 클래스는 한 파일에 하나만 담기.md ├── 05 ├── 26. 로 타입은 사용하지 말라.md ├── 27. 비검사 경고를 제거하라.md ├── 28. 배열보다는 리스트를 사용하라.md ├── 29. 이왕이면 제네릭 타입으로 만들라.md ├── 30.이왕이면 제네릭 메서드로 만들라.md ├── 31.한정적 와일드카드를 사용해 API 유연성을 높이라.md ├── 32. 제네릭과 가변인수를 함께 쓸 때는 신중해라.md └── 33. 타입 안전 이종 컨테이너를 고려하라.md ├── 06 ├── 34. int 상수 대신 열거 타입 써라.md ├── 35. ordinal 메서드 대신 인스턴스 필드를 사용하라.md ├── 36. 비트 필드 대신 EnumSet을 사용하라.md ├── 37. ordinal 인덱싱 대신 EnumMap을 사용하라.md ├── 39 명명 패턴보다 애너테이션을 사용하라.md ├── 40. 재정의 할 때는 항상 @Override 애너테이션을 붙여라.md ├── 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라.md ├── 42.익명 클래스 보다는 람다를 사용하라.md ├── 43. 람다보다는 메서드 참조를 사용하라.md └── 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라.md ├── 07 ├── 44. 표준 함수형 인터페이스를 사용하라.md ├── 45. 스트림은 주의해서 사용하라.md ├── 46. 스트림에서는 부작용 없는 함수를 사용하라.md ├── 47. 반환 타입으로는 스트림보다 컬렉션이 낫다.md └── 48. 스트림 병렬화는 주의해서 적용하라.md ├── 08 ├── 49. 매개변수가 유효한지 검사하라.md ├── 50. 적시에 방어적 복사본을 만들라.md ├── 51. 메서드 시그니처를 신중히 설계하라.md ├── 52. 다중정의는 신중히 사용하라.md ├── 53.가변인수는 신중히 사용하라.md ├── 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라.md ├── 55. 옵셔널 반환은 신중히 하라.md ├── 56. images │ ├── img.png │ ├── img_1.png │ └── img_2.png └── 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라.md └── 09 ├── 57. 지역변수의 범위를 최소화하라.md ├── 58. 전통적인 for 문보다는 for-each 문을 사용하라.md ├── 59. 라이브러리를 익히고 사용하라.md ├── 60. 금융 계산 등 정확한 답이 필요하다면 float와 double은 피하라.md ├── 61. 박싱된 기본 타입보다는 기본 타입을 사용하라.md ├── 62. 다른 타입이 적절하다면 문자열 사용을 피하라.md ├── 63. 문자열 연결은 느리니 주의하라.md ├── 64. 객체는 인터페이스를 사용해 참조하라.md ├── 65. 리플렉션 보다는 인터페이스를 사용하라.md ├── 66. 네이티브 메서드는 신중히 사용하라.md ├── 67. 최적화는 신중히 하라.md ├── 68. 일반적으로 통용되는 명명 규칙을 따르라.md └── images └── 60-1.png /handbook/02/01.정적 팩토리 메서드로 생성 목적에 맞는 객체를 생성하라.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 정적 팩토리 메서드로 생성 목적에 맞는 객체를 생성하라 4 | 5 | 정적 팩토리 메서드(Static Factory Method)란 public 생성자와 별도로 객체를 생성하는 메서드이다 6 | 7 | ```java 8 | // public 생성자를 이용한 객체 생성 9 | Car car = new Car("dog"); 10 | 11 | // 정적 팩토리 메서드를 이용한 객체 생성 12 | // Integer Wrapper 객체를 반환 13 | Integer i = Integer.valueOf(10); 14 | 15 | // 생성할 클래스가 아닌 다른 클래스에 정의된 정적 팩토리 메소드 16 | BufferReader br = Files.newBufferedReader(path) 17 | ``` 18 | 19 | 1. [정적 팩토리 메서드의 많은 장점](#정적-팩토리-메서드의-많은-장점) 20 | 2. [정적 팩토리 메서드의 사소한 단점](#정적-팩토리-메서드의-사소한-단점) 21 | 3. [정적 팩토리 메서드 네이밍 컨벤션](#정적-팩토리-메서드-네이밍-컨벤션) 22 | 23 |
24 | 25 | ## 정적 팩토리 메서드의 많은 장점 26 | 27 | - 반환 될 객체의 특성을 묘사하는 이름을 지을 수 있다 28 | ```java 29 | // 반환 된 Car의 위치가 0임을 짐작할 수 있다 30 | Car firstCar = Car.createZeroPosition("dog"); 31 | ``` 32 | 클래스에 여러 생성자가 필요할 때, 동일 시그니처로 생성할 수 없다 33 | 따라서, 정적 팩토리 메서드를 이용하여 각각의 차이를 드러내는 이름을 붙여준다 34 | `💡 동일 시그니처란?: 메서드 명과 파라미터가 모두 같음` 35 | 36 |
37 | 38 | - 호출할 때 마다 새로운 인스턴스를 생성하지 않아도 된다 39 | 40 | ```java 41 | Boolean b = Boolean.valueOf(true); 42 | 43 | // TRUE, FALSE를 반환하고 있다 44 | @HotSpotIntrinsicCandidate 45 | public static Boolean valueOf(boolean b) { 46 | return (b ? TRUE : FALSE); 47 | } 48 | ``` 49 | 불변 클래스의 인스턴스를 미리 만들어 놓거나, 인스턴스를 캐싱해 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다 50 | 이렇게 사용한다면 객체 생성 비용이 클 때 성능을 끌어올릴 수 있다 51 | 52 |
53 | 54 | - 반환 타입의 하위 타입 객체를 반환할 수 있다 55 | 56 | ```java 57 | public interface Crew { 58 | static Type getBackendCrew() { 59 | return new BackendCrew(); 60 | } 61 | 62 | static Type getFrontendCrew() { 63 | return new FrontendCrew(); 64 | } 65 | } 66 | ``` 67 | 68 | 구현 클래스를 노출시키지 않는 동시에, 사용자에게 반환 클래스가 어떤 클래스인지 굳이 찾아보지 않도록 할 수 있다 69 | 70 |
71 | 72 | - 매개변수에 따라 다른 클래스 인스턴스를 반환할 수 있다 73 | 74 | ```java 75 | public class WoowaMember { 76 | 77 | public static WoowaMember getMember(boolean isCrew) { 78 | return flag ? new Crew() : new Coach(); 79 | } 80 | 81 | static class Crew extends WoowaMember { 82 | } 83 | 84 | static class Coach extends WoowaMember { 85 | } 86 | 87 | public static void main(String[] args) { 88 | WoowaMember member1 = WoowaMember.getWoowaMember(true); // Crew 89 | WoowaMember member2 = WoowaMember.getWoowaMember(false); // Coach 90 | } 91 | } 92 | ``` 93 | 94 |
95 | 96 | - 작성 시점에서 반환 객체의 클래스가 존재하지 않아도 된다 97 | 98 | 서비스 제공자 프레임워크의 근반이 되며, 대표적으로 JDBC가 존재한다 99 | `작성자가 이해를 제대로 하지 못 한 관계로 jdbc의 예를 들어 설명을 잘 해둔 링크로 대체합니다` 100 | https://github.com/2021BookChallenge/Effective-Java/issues/1#issuecomment-755231691 101 | 102 | 103 |
104 | 105 | ## 정적 팩토리 메서드의 사소한 단점 106 | 107 | - 상속을 하려면 public이나 protected 생성자가 필요하다 108 | 하지만 상속보다 컴포지션을 사용하도록 유도하고, 불변 타입으로 만들기 위한 목적이라면, 단점이 아닌 장점으로 받아들일 수 있다 109 | 110 | - 프로그래머가 찾기 어렵다 111 | 다른 정적 메서드와 구분이 어려우므로 문서화를 잘 해두어야 한다 112 | 이 단점은 네이밍 컨벤션을 통해 완화할 수 있다 113 | 114 |
115 | 116 | ## 정적 팩토리 메서드 네이밍 컨벤션 117 | 118 | - from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환 119 | ```java 120 | Date date = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); 121 | ``` 122 | 123 | - of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환 124 | ```java 125 | Set lottoPrizes = EnumSet.of(FIRST, SECOND, THIRD, FOURTH, FIFTH); 126 | ``` 127 | 128 | - valueOf : from과 of의 더 자세한 버전 129 | ```java 130 | int x = Integer.valueOf("20"); 131 | ``` 132 | 133 | - Instance / getInstance : 매개변수로 명시한 인스턴스를 반환하나, 같은 인스턴스를 보장하지 않음 134 | ```java 135 | Calendar calendar = Calendar.getInstance(); 136 | ``` 137 | 138 | - create / newInstance : 매번 새로운 인스턴스 생성을 보장 139 | 140 | - getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 정의 141 | ```java 142 | int a = Character.getType('A'); 143 | ``` 144 | 145 | - newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 정의 146 | ```java 147 | BufferReader br = Files.newBufferedReader(path) 148 | ``` 149 | 150 | - type : getType과 newType의 간결한 버전 151 | ```java 152 | List litany = Collections.list(legacyLitany); 153 | ``` 154 | 155 |
156 | 157 | ## 참고한 글들 158 | 159 | https://devlog-wjdrbs96.tistory.com/256 160 | https://velog.io/@ljinsk3/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C%EB%8A%94-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C 161 | 162 |
163 | -------------------------------------------------------------------------------- /handbook/02/02.매개변수가 많으면, 빌더로 효율적이고 안전하게 생성할 수 있다.md: -------------------------------------------------------------------------------- 1 | `소주캉` 2 | 3 | ### `빌더🧱 패턴`이란🤔? 4 | 5 | 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법 6 | 7 | 1. 클라이언트는 객체를 직접 만들지 않고 필수 매개변수만으로 생성자(정적 팩토리 메서드)를 호출해 `빌더` 객체를 얻는다. 8 | 2. `빌더` 객체가 제공하는 메서드들로 원하는 선택 매개변수를 설정한다. 9 | 3. 매개변수가 없는 `build``메서드를 호출해 일반적으로 불변 객체를 얻는다. 10 | 4. `빌더`는 일반적으로 생성할 클래스 안에 정적 멤버 클래스로 둔다. 11 | 12 | ### 왜 사용할까? 13 | 14 | 객체의 생성자나 정적 팩토리 메서드가 매개변수가 많고 복잡하다면, 객체를 만드는 프로세스를 독립적으로 분리할 필요가 있다. 이때 빌더 패턴을 사용해볼 수 있다. 15 | 16 | ### 실 사용 예시🔧 17 | 18 | #### 자바 8 Stream.Builder API 19 | 20 | ```java 21 | public static void main(String[] args) { 22 | Stream.Builder builder = Stream.builder(); 23 | List result = builder.add("이름은") 24 | .add("포비") 25 | .add("미래소년") 26 | .build() 27 | .collect(Collectors.toList()); 28 | 29 | System.out.println(result); 30 | } 31 | ``` 32 | 33 | #### StringBuilder 34 | 35 | ```java 36 | public static void main(String[] args) { 37 | StringBuilder builder = new StringBuilder(); 38 | String builtString = builder.append(500) 39 | .append("원의 ") 40 | .append(0.01) 41 | .append("%는 ") 42 | .append("원") 43 | .insert(13, 5) 44 | .toString(); 45 | System.out.println(builtString); 46 | } 47 | ``` 48 | 49 | #### 예제 50 | 51 | - 생성자로 받는 경우 52 | 53 | ```java 54 | DecideLunch(int price, int distance, float meanStarScore, String restaurantName, String menuName) { 55 | this.price = price; 56 | this.distance = distance; 57 | this.meanStarScore = meanStarScore; 58 | this.restaurantName = restaurantName; 59 | this.menuName = menuName; 60 | } 61 | ``` 62 | - `빌더` 패턴 적용할 경우 63 | 64 | ```java 65 | class DecideLunchWithBuilder { 66 | private int price; 67 | private int distance; 68 | private float meanStarScore; 69 | private String restaurantName; 70 | private String menuName; 71 | 72 | static class Builder { 73 | // 필수 매개변수 74 | private final int price; 75 | private final int distance; 76 | 77 | // 선택 매개변수 78 | private float meanStarScore; 79 | private String restaurantName; 80 | private String menuName; 81 | 82 | Builder(int price, int distance) { 83 | this.price = price; 84 | this.distance = distance; 85 | } 86 | 87 | Builder meanStarScore(float meanStarScore) { 88 | this.meanStarScore = meanStarScore; 89 | return this; 90 | } 91 | 92 | Builder restaurantName(String restaurantName) { 93 | this.restaurantName = restaurantName; 94 | return this; 95 | } 96 | 97 | Builder menuName(String menuName) { 98 | this.menuName = menuName; 99 | return this; 100 | } 101 | 102 | DecideLunchWithBuilder build() { 103 | return new DecideLunchWithBuilder(this); 104 | } 105 | } 106 | 107 | private DecideLunchWithBuilder(Builder builder) { 108 | price = builder.price; 109 | distance = builder.distance; 110 | meanStarScore = builder.meanStarScore; 111 | restaurantName = builder.restaurantName; 112 | menuName = builder.menuName; 113 | } 114 | } 115 | ``` 116 | 117 | - 사용 118 | 119 | ```java 120 | public static void main(String[] args) { 121 | DecideLunchWithBuilder.Builder builder = new DecideLunchWithBuilder.Builder(18000, 3); 122 | DecideLunchWithBuilder decideLunchWithBuilder = 123 | builder 124 | .meanStarScore(4.3f) 125 | .restaurantName("우아한 식당") 126 | .menuName("치킨") 127 | .build(); 128 | } 129 | ``` 130 | 131 | ### 빌더의 장점 132 | 133 | - 만들기 복잡한 객체를 순차적으로 만들 수 있다. 134 | - 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있다. 135 | - 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수도 있다. 136 | - 불완전한 객체를 사용하지 못하도록 방지할 수 있다. 137 | 138 | ### 빌더의 단점 139 | 140 | - 원하는 객체를 만들려면 빌더부터 만들어야 한다. 141 | - 구조가 복잡해진다. (Trade-off) 142 | 143 | ### 참고 144 | 145 | - [https://www.inflearn.com/course/디자인-패턴/lecture/92861?tab=curriculum&volume=1.00](https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/lecture/92861?tab=curriculum&volume=1.00) -------------------------------------------------------------------------------- /handbook/02/03.Enum(열거타입) 방식, 또는 PRIVATE 생성자로서 인스턴스가 오직 하나뿐인 싱글턴임을 보증하라.md: -------------------------------------------------------------------------------- 1 | ## 싱글턴 패턴이란? 2 | 3 | **싱글턴(Singleton)** : 인스턴스를 오직 하나만 생성할 수 있는 클래스 4 | > ex) 함수(무상태 객체), 설계상 유일해야 하는 시스템 컴포넌트 5 | 6 | - **장점** 7 | - 고정된 메모리 영역을 얻으면서 한 번의 new로 인스턴스를 공유하기 때문에 메모리 낭비를 방지할 수 있다. 8 | - 두 번째 사용부터는 객체 로딩 시간이 줄어들어 성능이 좋아진다. 9 | - **단점** 10 | - 싱글턴 인스턴스가 너무 많은 일을 하면 인스턴스의 간의 결합도가 높아진다. (OCP(Open-Closed Principle, 개방 폐쇄 원칙) 의 원칙에 위배 ) 11 | - 디버깅이 어려움이 있다. 12 | - 테스트 코드의 작성의 어려움이 있다. 13 | 14 | ## 싱글턴을 만드는 방법 15 | 16 | ### 1. public static final 필드 방식 17 | 18 | : 싱글턴 인스턴스를 public static final 필드로 만들고, 생성자를 private 하게 지정하여 외부에서 기본 생성자를 생성할 수 없도록 만드는 방식. 19 | 20 | **장점 :** 싱글턴임이 API에 명백히 드러남, 간결함 21 | 22 | 다만, **Java Reflection API** 를 이용하면 싱글톤이더라도 객체를 생성해낼 수 있다. 23 | 24 | → 이를 방어하기 위해 방어코드를 추가로 작성한다. 25 | 26 | ```java 27 | class Car { 28 | public static final Car INSTANCE = new Car (); 29 | private Car() { 30 | // private한 생성자에서 두번째 객체가 만들어지는 것을 방지. 31 | if(INSTANCE != null){ 32 | throw new RuntimeException("이미 생성된 싱글톤 객체가 존재합니다.") 33 | } 34 | } 35 | } 36 | 37 | ... 38 | 39 | @Test 40 | @DisplayName("두 객체가 같은 인스턴스를 참조함") 41 | public void CarSameTest(){ 42 | // Car item1 = new Car(); 43 | Car item2 = Car.INSTANCE; 44 | Car item3 = Car.INSTANCE; 45 | assertThat(item2).isEqualTo(item3); 46 | } 47 | 48 | // 해당 테스트에서는 기본적으로 두 객체가 같다고 나온다. 49 | ``` 50 | 51 | ### 2. 정적 팩터리 방식 52 | 53 | : 아이템 1의 정적 팩터리 메서드를 public하게 만들어 싱글톤을 반환한다. 54 | 55 | - **장점** 56 | - API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다. 57 | - 정적 팩터리를 제네릭 싱글턴 팩터리로 만들어 타입에 유연하게 대처할 수 있다. 58 | - 정적팩터리의 메서드 참조를 공급자로 사용할 수 있다. 59 | 60 | *→ 다만, 이러한 장점들이 굳이 필요하지 않다면, public 필드 방식이 좋다.* 61 | 62 | ```java 63 | class Car { 64 | public static final Car INSTANCE = new Car (); 65 | private Car () { 66 | if(INSTANCE != null){ 67 | throw new RuntimeException("이미 생성된 싱글톤 객체가 존재합니다."); 68 | } 69 | } 70 | // 정적 팩터리 방식의 싱글턴. 71 | public static Car getInstance(){ 72 | return INSTANCE; 73 | } 74 | } 75 | 76 | @Test 77 | @DisplayName("인스턴스가 전체 시스템에서 하나뿐임이 보장된다.") 78 | public void singleton() { 79 | Car instance1 = Car.getInstance(); 80 | Car instance2 = Car.getInstance(); 81 | 82 | assertThat(instance1).isEqualTo(instance2); 83 | } 84 | ``` 85 | 86 | ### 3. Enum 방식- 바람직한 방법! 87 | 88 | : public 필드 방식과 비슷하지만, 더 간결하며, 제2의 인스턴스가 만들어지는 것을 완벽히 막을 수 있다. 89 | 90 | 대부분의 상황에서는 원소가 하나뿐인 Enum 타입이 싱글턴을 만드는 가장 좋은 방식이다. 91 | 92 | ```java 93 | public enum Car{ 94 | INSTANCE; 95 | 96 | } 97 | ``` -------------------------------------------------------------------------------- /handbook/02/04.인스턴스화를 막으려거든 private 생성자를 사용하라.md: -------------------------------------------------------------------------------- 1 | ## 인스턴스화를 막아야 하는 상황은 언제일까요? 2 | 가끔 정적 필드와 정적 메소드만을 갖고 있는 클래스가 필요한 상황이 있습니다. 이러한 유틸리티 클래스는 인스턴스로 사용하기 위해 생성한 것은 아닙니다. 3 | 4 | 그런데, 이러한 의도를 어떻게 코드로 나타낼 수 있을까요? 5 | 6 | 생성자를 명시하지 않으면, 컴파일러는 자동으로 **기본 생성자**를 생성하게 됩니다. 7 | 8 | ```java 9 | public class UtilityClass { 10 | 11 | //기본 생성자 12 | public UtilityClass() { 13 | } 14 | } 15 | ``` 16 | 17 | 기본 생성자의 접근 제어자는 public이죠. 그렇다면 언제든지 외부에서 해당 클래스 인스턴스를 만들 가능성이 있습니다. 18 | 19 | 20 | ## 인스턴스화를 막을 방법은 무엇일까요? 21 | - 여기서 추가로, ‘인스턴스를 안 만들려면, 추상 클래스로 만들면 되지 않을까?’ 라고 생각할 수 있지만, 추상 클래스로는 인스턴스를 막을 수 없습니다. 22 | 23 | → 하위 클래스를 생성하여 인스턴스화할 수 있기 때문입니다. 24 | 25 | → 추상 클래스를 상속하여 쓰라는 의미로 이해할 수 있습니다. → 더 큰 문제! 26 | 27 | 28 | 올바른 해결 방법은 private 생성자를 생성하는 것입니다. 29 | 30 | ```java 31 | public class UtilityClass { 32 | 33 | private UtilityClass() { 34 | } 35 | } 36 | ``` 37 | 38 | 그렇다면 외부에서 새로운 인스턴스를 생성하지 못하니, 클래스의 인스턴스 화를 막을 수가 있습니다. 39 | 40 | 추가적으로 상속도 막을 수 있습니다. (생성자가 private이기 때문에, 상위 클래스의 생성자에 접근할 수가 없습니다.) 41 | 42 | 43 | ### **Reference** 44 | 45 | **조슈아 블로크, 이펙티브 자바 Effective Java 3/E, 2018** 46 | 47 | [https://parkadd.tistory.com/74](https://parkadd.tistory.com/74) -------------------------------------------------------------------------------- /handbook/02/05.자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - **의존성 주입의 필요성** 4 | 5 | ```java 6 | public class Car { 7 | 8 | } 9 | ``` 10 | 11 | ```java 12 | public class Racing { 13 | private Car car; 14 | 15 | public Racing(){ 16 | this.car = new Car(); 17 | } 18 | } 19 | ``` 20 | 21 | - Car와 Racing 클래스가 강하게 결합되어 있다. 22 | 23 | IF) Car가 아닌 Bike가 Racing에 참여한다면? ⇒ 유연성이 떨어진다! 24 | 25 | - 객체가 아니라 클래스 사이에 관계가 맺어져 있다. 26 | 27 | Racing에 참여할 Vehicle에 대한 관심이 분리되지 않았기 때문! ⇒ 어떠한 객체를 사용할지에 대한 책임을 넘겨버리자! (IoC) 28 | 29 | - **해결 방안** 30 | 31 | ```java 32 | // 다형성을 위한 인터페이스 구현 33 | 34 | public interface Vehicle { 35 | 36 | } 37 | ``` 38 | 39 | ```java 40 | public class Car implements Vehicle { 41 | 42 | } 43 | ``` 44 | 45 | ```java 46 | public class Racing { 47 | 48 | private Vehicle vehicle; 49 | 50 | public Racing(Vehicle vehicle){ 51 | this.vehicle = vehicle; 52 | } 53 | } 54 | ``` 55 | 56 | ```java 57 | // DI Container에서 58 | 59 | Car car = new Car(); // Bean 생성 60 | 61 | Racing racing = new Racing(car); // 의존성 주입 62 | ``` 63 | 64 | - **의존성 주입(DI) 이란?** 65 | - 객체 사이의 관계를 외부에서 결정 66 | - 인터페이스를 통해 클래스 간의 의존성을 없애고 어플리케이션 실행 시점에 객체를 연결 67 | - 클래스가 하나 이상의 자원에 의존하고, 사용하는 자원이 클래스 동작에 영향을 준다면 DI를 사용하는 것이 유리! 68 | 69 | - **DI의 장단점** 70 | - 장점: 유연성, 재사용성, 테스트 용이성 71 | - 단점: 코드의 가독성을 저해할 수 있다. ⇒ Spring 같은 DI Framework 사용하면 해결! 72 | - **참고** 73 | - [https://velog.io/@sc_shin/이펙티브-자바-아이템-5.-자원을-직접-명시하지-말고-의존-객체-주입을-사용하라](https://velog.io/@sc_shin/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C-5.-%EC%9E%90%EC%9B%90%EC%9D%84-%EC%A7%81%EC%A0%91-%EB%AA%85%EC%8B%9C%ED%95%98%EC%A7%80-%EB%A7%90%EA%B3%A0-%EC%9D%98%EC%A1%B4-%EA%B0%9D%EC%B2%B4-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC) 74 | - [https://mangkyu.tistory.com/151](https://mangkyu.tistory.com/151) 75 | -------------------------------------------------------------------------------- /handbook/02/06.불필요한 객체 생성을 피하라.md: -------------------------------------------------------------------------------- 1 | # 불필요한 객체 생성을 피하라 2 | 3 | ## String 을 통해 알아보는 극단적인 불필요한 객체 생성 예시 4 | 5 | > 선수 지식 : 자바는 문자열 리터럴 (`””`) 이 동일하다면, String 객체를 공유한다. 6 | 7 | ```java 8 | String s1 = new String("hello java"); 9 | String s2 = new String("hello java"); 10 | // 같은 문자열을 담고 있지만, 객체가 두개 생성된다. 11 | // s1 와 s2 는 서로 다른 String 객체를 참조한다. 12 | 13 | String s3 = "hello java"; 14 | String s4 = "hello java"; 15 | // Heap 영역에 String 객체가 하나만 생성되고, s3 와 s4 는 동일한 String 객체를 참조한다. 16 | ``` 17 | 18 | ## 불필요한 객체 생성을 피하기 위한 정적 팩토리 메소드 사용 19 | 20 | [아이템1](https://github.com/woowacourse-study/2022-daily-effective-java/blob/main/handbook/02/01.%EC%A0%95%EC%A0%81%20%ED%8C%A9%ED%86%A0%EB%A6%AC%20%EB%A9%94%EC%84%9C%EB%93%9C%EB%A1%9C%20%EC%83%9D%EC%84%B1%20%EB%AA%A9%EC%A0%81%EC%97%90%20%EB%A7%9E%EB%8A%94%20%EA%B0%9D%EC%B2%B4%EB%A5%BC%20%EC%83%9D%EC%84%B1%ED%95%98%EB%9D%BC.md)의 정적 팩토리 메소드에서 이미 등장한 `Boolean` 클래스를 통한 불필요한 객체 생성 피하기. 21 | 22 | ```java 23 | Boolean b = Boolean.valueOf(true); 24 | 25 | @HotSpotIntrinsicCandidate 26 | public static Boolean valueOf(boolean b) { 27 | return (b ? TRUE : FALSE); 28 | } 29 | ``` 30 | 31 | ## 생성 비용이 아주 비싼 객체는 캐싱을 통해 재사용하자 32 | 33 | 아래 코드는 `String` 클래스의 `matches` 메소드를 사용하여 문자열을 정규표현식으로 검사한 예제. 34 | 35 | `matches` 메소드는 `Pattern` 인스턴스를 생성하고, 곧바로 버린다. 즉, **반복해서 사용하기 부적절.** 36 | 37 | ```java 38 | static boolean isRomanNumeralSlow(String s) { 39 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" 40 | + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 41 | } 42 | ``` 43 | 44 | 직접 재사용 가능한 `Pattern` 인스턴스를 만들고 **캐싱**하여 최적화 해보자. 45 | 46 | ```java 47 | public class RomanNumerals { 48 | private static final Pattern ROMAN = Pattern.compile( 49 | "^(?=.)M&(C[MD]|D?C{0,3})"+"(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 50 | 51 | static boolean isRomanNumeral(String s) { 52 | return ROMAN.matcher(s).matches(); 53 | } 54 | } 55 | ``` 56 | 57 | > `Pattern.compile` 은 주어진 정규표현식을 통해 `Pattern` 인스턴스를 생성한다. 58 | 59 | 위와 같이 **생성 비용이 비싼 객체**는 **한번만 생성하고 재사용**하는 방식으로 성능 향상이 가능하다. 코드도 읽기 쉽고, 명확해졌음. 60 | 61 | > (참고) 위 예제에 길이가 8인 문자열을 입력했을 때, 약 6.5배정도 성능이 향상되었다고 함. 62 | 63 | ## 어댑터(뷰)의 경우 64 | 65 | > 책에서는 어댑터 패턴을 통해 이를 설명하는데, 어댑터 패턴에는 익숙치 않아서 직관적으로 이해한 부분까지만 서술해보겠습니다 🥲  잘 알고 계시는 분은 코멘트 부탁드립니다 ㅎㅎ.. 66 | 67 | `Map` 클래스의 `keySet` 은 Map 객체의 키를 전부 담은 Set 을 반환한다. (이 Set 을 Map 의 View 라고 한다) 68 | 69 | ```java 70 | Map map = new HashMap<>(); 71 | 72 | map.put(1, "hello"); 73 | map.put(2, "effective"); 74 | map.put(3, "java"); 75 | map.put(4, "3th"); 76 | map.put(5, "edition"); 77 | 78 | System.out.println(map); 79 | // {1=hello, 2=effective, 3=java, 4=3th, 5=edition} 80 | System.out.println(map.keySet()); 81 | // [1, 2, 3, 4, 5] 82 | ``` 83 | 84 | `keySet` 은 항상 같은 객체를 반환한다. 즉 아래의 코드는 `true` 이다. 85 | 86 | ```java 87 | Set set1 = map.keySet(); 88 | Set set2 = map.keySet(); 89 | System.out.println(set1 == set2); // true 90 | ``` 91 | 92 | 즉, `set1` 을 수정하면, `set2` 도 변하게 된다. 아래와 같다. 93 | 94 | ```java 95 | set1.remove("hello"); 96 | System.out.println(set1.size() == set2.size()); // true 97 | ``` 98 | 99 | 즉, 굳이 `keySet` 으로 여러개의 인스턴스를 만들어서 오는 이득이 존재하지 않는다. 100 | 101 | ### 박싱된 기본타입 보다는 기본 타입을 사용하고, 의도치 않은 오토박싱 (Auto Boxing) 을 주의하자 102 | 103 | > 박싱된 기본타입이란? 기본 타입을 내부에 갖고있는 객체. 예를들어 `int` 의 박싱된 기본타입은 `Integer`. 포장(Wrapper) 클래스 라고도 함. 104 | 105 | **오토박싱?** 106 | 107 | (관련 아이템: [아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라](https://github.com/woowacourse-study/2022-daily-effective-java/blob/main/handbook/09/61.%20%EB%B0%95%EC%8B%B1%EB%90%9C%20%EA%B8%B0%EB%B3%B8%20%ED%83%80%EC%9E%85%EB%B3%B4%EB%8B%A4%EB%8A%94%20%EA%B8%B0%EB%B3%B8%20%ED%83%80%EC%9E%85%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC.md)) 108 | 109 | 포장 클래스 타입에 기본값이 대입 될 경우 오토 박싱이 발생되어 힙 영역에 포장 클래스 타입이 생성됨. 110 | 111 | ```java 112 | Integer number = 100; // 오토 박싱 113 | ``` 114 | 115 | **의도치 않은 오토박싱으로 인한 성능저하** 116 | 117 | 아래 메소드를 실행하면 `Long` 의 인스턴스는 몇 번 생성될까? (`Integer.MAX_VALUE` == 2^31) 118 | 119 | ```java 120 | long sum() { 121 | Long sum = 0L; 122 | for (long i = 0; i <= Integer.MAX_VALUE; i++) 123 | sum += i; 124 | return sum; 125 | } 126 | ``` 127 | 128 | `sum` 에 `i` 가 더해질 때 마다 `Long` 의 인스턴스는 새로 생성된다. 즉, 2^31 번이나 생성된다. 129 | `Long` 포장 클래스를 `long` 기본 타입으로 변경하면, 성능은 약 10배나 빨라진다. 130 | 131 | ## 객체 생성 자체가 비싸니 피해야 한다” 라고 오해하면 안된다. 132 | 133 | 최근 JVM 은 작은 객체의 생성, 회수는 큰 부담이 안된다. 프로그램의 명확성, 간결성, 기능을 위한 객체 추가 생성은 일반적으로 좋은 일이다. 134 | 따라서 **객체 풀 (Object Pool)** 을 생성하는 것은 하지 말자. 135 | 136 | > 객체 풀이란? 자주 생성되고 파괴되는 객체의 경우 생성, 파괴, 생성, 파괴 ... 로 인한 자원 낭비 (오버헤드) 를 막기 위해 일정량의 객체를 **미리 많~이 생성**해두고, 재사용 하는 방법. 주로 게임에서 쓰임. 137 | > 최근의 JVM 의 가비지 컬렉터는 상당히 최적화되어 가벼운 객체의 경우, 객체풀을 만드는 것이 더 느릴 수도 있음. 무엇보다 객체 풀에 생성되었지만, 정작 사용되지 않는 객체는 메모리 낭비임. 138 | 139 | ## 참고 140 | 141 | - [https://rockintuna.tistory.com/135](https://rockintuna.tistory.com/135) 142 | - [https://jithub.tistory.com/309](https://jithub.tistory.com/309) 143 | 144 | ## 기타 연관 아이템 145 | 146 | - [83. 지연 초기화는 신중히 사용하라](https://github.com/woowacourse-study/2022-daily-effective-java/blob/main/handbook/11/83.%20%EC%A7%80%EC%97%B0%20%EC%B4%88%EA%B8%B0%ED%99%94%EB%8A%94%20%EC%8B%A0%EC%A4%91%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC.md) 147 | - [67. 최적화는 신중히 하라](https://github.com/woowacourse-study/2022-daily-effective-java/blob/main/handbook/09/67.%20%EC%B5%9C%EC%A0%81%ED%99%94%EB%8A%94%20%EC%8B%A0%EC%A4%91%ED%9E%88%20%ED%95%98%EB%9D%BC.md) 148 | - [50. 적시에 방어적 복사본을 만들라](https://github.com/woowacourse-study/2022-daily-effective-java/blob/main/handbook/08/50.%20%EC%A0%81%EC%8B%9C%EC%97%90%20%EB%B0%A9%EC%96%B4%EC%A0%81%20%EB%B3%B5%EC%82%AC%EB%B3%B8%EC%9D%84%20%EB%A7%8C%EB%93%A4%EB%9D%BC.md) 149 | -------------------------------------------------------------------------------- /handbook/02/07.메모리 누수가 발생할 가능성이 있는 객체는 미리 처리하라.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2022-daily-effective-java/db845fe8ca44c4f0f034be323c65f08b61df091c/handbook/02/07.메모리 누수가 발생할 가능성이 있는 객체는 미리 처리하라.md -------------------------------------------------------------------------------- /handbook/02/08.finalizer와 cleaner 사용을 피하라. (그냥 쓰지마라).md: -------------------------------------------------------------------------------- 1 | # 08.finalizer와 cleaner ~~사용을 피하라~~. (그냥 쓰지마라) 2 | 3 | > 심지어 finalizer는 자바9부터 deprecated 되었다...그래도 일단 알아보자. 4 | 5 | **finalizer와 cleaner란?** 6 | ➡️ 자바가 제공하는 두가지 객체 소멸자이다. 7 | 8 | ## 왜 사용하면 안되는가? 9 | 1. 예측할 수 없다. 10 | - 수행하는 스레드가 즉시 수행된다는 보장이 없다. 11 | - 내가 원하는 타이밍에 작업을 절.대.로 수행할 수 없다. 12 | 2. 느리다. 13 | - `try-with-resource` 로 GC가 객체를 수거하기까지 12ns 소요 14 | - `finalizer`는 550ns 소요 15 | - `cleaner`는 500ns 소요 16 | 3. 일반적으로 불필요하다. 17 | - 우리에겐 GC와 `AutoCloseable`이 있다! 18 | 19 | ## 그럼 대체 어디에 사용되는가? 🤔 20 | - 안전망 역할 21 | > 즉시 호출된다는 보장이 없지만.. 언젠간 호출되니까...^^ 22 | - 정도의 생각을 갖고 사용해야 한다. 절대 객체의 소멸을 맡기면 안된다. 23 | - 네이티브 피어와 연결된 객체에서 사용 24 | - 네이티브 피어란, C/C++로 작성된 프로그램을 Java의 Native API를 통해 연관된 객체를 의미한다. 25 | - 결국 네이티브 피어는 자바 객체가 아니기때문에 GC가 자원을 회수할 수 없다. 26 | ➡️ 하지만 두가지 경우 모두 **성능 저하 감당**을 할수 있고, **해당 자원이 중요하지 않을 때** 사용하자. 27 | 28 | ## 결론 29 | - 사용하지 말자. 30 | - 네이티브 피어의 경우 성능 저하를 감당하겠다면, 도입을 고려해볼 수 있다. 31 | 32 | **참고** 33 | - finalize 코드로 이해해보기: https://www.baeldung.com/java-finalize 34 | -------------------------------------------------------------------------------- /handbook/02/09.try-finally 보다는 try-with-resources를 사용하라.md: -------------------------------------------------------------------------------- 1 | # 자원을 직접 닫을 경우 try-finally 보다는 try-with-resources를 사용하라 2 | `엘리` 3 | > 💡 `close` 메서드를 호출해 직접 닫아줘야하는 자원에 `try-with-resources`를 적용해 안전하게 닫아주자!! 4 | - 예) InputStream, OutputStream, java.sql.Connection 등 5 | - **자원을 닫지 않으면 성능 문제로 이어질 수 있다!!** 6 | 7 | ## try-finally (Java7 이전) 8 | ```java 9 | 변수1 = null; 10 | 변수2 = null; 11 | try { 12 | 변수1 = new 자원객체1(); 13 | 변수2 = new 자원객체2(); 14 | 변수1.작업(); 15 | 변수2.작업(); 16 | ... 17 | } finally { 18 | if (변수1 != null) 변수1.close(); 19 | if (변수2 != null) 변수2.close(); 20 | } 21 | ``` 22 | - 어떤 경우에도 자원을 반납하도록 구현하기위해 `fianlly`를 사용하게 된다. 23 | - `null` 여부를 추가로 검사해야한다. 24 | - `close` 해주는 걸 잊으면 그냥 끝.. 25 |
26 | 디버깅의 어려움 27 | 28 | - 예외는 `try 블록`과 `finally 블록` 모두에서 발생할 수 있다. 29 | - 이럴 경우 `fianlly 블록`에서 발생한 예외만 남게되고 `try 블록`에서 발생한 예외는 알 수 없게 된다. 30 | 31 | ![image](https://user-images.githubusercontent.com/45311765/158436879-f1560115-accb-4548-a2c4-03645842c2ab.png) 32 | ```java 33 | @Test 34 | void throwExceptionTryFinally() throws IOException { 35 | FileInputStream is = null; 36 | BufferedInputStream bis = null; 37 | try { 38 | is = new FileInputStream("/Users/ellie/MyData/woowacourse/file.txt"); 39 | bis = new BufferedInputStream(is); 40 | int data = -1; 41 | while ((data = bis.read()) != -1) { 42 | System.out.print((char) data); 43 | } 44 | throw new IOException("try에서 에러 발생"); 45 | } finally { 46 | if (is != null) { 47 | is.close(); 48 | } 49 | if (bis != null) { 50 | bis.close(); 51 | } 52 | throw new IOException("finally에서 에러 발생"); 53 | } 54 | } 55 | ``` 56 |
57 | 58 | ## try-with-resources (Java7 이후) 59 | ```java 60 | try (변수1 = new 자원객체1(); 61 | 변수2 = new 자원객체2()) { 62 | ... 63 | } 64 | ``` 65 | try-with-resources는 `try(...)`에서 선언된 객체들에 대해 `try`가 종료될 때 **자동으로 `close`를 호출하여 자원을 해제해주는 기능을 제공**한다. 66 | 67 | - Java7부터 제공한다. 68 | - 이때 해제하려는 자원의 객체에는 `AutoCloseable` 인터페이스가 구현되어 있어야한다. (아이템8 참고) 69 | 70 | 위의 경우가 아니라면 무조건 try-with-resources를 사용하자!! 71 | 72 | - 짧고 읽기 수월해진다. 73 | - `close`를 잊을 일이 없다. 74 | - 문제를 진단하기 좋아진다. 75 |
76 | 디버깅의 어려움 해소 77 | 78 | - `try 블록`과 `close`에서 모두 예외가 발생할 수 있지만 **try 블록에서 발생한 예외가 기록된다.** 79 | - 이때 close에서 발생한 예외는 숨겨지며, 스택 트레이스에 “Suppressed(숨겨짐)” 키워드로 출력된다. 80 | 81 | ![image](https://user-images.githubusercontent.com/45311765/158437955-41574ee1-af69-4bd1-876e-06ca64b13303.png) 82 | ```java 83 | public class CustomResource implements AutoCloseable { 84 | 85 | public void doSomething() { 86 | System.out.println("Do something..."); 87 | } 88 | 89 | @Override 90 | public void close() { 91 | System.out.println("CustomResource.close() is called"); 92 | throw new IllegalArgumentException("close에서 에러 발생"); 93 | } 94 | } 95 | ``` 96 | ```java 97 | @Test 98 | void throwExceptionTryWithResources() { 99 | try (CustomResource cr = new CustomResource()) { 100 | cr.doSomething(); 101 | throw new IOException("try에서 에러 발생"); 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } 105 | } 106 | ``` 107 |
108 | 109 | ** IntelliJ에서도 다음과 같이 try-fianlly로 자원을 해제할 경우 경고를 띄우며, try-with-rewources로의 변경을 권장하고 있다. 110 | ![image](https://user-images.githubusercontent.com/45311765/158437340-c0f5226c-e26e-4fc5-a500-0fe71848f38b.png) 111 | 112 | ## Ref. 113 | - https://devlog-wjdrbs96.tistory.com/116 114 | - https://codechacha.com/ko/java-try-with-resources/ 115 | -------------------------------------------------------------------------------- /handbook/03/11.equals 를 재정의 하려거든 hashCode 도 재정의하라.md: -------------------------------------------------------------------------------- 1 | # equals 를 재정의 하려거든 hashCode 도 재정의하라 2 | 3 | - 핵심 : equals 를 재정의한 클래스는 모두 hashCode 도 재정의해야한다. 4 | - 이유 : 만약 재정의 해주지 않는다면, 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다는 해시코드 규약을 위반하게 되어, HashMap 이나 HashSet 과 같이 hashCode 를 활용하는 클래스에서 문제가 생길 수 있기 때문이다. 5 | - 클래스에서 equals 를 재정의 한다는 것은 일반적으로 동일성 보다 동등성으로 객체를 비교하고자 함을 의미한다. 6 | - equals 와 함께 hashCode 를 재정의 해주지 않으면 논리적으로 같은 객체이지만 다른 hashCode 를 가지게 되므로 hashCode 를 활용하는 클래스에서 문제가 생길 수 있습니다. 7 | - 같다고 정의한 인스턴스는 반드시 같은 해시코드가 나와야하고 서로 다른 인스턴스는 가능한 서로 다른 해시코드가 나와야한다. 8 | 9 | ```java 10 | public class Rectangular { 11 | 12 | int width; 13 | int length; 14 | 15 | public Rectangular(int width, int length) { 16 | this.width = width; 17 | this.length = length; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) { 23 | return true; 24 | } 25 | if (o == null || getClass() != o.getClass()) { 26 | return false; 27 | } 28 | Rectangular that = (Rectangular) o; 29 | return width == that.width && length == that.length; 30 | } 31 | } 32 | ``` 33 | 34 | ```java 35 | public static void main(String[] args) { 36 | Rectangular rec1 = new Rectangular(20, 30); 37 | Rectangular rec2 = new Rectangular(20, 30); 38 | System.out.println(rec1.hashCode()); //1975358023 39 | System.out.println(rec2.hashCode()); //2101440631 40 | } 41 | ``` 42 | 43 | - 좋은 hashCode() 메서드를 만드는 법 44 | - 같다고 정의한 인스턴스는 반드시 같은 해시코드가 나와야하고 서로 다른 인스턴스는 가능한 서로 다른 해시코드가 나와야한다. 45 | - equals 를 정의할 때 사용했던 필드를 핵심 필드라고 하고, 해시코드를 생성 할 때 핵심 필드만 사용해서 조합 하는 것이 제일 좋다. 46 | - 핵심 필드 각각의 해시코드 값들을 연산하여 객체의 해시코드를 만든다. 47 | - 연산은 단순하면서도 연산 순서를 이용해서 같은 해시코드가 나올 가능성을 낮추는 것이 좋다. 48 | 49 | ```java 50 | @Override 51 | public int hashCode() { 52 | int result = Integer.hashCode(width); 53 | result = 31 * result + Integer.hashCode(length); 54 | return result; 55 | } 56 | ``` 57 | 58 | ```java 59 | public static void main(String[] args) { 60 | Rectangular rec1 = new Rectangular(20, 30); 61 | Rectangular rec2 = new Rectangular(20, 30); 62 | System.out.println(rec1.hashCode()); //650 63 | System.out.println(rec2.hashCode()); //650 64 | } 65 | ``` 66 | 67 | ```java 68 | @Override 69 | public int hashCode() { 70 | return Objects.hash(width, length); 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /handbook/03/12.유용한 정보를 가지도록 toString을 재정의하자.md: -------------------------------------------------------------------------------- 1 | # 12. 유용한 정보를 가지도록 toString을 재정의하자 2 | 3 | ## `toString` 이란 4 | 5 | - 자바에서 가장 최상위 클래스인 `Object` 클래스의 메서드 6 | 7 | ![](https://user-images.githubusercontent.com/47477359/156315493-41029934-dcaf-48b7-9695-0f5f8c059852.png) 8 | 9 | - `toString` 메서드는 객체의 문자 정보 즉, **객체를 문자열로 표현한 값**을 리턴함 10 | 11 | ## 재정의해야 하는 이유 12 | 13 | - `Object`의 `toString` 메서드 리턴값은 **클래스명@16진수해시코드**로 구성되어 별 값어치가 없는 정보가 반환됨 14 | - 디버깅을 할 때나 `println` 구문에 객체를 넘길 때 등 자동으로 `toString` 이 호출됨 15 | - 만약 `toString` 을 재정의하지 않는다면 쓸데없는 정보가 출력되기 때문에 **간결하고 유용한 정보를 리턴하도록 재정의해야 함** 16 | 17 | ```java 18 | public class WinningLotto { 19 | private Lotto lotto; 20 | private LottoNumber lottoNumber; 21 | } 22 | ``` 23 | 24 | ### 재정의하지 않은 경우 25 | 26 | ```java 27 | System.out.println(winningLotto); 28 | 출력: WinningLotto@55b0dcab 29 | ``` 30 | 31 | - 그다지 쓸모가 없는 메시지가 출력됨 32 | 33 | ### 적절하게 재정의한 경우 34 | 35 | ```java 36 | @Override 37 | public String toString() { 38 | return "당첨 번호{" + 39 | "로또 번호=" + lotto + 40 | ", 보너스 번호=" + bonus + 41 | "}"; 42 | } 43 | 출력: 당첨 번호{로또 번호=[1, 2, 3, 4, 5, 6], 보너스 번호=7} 44 | ``` 45 | 46 | - 재정의하지 않은 경우보다 훨씬 유용한 정보가 출력됨 47 | 48 | ## 지켜야할 점 49 | 50 | - 주요 정보를 모두 반환하자 51 | - `toString` 을 재정의할 때 가능한 객체 스스로를 완벽히 설명하는 문자열이 될 수 있도록 주요 정보를 모두 반환하는 것이 좋음 52 | - 만약 정확하게 설명이 안된다면 요약 정보를 담아야 함 53 | - `로또 목록(총 500000000개)` 54 | - `toString` 에 포함된 정보를 얻어올 수 있도록 API를 제공하자 55 | - 위의 예시에서는 로또 번호와 보너스 번호용 접근자를 제공해야 함 56 | - 그렇지 않으면 이 정보가 필요한 프로그래머는 `toString` 의 반환값을 파싱해서 사용해야 함 57 | - 쓸데 없는 연산이 추가됨 58 | 59 | ## `toString` 을 제공하지 않아도 되는 경우 60 | - 정적 유틸리티 클래스 61 | - 정적이기 때문에 객체의 상태 정보를 담을 필요가 없음 62 | - 따라서 `toString` 을 제공하지 않아도 됨 63 | - 열거 타입 64 | - 자바에서 이미 완벽한 `toString` 을 제공하기 때문에 굳이 우리가 만들지 않아도 됨 65 | -------------------------------------------------------------------------------- /handbook/03/13.clone 재정의는 주의해서 진행.md: -------------------------------------------------------------------------------- 1 | ### Cloneable 2 | 3 | - **복제해도 되는 클래스**임을 명시 4 | - 구현(implements)하지 않고 `clone()` 메소드 사용 시 `CloneNotSupportedException` 5 | - 구현 후 `clone()` 호출 시 객체의 필드를 하나하나 복사한 객체 반환 6 | 7 | ### Clone 8 | ### 전제조건 9 | 10 | --- 11 | 12 | 상위 클래스가 **제대로 동작하는 clone 메서드**를 가지며 **Cloneable을 구현**해줄 때 13 | 14 | ### 모든 필드가 기본 타입 / 불변 타입 15 | 16 | --- 17 | 18 | - 상위 객체의 `clone() 메서드`를 호출 19 | - 바로 원본의 복사본을 얻을 수 있다. 20 | 21 | ### 오버라이딩 22 | 23 | - `리턴 타입`이 `부모 메소드의 리턴 타입의 자식`인 경우 리턴 타입을 변경 가능하다. 24 | - Object에서는 Object 타입을 리턴해 준다. 25 | - Crew 클래스에서는 편의를 위해 Crew 타입을 리턴하도록 변경 26 | - 부모 메소드의 `접근제한자를 확장 가능`하다. 27 | - Object에서는 protected로 선언되어 있다. 28 | - Crew 클래스에서는 clone()를 모두 사용할 수 있도록 public 제한자로 변경 29 | 30 | ```java 31 | class Crew implements Cloneable{ 32 | String course; 33 | String name; 34 | 35 | public Crew(String course, String name) { 36 | this.course = course; 37 | this.name = name; 38 | } 39 | 40 | @Override 41 | public Crew clone() { 42 | try { 43 | return (Crew)super.clone(); 44 | } catch (CloneNotSupportedException e) { 45 | e.printStackTrace(); 46 | return null; 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | ### 참조 필드가 존재하는 경우 53 | 54 | - **문제점** 55 | 56 | Clone()의 결과로 `같은 객체를 참조`하게 되어 문제 발생 57 | 58 | - **해결 방안** 59 | - 상위 객체의 `clone()` 메서드를 호출 60 | - 참조 필드에 대해서 복사 진행 61 | 62 | ```java 63 | int[] numbers; 64 | 65 | ... 66 | 67 | @Override 68 | public Lotto clone() { 69 | try { 70 | Lotto clonedLotto = (Lotto)super.clone(); 71 | clonedLotto.numbers = numbers.clone(); 72 | return clonedLotto; 73 | } catch (CloneNotSupportedException e) { 74 | e.printStackTrace(); 75 | return null; 76 | } 77 | } 78 | ``` 79 | 80 | - **복사 생성자 / 복사 팩터리** 81 | - **자신과 같은 클래스의 인스턴스**를 인수로 받는 생성자 / 메서드 82 | 83 | ```java 84 | public Lotto() { 85 | numbers = new int[]{1, 2, 3, 4, 5, 6}; 86 | } 87 | 88 | // 복사 생성자 89 | public Lotto(Lotto lotto) { 90 | // 매개변수에 있는 lotto의 값들로 현재 인스턴스를 채워준다. 91 | super(); 92 | setLotto(lotto.numbers); 93 | } 94 | 95 | // 복사 팩터리 96 | public static Lotto newInstance(Lotto lotto) { 97 | // 매개변수에 있는 lotto의 값들로 채워진 인스턴스를 반환 98 | Lotto instance = new Lotto(); 99 | instance.setLotto(lotto.numbers); 100 | return instance; 101 | } 102 | 103 | public void setLotto(int[] targetNumbers) { 104 | for (int i = 0; i < 6; i++) { 105 | numbers[i] = targetNumbers[i]; 106 | } 107 | } 108 | ``` 109 | 110 | - **인터페이스 타입의 인스턴스를 인수로 받을 수 있다는 장점**이 있다. 111 | - Collection 변환에 사용된다. 112 | 113 | ```java 114 | public static void main(String[] args) { 115 | List list = List.of(1, 2, 3, 4); 116 | List arrayList = new ArrayList<>(list); // 복사 생성자 117 | } 118 | ``` 119 | 120 | 121 | ### 핵심 정리 122 | 123 | > 기본 원칙으로는 복제 기능은 생성자와 팩터리를 이용하는 것이 가장 좋으며, 배열의 경우는 예외로 clone 메서드가 가장 깔끔하다. 124 | 125 | 126 | **참고 자료** 127 | 128 | [https://velog.io/@roro/Java-Object-클래스-clone](https://velog.io/@roro/Java-Object-%ED%81%B4%EB%9E%98%EC%8A%A4-clone) 129 | 130 | [https://blog.geunho.dev/posts/java-deep-copy-shallow-copy/](https://blog.geunho.dev/posts/java-deep-copy-shallow-copy/) 131 | 132 | [https://bitsoul.tistory.com/55](https://bitsoul.tistory.com/55) 133 | -------------------------------------------------------------------------------- /handbook/03/14.Comparable을 구현할지 고려하라.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-study/2022-daily-effective-java/db845fe8ca44c4f0f034be323c65f08b61df091c/handbook/03/14.Comparable을 구현할지 고려하라.md -------------------------------------------------------------------------------- /handbook/04/15.클래스와 멤버의 접근 권한을 최소화하라.md: -------------------------------------------------------------------------------- 1 | ## 클래스와 멤버의 접근 권한을 최소화하라 2 | 3 | - 이유: 잘 설계된 컴포넌트란 내부 데이터와 구현을 외부로부터 잘 숨긴(캡슐화) 컴포넌트이다. 접근 권한을 최소화함으로써 외부로부터 내부를 더 잘 숨길 수 있다. 4 | 5 | **정보 은닉(캡슐화)의 장점은 아래와 같다** 6 | 7 | - 병렬 개발을 할 수 있다 → 개발 속도 up 8 | - 성능 최적화에 도움을 준다 → 캡슐화 자체가 성능에 도움을 주지는 않지만 개별 컴포넌트를 더욱 쉽게 수정할 수 있기 때문에 최적화에도 능하다 9 | - 재사용성을 높인다 10 | 11 | **몇가지 유용한 지침** 12 | 13 | - 패키지 외부에서 쓸 이유가 없다면 package-private(default)를 사용하자 14 | 15 | - public 클래스의 멤버는 상수(`public static final`)가 아닌 이상 private으로 선언한다 16 | - 한 클래스에서만 사용하는 package-private 톱레벨 클래스라면 이를 사용하는 클래스 안에 private static으로 중첩시켜 사용하자 17 | - 접근 제한자를 넓히기는 쉽지만 좁히기는 굉장히 어렵다는 것을 명심하자 18 | 19 | **보안 허점에 주의** 20 | 21 | 참조 타입의 경우 final로 선언해도 불변을 보장할 수 없다. 22 | 23 | ```java 24 | public static final Thing[] VALUES = {...}; 25 | ``` 26 |
27 | 28 | 이 경우 두 가지 해결책이 있다. 29 | 30 | 1. public 불변 리스트를 추가하는 방법 31 | 32 | ```java 33 | private static final Thing[] PRIVATE_VALUES = {...}; 34 | public static final Thing[] VALUES = 35 | Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES); 36 | ``` 37 | 38 | 2. 방어적복사 39 | 40 | ```java 41 | private static final Thing[] PRIVATE_VALUES = {...}; 42 | public static final Thing[] values() { 43 | return PRIVATE_VALUES.clone(); 44 | } 45 | ``` -------------------------------------------------------------------------------- /handbook/04/16.public 클래스의 필드를 외부로 직접 노출하지 마라.md: -------------------------------------------------------------------------------- 1 | ## public 클래스의 필드를 외부로 직접 노출하지 마라 2 | 3 | 아래와 같이 인스턴스 필드를 모아 놓기만 하는 퇴보한 클래스를 작성하지 마라 4 | 5 | ```java 6 | public class UsedCar { 7 | public int mileage; 8 | public int price; 9 | } 10 | ``` 11 | 12 | 이런 클래스는 13 | 14 | - 데이터 필드에 직접 접근할 수 있으므로, 캡슐화의 이점이 없다 15 | - API 수정 없이 내부 표현을 바꿀 수 없다 16 | - 외부에서 필드에 접근할 때, 부수 작업을 수행할 수 없다 17 | - 불변식을 보장할 수 없다 18 | 19 |
20 | 21 | 만약 public 클래스의 필드를 아래와 같이 불변으로 만든다면, 불변식을 보장할 수는 있게 된다 22 | 하지만 나머지 단점은 여전히 존재한다 23 | 24 | ```java 25 | public final class UsedCar { 26 | private static final int MILEAGE_MINIMUM = 0; 27 | private static final int PRICE_MINIMUM = 0; 28 | 29 | public final int mileage; 30 | public final int price; 31 | 32 | public UsedCar(int mileage, int price) { 33 | if (mileage < MILEAGE_MINIMUM || price < PRICE_MINIMUM) { 34 | throw new IllegalArgumentException("주행 거리와 가격은 0보다 커야 합니다."); 35 | } 36 | this.mileage = mileage; 37 | this.price = price; 38 | } 39 | } 40 | ``` 41 | 42 | 책에서는 이 대신 getter와 setter를 사용하는 것이 객체지향적이라는 말이 나온다 43 | 하지만 위의 좋지 않은 사례에 상대적으로 그렇다는 것이고, 이미 둘을 지양해야 할 이유를 미션 내에서 모두 체득하고 있다고 생각하기에, 44 | getter와 setter를 넣은 코드는 생략하겠다 45 | 46 |
47 | 48 | 별개로 [pakage-private](https://hyeon9mak.github.io/Java-dont-use-package-private/) 클래스, 혹은 private 중첩 클래스라면, 데이터 필드를 노출해도 괜찮다 49 | pakage-private의 경우에는 패키지 내부에서만 사용되기에, 외부의 수정 없이 데이터 표현 방식을 바꿀 수 있기 때문이다 50 | private 중첩 클래스는 범위가 더욱 좁아서, 외부 클래스까지만 수정 범위가 제한된다 51 | 52 | ```java 53 | public class UsedCar { 54 | private int mileage; 55 | private int price; 56 | private Fuel fuel; 57 | 58 | public UsedCar(int mileage, int price) { 59 | this.mileage = mileage; 60 | this.price = price; 61 | this.fuel = new Fuel(); 62 | } 63 | 64 | private void chargeFuel() { 65 | fuel.amount = 100; 66 | } 67 | 68 | private class Fuel { 69 | public int amount; 70 | public Fuel() { 71 | this.amount = 0; 72 | } 73 | } 74 | } 75 | ``` 76 | 77 |
78 | 79 | ## 참고한 글들 80 | 81 | https://taxol1203.github.io/effective%20java/EJ-Item16/ 82 | 83 |
84 | -------------------------------------------------------------------------------- /handbook/04/17.변경 가능성을 최소화하라. 불변 객체를 만들자.md: -------------------------------------------------------------------------------- 1 | ## `불변 클래스`🦴란? 2 | 인스턴스의 내부 값을 수정할 수 없는 클래스 3 | 4 | ## `불변 클래스`의 장점👍은? 5 | - 가변 클래스보다 설계하고 구현하고 사용하기 쉽다. 6 | - 오류가 생길 여지가 적고 훨씬 안전하다. 7 | ## `불변 클래스`로 만드는 방법🔨 8 | ### 객체의 상태를 변경하는 메서드를 제공하지 않는다. 9 | 10 | `Setter`... 등등 알죠? 11 | 12 | ### 클래스를 확장할 수 없도록 한다. 13 | 상속이 가능하면 하위 객체는 `오버라이딩`등 불변을 보장할 수 없게 된다. 14 |
15 | 상속하지 못하게 하는 방법 16 | 모든 생성자를 `private` 혹은 `package-private`으로 만들고, `public 정적 팩토리`를 제공한다. 17 | 18 | ```java 19 | private Yield(float yield) { 20 | this.yield = yield; 21 | } 22 | 23 | public static Yield calculate(LottoMoney lottoMoney, Long totalWinningMoney) { 24 | return new Yield(lottoMoney.divide(totalWinningMoney)); 25 | } 26 | ``` 27 |
28 | 29 | ### 모든 필드를 `final`로 선언한다. 30 | 설계자의 의도를 명확하게 드러내고 `멀티 쓰레드`에서도 안전할 수 있게 한다. 31 | 32 | ### 모든 필드를 `private`으로 선언한다. 33 | 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다. 34 | 35 | ### 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 36 | `가변 컴포넌트(배열, Collection 등)`를 가지는 필드가 하나라도 있다면 클라이언트에서 참조를 얻을 수 없도록 해야한다. `방어적 복사`를 수행하라. 37 | 38 | ### `함수형 프로그래밍`을 이용한다. 39 | 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴 40 | ```java 41 | class Car { 42 | private final int position; 43 | 44 | Car(int position) { 45 | this.position = position; 46 | } 47 | 48 | Car move() { 49 | return new Car(position + 1); 50 | } 51 | } 52 | ``` 53 | 54 | ##`불변 객체`의 장점👍은? 55 | ### 단순하다. 56 | 57 | 생성 시점의 상태를 파괴될 때까지 간직하므로 믿고 사용할 수 있다. 58 | 59 | ### 스레드 안전하여 동기화할 필요가 없다. 60 | 61 | 어떤 스레드도 다른 스레드에 영향을 줄 수 없으므로 안심하고 공유할 수 있다. 62 | 63 | ### 자주 사용되는 인스턴스를 `캐싱`하여 인스턴스를 중복 생성하지 않게 해주는 `정적 팩터리`를 제공할 수 있다. 64 | - 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 GC 비용이 줄어든다. 65 | - 방어적 복사도 필요 없으므로 `clone` 메서드나 `복사 생성자`를 제공하지 않는 게 좋다. 66 | 67 | ### 자유롭게 공유할 수 있으며, 불변 객체끼리는 내부 데이터를 공유할 수 있다. 68 | 69 | ```java 70 | public class BigInteger extends Number implements Comparable { 71 | final int signum; 72 | final int[] mag; 73 | 74 | public BigInteger negate() { 75 | return new BigInteger(this.mag, -this.signum); 76 | } 77 | ``` 78 | 79 | `mag`배열은 비록 변이지만 복사하지 않고 원본 인스턴스와 공유해도 된다. 그 결과 새로 만든 `BigInteger` 인스턴스도 원본 인스턴스가 가리키는 내부 배열을 그대로 가리킨다. 80 | 81 | ### 불변 객체를 구성요소로 사용하면 사용 객체도 불변을 유지하기 쉽다. 82 | 불변 객체로 이뤄진 객체라면 아무리 복잡해도 불변식을 유지하기 훨씬 쉽다. 불변 객체는 `맵의 키`와 `집합(Set)의 원소`로 쓰기에 좋다. 83 | 84 | ```java 85 | public class LottoResult { 86 | private final Map result; 87 | ``` 88 | 89 | ### `실패 원자성`을 제공한다. 90 | 91 | `실패 원자성(failure atomicity)`이란 메서드에서 예외가 발생한 후에도 그 객체는 여전히 호출 전과 같은 유효한 상태여야 한다는 성질이다. 불변 객체의 메서드는 내부 상태를 바꾸지 않으니 이 성질을 만족한다. 92 | 93 | ## `불변 객체`의 단점👎은? 94 | ### 값이 다르면 반드시 `독립된(식별자가 다른)` 객체로 만들어야 한다. 95 | 96 | 객체 생성 비용이 높고 상태가 다른 객체를 자주 만들어야 하는 객체라면 큰 비용이 들 수 있다. 97 | 98 | - `다단계 연산`을 제공하여 각 단계마다 객체를 생성하지 않도록 한다. 99 | - `StringBuilder` 100 | ### `불변 객체`로 만들기 어려운 상황😥이라면? 101 | - 변경할 수 있는 부분을 최소한으로 줄인다. 102 | 103 | 나머지 모두를 `final`로 선언한다. 다른 합당한 이유가 없다면 모든 필드는 `private final`이어야 한다. 104 | 105 | - `생성자`는 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다. -------------------------------------------------------------------------------- /handbook/04/18.상속보다는 컴포지션과 전달을 사용하자. 래퍼클래스가 더 강력하다.md: -------------------------------------------------------------------------------- 1 | ## 상속이란? 2 | 3 | 아래의 예시에 나온 Dog과 Animal의 관계가 바로 상속의 예시이다. 4 | 5 | ```java 6 | class Animal { 7 | String name; 8 | 9 | void setName(String name) { 10 | this.name = name; 11 | } 12 | } 13 | 14 | class Dog extends Animal { 15 | void sleep() { 16 | System.out.println(this.name+" zzz"); 17 | } 18 | } 19 | 20 | public class Sample { 21 | public static void main(String[] args) { 22 | Dog dog = new Dog(); 23 | dog.setName("poppy"); 24 | System.out.println(dog.name); // poppy 출력 25 | dog.sleep(); // poppy zzz 출력 26 | } 27 | } 28 | ``` 29 | 30 | Dog는 Animal에 포함되기 때문에, “Dog `is a` Animal”이라고 말할 수 있는 관계를 상속관계라고 이야기한다. 이는 코드를 재사용하고, 계층적인 관계를 구성하는 31 | 것에 있어서 효과적으로 사용된다. 32 | 33 | ## 구현상속 vs 인터페이스 상속 34 | 35 | **구현상속**: 다른 클래스를 추가로 확장하여 사용하는 의미. 36 | 37 | **인터페이스 상속** : 확장의 의미보단 인터페이스에 정의된 멤버들을 클래스에서 대신 구현하겠다는 의미. 38 | 39 | *→ 해당 아이템에서 이야기하는 ‘상속’은 구현 상속에 한정된다.* 40 | 41 | ## 컴포지션이란? 42 | 43 | 컴포지션 = 새로운 클래스를 만들어 기존 클래스가 새로운 클래스의 구성요소로 사용되는 설계. 44 | 45 | **새로운 클래스의 역할** 46 | 47 | - private 필드로 기존 클래스의 인스턴스 참조 48 | - 기존 클래스의 대응하는 메서드를 호출해 결과 반환 = 전달(forwarding) 49 | - 새 클래스의 메서드는 전달메서드라고 부른다. 50 | → 이는 내부 구현 방식에서 독립적으로 구성할 수 있다. 51 | 52 | ## 상속 vs 컴포지션 53 | 54 | - 상속은 캡슐화를 꺠트리고, 상위 클래스에 의존성 관계를 가지고 있기 때문에 상위클래스의 변화에 따라 하위클래스가 변경될 가능성이 있다. (OCP 규칙에 위반) 55 | - 메소드는 언제든지 재정의될 가능성이 있음 56 | - 다른 패키지의 구체클래스를 상속하는 일은 위험하고, 같은 패키지 내에서라도 문제를 야기할수 있다. 57 | - 보통 IS-A 관계일떄 상속을 사용하고, HAS-A 관계일때 컴포지션을 사용하라고 하지만 이는 잘못된 것( But 관점의 차이일뿐임 CAR IS-A AUTOMOBILE 이기도 58 | 하지만, CAR HAS-A ENGINE, WHEEL 관계이기도 함) 59 | 60 | 예시 61 | 62 | - 상속 코드 63 | ```java 64 | class Animal{ 65 | 66 | void eat(); 67 | } 68 | 69 | class Cat extends Animal{ 70 | void meow(); 71 | } 72 | 73 | class Dog extends Animal{ 74 | void bark(); 75 | } 76 | 77 | class Robot{ 78 | void move(); 79 | } 80 | 81 | class CleaningRobot extends Robot{ 82 | void clean(); 83 | } 84 | 85 | class KillingRobot extends Robot{ 86 | void kill(); 87 | } 88 | ``` 89 | 90 | 위와 같은 코드에서 만약 요구사항이 바뀌어 KillingRobotDog이 필요하게 되면 어떻게될까? 91 | 92 | → 상속은 객체의 특성을 단정짓기 때문에, 상속으로 구성된 구현은 요구사항의 변경에 유연하게 대처하지 못한다. 93 | 94 | 95 | - 컴포지션 코드 96 | 97 | 아래 코드는 컴포지션 관계로 해당 코드를 변경한 예시이다. 98 | 99 | ```java 100 | class Dog{ 101 | BarkingStrategy bark; 102 | EatingStrategy eat; 103 | } 104 | 105 | class ManyEatingDog{ 106 | BarkingStrategy bark; 107 | EatingStrategy eat;// eat 구현체를 많이 먹는 것으로 갈아낀다 ! 108 | } 109 | 110 | class Cat{ 111 | MeowingStrategy meow; 112 | EatingStrategy eat; 113 | } 114 | 115 | class CleaningRobot{ 116 | CleaningStrategy clean; 117 | MovingStrategy move; 118 | } 119 | 120 | class KillingRobot{ 121 | KillingStrategy kill; 122 | MovingStrategy move; 123 | } 124 | 125 | class KillingRobotDog { 126 | KillingStrategy kill; 127 | MovingStrategy move; 128 | BarkingStrategy bark; 129 | } 130 | ``` 131 | 132 | 행동을 하는 부분을 객체화시키고, 그 안의 Killing Strategy 같은 클래스 내부에서 구현하는 방식으로 진행하는 것이다. 133 | 134 | - 교재의 예시 135 | 136 | ```java 137 | public class InstrumentedHashSet extends ForwardingSet { 138 | 139 | //추가된 원소의 수 140 | private int addCount = 0; 141 | 142 | public InstrumentedHashSet(Set s){ 143 | super(s); 144 | } 145 | 146 | @Override 147 | public boolean add(E e) { 148 | addCount++; 149 | return super.add(e); 150 | } 151 | 152 | @Override 153 | public boolean addAll(Collection c) { 154 | addCount += c.size(); 155 | return super.addAll(c); 156 | } 157 | 158 | public int getAddCount() { 159 | return addCount; 160 | } 161 | 162 | } 163 | ``` 164 | 165 | ```java 166 | public class ForwardingSet implements Set { 167 | private final Set s; 168 | public ForwardingSet(Set s) { this.s= s;} 169 | 170 | public void clear() {s.clear();} 171 | public boolean contains(Object o) { return s.contains(o);} 172 | public boolean isEmpty() { return s.isEmpty();} 173 | public int size() { return s.size();} 174 | public Iterator iterator() { return s.iterator(); } 175 | public boolean add(E e) { return s.add(e); } 176 | public boolean addAll(Collection c) { return s.addAll(c); } 177 | 178 | 179 | } 180 | ``` 181 | 182 | ## 래퍼클래스 183 | 184 | 아래에서 set 인스턴스를 감싸고 있다는 뜻에서 바로 위에 제시된 교재 내의 예시인 InstrumentedSet 과 같은 클래스를 `래퍼 클래스`라고 하며, 185 | 이는 `데코레이터 패턴`이라고도 불린다. 186 | 187 | 이런 래퍼 클래스는 단점이 거의 없으나, callback (콜백) 프레임워크와 어울리지 않는다는 점만 주의하면 된다. 188 | 189 | ## 정리 190 | 191 | - 상속은 강력하지만 캡슐화를 해치고 + 내부 구현을 불필요하게 노출할 수 있으므로, 확실하게 `IS-A` 관계일 때에만 사용하자. 192 | - 컴포지션과 전달을 사용하자. 193 | - 래퍼 클래스는 충분히 강력한 성능을 가지고 있다. -------------------------------------------------------------------------------- /handbook/04/19.상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속은 금지하라.md: -------------------------------------------------------------------------------- 1 | # 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속은 금지하라 2 | 3 | # 📩 상속을 고려한 설계와 문서화 4 | 5 | ## 문서화하지 않은 외부 클래스 상속은 위험⚠️! 6 | 7 | - 프로그래머의 통제권 밖 8 | - 언제 변경될 지 모름 9 | 10 | ## 상속을 고려한 설계와 문서화👍란? 11 | 12 | - `재정의 가능 메서드`를 호출할 수 있는 '모든 상황' 문서화 13 | - API의 공개된 메서드에서 호출되는 `재정의 가능 메서드` 언급 14 | - 내부에서 어떻게 `재정의 가능 메서드`를 이용하는지 설명 15 | 16 | ## 재정의 가능 메서드 란? 17 | 18 | - `final`이 아님 19 | - `public`, `protected`, `default` 메서드 20 | - `static`이 아님 21 | - 즉 클래스 밖에서 접근 / 수정 가능한 모든 메서드 22 | 23 | ## 문서화 예시 24 | 25 | ```java 26 | public abstract class AbstractCollection implements Collection { 27 | /** 28 | * {@inheritDoc} 29 | * 30 | * @implSpec 31 | * This implementation iterates over the collection looking for the 32 | * specified element. If it finds the element, it removes the element 33 | * from the collection << using the iterator's remove method.>> 34 | * 35 | *

Note that this implementation throws an 36 | * {@code UnsupportedOperationException} <> 39 | * 40 | * @throws UnsupportedOperationException {@inheritDoc} 41 | * @throws ClassCastException {@inheritDoc} 42 | * @throws NullPointerException {@inheritDoc} 43 | */ 44 | public boolean remove(Object o) { 45 | Iterator it = iterator(); 46 | if (o == null) { 47 | while (it.hasNext()) { 48 | if (it.next() == null) { 49 | it.remove(); 50 | return true; 51 | } 52 | } 53 | } 54 | //... 55 | } 56 | } 57 | ``` 58 | 59 | - `@implSpec` 태그를 붙여주면 `javaDoc` 도구가 절 생성 60 | - `AbstractCollection.remove()` 안에서 `Iterator.remove()`를 사용 61 | - 재정의 가능 메서드가 주는 영향 및 주의사항을 문서화하기 62 | 63 | ```java 64 | public interface Iterator { 65 | default void remove() { 66 | throw new UnsupportedOperationException("remove"); 67 | } 68 | //... 69 | } 70 | ``` 71 | 72 | - `Iterator.remove()`는 `default` 접근제어자, 즉 `재정의 가능 메서드` 이다. 73 | 74 | # ⛏ 훅(hook) 75 | 76 | ## 캠슐화를 해치는 상속 77 | 78 | - 상속하면 필연적으로 내부 구현방식을 설명해야 함 79 | - 캠슐화를 해침ㅠㅠ 80 | 81 | ## 훅을 선별하자 82 | 83 | 1. 상속용 클래스 내부 동작 과정 중간에, 끼어들 수 있는 지점 `hook`를 만들자 84 | 2. 해당 지점을 `protected` 메서드로 공개 85 | 3. 드물게는 `protected` 필드로 공개 86 | 87 | ## 어떤 메서드를 `훅`으로 만들지? 88 | 89 | - 정답은 없다. 두뇌풀가동...! 90 | - 실제 하위클래스를 만들어 시험 91 | - `훅` 은 적어야 하지만, 너무 적으면 상속의 이점이 사라짐 92 | 93 | # 🔍 상속용 클래스 검증 94 | 95 | - 배포 전 반드시 검증!! 96 | 97 | ## 어떻게 검증하지? 98 | 99 | - 실제 하위클래스를 만들어 시험 100 | - 안 쓰이는 `protected` 는 `privated`로 변경 101 | - 검증을 위해 3개 정도의 하위클래스 생성하기 102 | - 하나 이상은 제 3자가 작성 103 | 104 | ## 추가제약 105 | 106 | ### 상속용 클래스 생성자가 `재정의 가능 메서드`를 호출해서는 안됨!! 107 | ```java 108 | public class Test { 109 | public Test() { 110 | overrideMe(); 111 | } 112 | 113 | public void overrideMe() { 114 | System.out.println("Test method"); 115 | } 116 | } 117 | 118 | 119 | 120 | public class SubTest extends Test{ 121 | private final Date date; 122 | 123 | public SubTest() { 124 | date = new Date(); 125 | } 126 | 127 | @Override 128 | public void overrideMe() { 129 | System.out.println(date); 130 | } 131 | 132 | public static void main(String[] args) { 133 | SubTest sub = new SubTest(); 134 | sub.overrideMe(); 135 | } 136 | } 137 | ``` 138 | ```java 139 | > null 140 | //subTest main 실행 141 | //원래는 data 값이 나오는 걸 의도했지만, null이 나옴 142 | ``` 143 | 1. 상위 클래스(Test)의 생성자가 하위 클래스(subTest)의 생성자보다 먼저 실행 144 | 2. 상위 클래스(Test) 에서 재정의한 메서드가 하위 클래스(subTest) 의 생성자보다 먼저 호출된다. 145 | 3. 프로그램 오작동 146 | 147 | ### `clone`, `readObject` 메서드가 `재정의 가능 메서드`를 호출해서는 안됨!! 148 | ### `Serializable`이 구현된 상속용 클래스 149 | - `readResolve`나 `writeReplace`를 메서드로 가질 시 150 | - 해당 메서드들을 `protected`로 선언해야 함...... 151 | 152 | # ⚖️ 결론 153 | 154 | - 클래스를 상속용으로 설계하려면 `엄청난 노력`이 들며, 제약이 많다!! 155 | - 일반적인 구체 클래스(final도 아니고 상속용도 아닌... 우리가 주로 쓰는 클래스)도 상속가능해 위험하다!! 156 | 157 | ## 상속용으로 설계하지 않은 클래스 : 상속을 금지🚫 하자! 158 | 159 | - 방법 1 : 클래스를 `final`로 선언 160 | - 방법 2 : 모든 생성자를 `package-private`선언 -> `public` 정적 팩토리 메서드(아이템 17) 사용 161 | - 헌치 픽 162 | - 방법 3 : 인터페이스 사용 163 | - 방법 4 : 래퍼 클래스 패턴(아이템 18) 사용 164 | 165 | ## 구체 클래스는요?😅 166 | 167 | - 표준 인터페이스 없이 상속을 금지하면 사용하기 불편해진다... 168 | - 방법 1 : 클래스 내부에서 `재정의 가능 메서드`를 사용하지 않고, 이를 문서화 169 | - 방법 2 : 래퍼 클래스 패턴(아이템 18) 사용 -------------------------------------------------------------------------------- /handbook/04/20.추상 클래스보다는 인터페이스를 우선하라.md: -------------------------------------------------------------------------------- 1 | # 20.추상 클래스보다는 인터페이스를 우선하라 2 | 3 | **인터페이스 vs. 추상 클래스** 4 | - 공통된 **목적** 5 | - Java의 다중 구현 메커니즘. 6 | - 인스턴스 메서드를 구현하여 제공 가능 7 | - **구현**에서 차이 8 | - 추상 클래스를 구현 ➡ 추상클래스의 하위 클래스. 단일 상속 9 | - 인터페이스를 구현 ➡ 같은 타입(계층 ❌). 다중 상속 10 | 11 | ## 인터페이스의 장점 🔍 12 | - 기존 클래스에서 새로운 인터페이스 쉽게 구현 가능 13 | - `implements`로 선언을 추가하고, 요구되는 메서드 구현 14 | - 추상 클래스를 기존 클래스에 추가하는 경우, 계층 구조에 대해 확인할 필요가 있음 15 | - 믹스인(mixin) 정의에 적합 16 | - `믹스인`: 클래스가 구현할 수 있는 타입. 믹스인을 구현한 클래스에서는 `주된 타입`외에도 특정 `선택적 행위` 제공 가능 17 | ```java 18 | public class Count implements Comparable{ 19 | 20 | @Override 21 | public int compareTo(Count o) { // 믹스인 22 | return 0; 23 | } 24 | 25 | public void increase() { // 주된 타입 26 | this.number++; 27 | } 28 | } 29 | ``` 30 | - 두개 이상의 추상 클래스로는 믹스인을 삽입하기 어려움 31 | - 계층구조가 없는 타입 프레임워크 생성 가능 32 | ```java 33 | public interface TikToker { 34 | Video dance(Music music); 35 | } 36 | 37 | public interface Youtuber { 38 | StreamService broadcast(); 39 | } 40 | 41 | public interface Influencer extends TikToker, Youtuber { // 유튜버+틱톡커인 인플루언서 타입 새롭게 정의! 42 | Video dance(Music music); 43 | StreamService broadcast(); 44 | } 45 | ``` 46 | - n개의 속성이 존재할 때, 추상클래스로 구현하는 경우 `2^n`만큼의 조합의 수 47 | - 래퍼 클래스 관용구와 궁합이 좋음 48 | - 기능 향상 시키는 안전하고 강력한 수단 49 | 50 | ### 인터페이스의 default 메서드 특징 정리 51 | - Java 8부터 제공 52 | - `@implSpec` 태그로 문서화하기 53 | - `equals`와 `hashCode`는 정의 불가 54 | - 인스턴스 필드 선언 불가 55 | - public이 아닌 정적 멤버 선언 불가(private 정적 메서드는 예외) 56 | 57 | ## 인터페이스 장점과 추상 클래스의 장점 모두 활용하기 58 | > 인터페이스를 사용하라는거 아니였나요? 왜 둘다 활용해야하죠??🧐 59 | 60 | ➡️ 복잡한 인터페이스의 경우, 매번 구현해야하는 수고를 덜 수 있기 때문!🤓 61 | - 인터페이스의 장점 + 추상 클래스의 장점(템플릿 메서드 패턴) 62 | - `인터페이스의 장점 활용하기`: 타입을 쉽게 정의, 디폴트 메서드 제공 63 | - `추상 클래스의 장점 활용하기`: 추상(골격 구현) 클래스에서 남은 메서드 중 필요한 메서드 구현 64 | - 네이밍 방법: Abstract+Interface명 (ex. AbstractCollection, AbstractSet) 65 | 66 | 67 | ### 골격 구현 방법(난이도 下) 68 | **1. 인터페이스에서 다른 메서드들의 구현에 사용되는 기반 메서드 선정하기** 69 | 기반 메서드는 골격 구현에서 추상 메서드가 된다. 70 | `Lecture`클래스를 잘 살펴보자... 71 | ```java 72 | public interface Lecture { 73 | public boolean equals(); 74 | public int getSize(); // isEmpty()를 구현할 때 사용될거 같다! 기반 메서드로 선정! 75 | public boolean isEmpty(); 76 | } 77 | ``` 78 | **2. 기반 메서드들을 사용해 직접 구현할 수 있는 메서드들을 모두 디폴트 메서드로 제공하기** 79 | ```java 80 | public interface Lecture { 81 | public boolean equals(); // (default 메서드 특징) equals와 hashCode는 default 메서드로 정의 불가하다는 점 잊지 말자 82 | public int getSize(); 83 | default boolean isEmpty() { // 기반 메서드인 getSize()를 사용해 디폴트 메서드로 변경 84 | return getSize() == 0; 85 | } 86 | } 87 | ``` 88 | **3. 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아있다면, 해당 인터페이스를 구현하는 골격 구현 클래스 생성** 89 | 필요하다면 public이 아닌 필드와 메서드를 추가할 수 있다. 90 | 반대로 인터페이스에서 모두 기반 메서드와 디폴트 메서드로 만들 수 있다면, 골격 구현을 할 필요는 없다. 91 | ```java 92 | public abstract class AbstractLecture implements Lecture{ // 클래스 명명 규칙 잊지말자 93 | @Override 94 | public boolean equals() { // 남은 메서드를 구현하자 95 | } 96 | } 97 | ``` 98 | 마지막으로 골격 구현은 기본적으로 상속이므로, 설계 및 문서화 지침을 모두 따라야 함을 잊지말자. 99 | -------------------------------------------------------------------------------- /handbook/04/21.인터페이스는 구현하는 쪽을 생각해 설계하라.md: -------------------------------------------------------------------------------- 1 | # 디폴트 메서드를 너무 믿지 말고, 인터페이스를 설계할 때는 세심한 주의를 기울여야 한다 2 | 3 | **Java8 이전** 4 | - 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 수 있는 방법이 없었다. 5 | - "현재 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다" 6 | - **요구사항 추가 -> 인터페이스에 메서드 추가 -> 모든 구현체 클래스 변경** 7 | 8 | **Java8 이후** 9 | 기존 인터페이스에 메서드를 추가할 수 있는 `디폴트 메서드`가 추가되었다. 10 | 11 | ## 핵심 12 | - 디폴트 메서드는 완전한 것이 아니다. 기존 클래스에 여러 문제를 야기할 수 있다. 13 | - 디폴트 메서드는 꼭 필요한 경우가 아니면 사용을 피해라. 14 | - 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다. 15 | - 인터페이스를 설계할 때는 추후 변경사항이 발생하지 않도록 릴리즈 전 많은 테스트 과정을 거쳐야한다. 16 | 17 | ## 디폴트 메서드(Defautl Method) 18 | 디폴트 메서드는 **인터페이스에 있는 구현 메서드**를 의미한다. 19 | - 메서드 앞에 `deault 에약어`가 붙는다. 20 | - `구현부 {}`가 있어야 한다. 21 | 22 | 다음은 List 인터페이스의 디폴트 메서드와 추상 메서드이다. 23 | ```java 24 | // 디폴트 메서드 25 | default void sort(Comparator c) { 26 | Object[] a = this.toArray(); 27 | Arrays.sort(a, (Comparator) c); 28 | ListIterator i = this.listIterator(); 29 | for (Object e : a) { 30 | i.next(); 31 | i.set((E) e); 32 | } 33 | } 34 | 35 | // 추상 메서드 36 | void clear(); 37 | ``` 38 | 39 | ## 디폴트 메서드.. 마냥 좋은 것이 아니다 40 | **장점** 41 | - 기존 추상 메서드와 다르게 인터페이스 안에서 디폴트 구현을 지정할 수 있다. 42 | - 디폴트 메서드를 재정의하지 않은 모든 클래스에서는 디폴트 구현을 사용하며, 재정의해 사용할 수도 있다. 43 | 44 | **단점** 45 | - 디폴트 메서드는 **구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 삽입될 뿐이므로 주의해야한다.** 46 | - Java8에서는 람다를 제공하기 위해 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드들을 추가하였다. 47 | - 코드 품질이 높고 범용적이라 대부분의 상황에서 잘 작동한다. 48 | - 하지만 모든 상황을 커버할 수는 없다. 49 | 50 | ### Java8의 Collection 인터페이스에 추가된 디폴트 메서드 51 | ```java 52 | public interface Collection extends Iterable { 53 | default boolean removeIf(Predicate filter) { 54 | Objects.requireNonNull(filter); 55 | boolean removed = false; 56 | final Iterator each = iterator(); 57 | while (each.hasNext()) { 58 | if (filter.test(each.next())) { 59 | each.remove(); 60 | removed = true; 61 | } 62 | } 63 | return removed; 64 | } 65 | } 66 | ``` 67 | `org.apache.commons.collections4.collection.SynchronizedCollection`와의 호환 문제 68 | - `SynchronizedCollection`은 동기화를 제공해 멀티 스레드 환경에서 안정성을 보장해준다. 69 | - 하지만 `removeIf()`를 재정의하고 있지 않다. 70 | - `removeIf()`는 동기화에 관해 아무것도 모르므로 멀티 스레드 환경에서 실행하다 보면 예기치 못한 결과를 야기할 수 있게된다. *(최신 버전에서는 이슈 해결)* 71 | 72 | 자바 플랫폼에서는 다음과 같은 조치를 취했다. 73 | - 구현한 인터페이스의 디폴트 메서드를 재정의한다. 74 | - 다른 메서드에서는 디폴트 메서드를 호출하기 전에 필요한 작업을 수행하도록 했다. 75 | 76 | > 하지만 그렇다고 해서 모든 오류를 잡아낼 수 있는 것이 아니며, 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으키는 등 다양함 문제를 야기할 수 있다. 🥲 77 | 78 | ## Ref. 79 | - https://asfirstalways.tistory.com/353 80 | - https://k3068.tistory.com/73 81 | -------------------------------------------------------------------------------- /handbook/04/22.상수 인터페이스 절대 쓰지마라.md: -------------------------------------------------------------------------------- 1 | # 22. 인터페이스는 타입을 정의하는 용도로만 써라 2 | ## 🙅 **상수 인터페이스 절대 쓰지마라** 🙅 3 |
4 | 5 | ### 결론 6 | - 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 7 | - 인터페이스는 오직 이 용도로만 사용하자! 8 | 9 | 10 | ### ***안티패턴 : 상수 인터페이스*** 11 | ```java 12 | public interface LottoNumbersConstants{ 13 | static final int LOTTO_NUMBER = 6; 14 | static final int LOTTO_MIN = 1; 15 | static final int LOTTO_MAX = 45; 16 | } 17 | ``` 18 | 19 | **왜 쓰면 안될까?** 20 | - 클래스 내부에서 사용하는 상수는 내부 구현에 해당함. 그러므로 상수 인터페이스를 구현하는 것은 내부 구현을 클래스의 API로 노출하는 행위! (사용자에게 의미없는 정보를 노출하여 혼란을 야기) 21 | - 내부 코드가 상수들에 종속되게 된다. 22 | - 이후 상수가 필요없어져도 호환성 유지를 위해 여전히 상수 인터페이스를 사용해야할수도 있다 23 | 24 | 실제 자바 플랫폼 라이브러리에서도 잘못 활용한 예가 존재하긴 하다. 25 | 26 | ![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/864d6acf-37b8-469a-a806-769406cf7c89/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-03-03_14.26.24.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220303%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220303T052634Z&X-Amz-Expires=86400&X-Amz-Signature=8e467cbd420e718d56db76d1ac7ae61da66a0a2428f732f1afc7cc577d6fc31a&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA%25202022-03-03%252014.26.24.png%22&x-id=GetObject) 27 | 28 | 이는 잘못 활용한 것이니 절대 따라하지 말자. 29 | 30 | **상수를 나타내기 위한 방법?** 31 | - 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해주기 32 | - 열거 타입으로 나타내기 적합한 상수라면 Enum 타입으로 만들어 공개하기 (item34) 33 | - 인스턴스화할 수 없는 유틸리티 클래스에 담아 공개하기 (item4) 34 | 35 | > 인터페이스는 타입을 정의하는 용도로만 쓰자. 상수쓰려고 쓰지 말자! 🙅 -------------------------------------------------------------------------------- /handbook/04/23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라.md: -------------------------------------------------------------------------------- 1 | # 태그 달린 클래스보다는 클래스 계층구조를 활용하라. 2 | 3 | - 핵심 : 추상클래스를 잘 활용해! 4 | 5 | ``` 6 | // 태그 달린 7 | public class Figure { 8 | 9 | enum Shape {RECTANGLE, CIRCLE}; 10 | 11 | final Shape shape; 12 | 13 | double length; 14 | double width; 15 | 16 | double radius; 17 | 18 | public Figure(double length, double width) { 19 | this.shape = Shape.RECTANGLE; 20 | this.length = length; 21 | this.width = width; 22 | } 23 | 24 | public Figure(double radius) { 25 | this.shape = Shape.CIRCLE; 26 | this.radius = radius; 27 | } 28 | 29 | double area() { 30 | switch (shape) { 31 | case RECTANGLE: 32 | return length * width; 33 | case CIRCLE: 34 | return Math.PI * (radius * radius); 35 | default: 36 | throw new AssertionError(shape); 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | - 태그 달린 클래스의 단점 43 | - 가독성이 떨어진다. 44 | - 코드가 변경에 취약하고 오류를 내기 쉽다. 45 | - 사실상 안쓰는게 맞다. 46 | 47 | ``` 48 | // 추상클래스 49 | abstract class Figure { 50 | abstract double area(); 51 | } 52 | 53 | class Circle extends Figure { 54 | final double radius; 55 | 56 | public Circle(double radius) { 57 | this.radius = radius; 58 | } 59 | 60 | @Override 61 | double area() { 62 | return Math.PI * (radius * radius); 63 | } 64 | } 65 | 66 | class Rectangle extends Figure { 67 | final double length; 68 | final double width; 69 | 70 | public Rectangle(double length, double width) { 71 | this.length = length; 72 | this.width = width; 73 | } 74 | 75 | @Override 76 | double area() { 77 | return length * width; 78 | } 79 | } 80 | ``` 81 | - 추상클래스의 장점 82 | - 사실상 태그달린 클래스와 같은 컨셉이지만 단점은 없고 장점은 더 많다. 83 | - 없어도 되는 필드는 없게 만들수 있기 때문에 간결하다. 84 | - area() 메서드에서 오류가 발생할 가능성이 낮고 코드의 유지보수성이 좋아진다. 85 | - 상속 받은 클래스는 더 자유롭게 확장할 수 있다. 86 | -------------------------------------------------------------------------------- /handbook/04/24. 멤버 클래스는 되도록 static으로 만들라.md: -------------------------------------------------------------------------------- 1 | # 아이템 24. 멤버 클래스는 되도록 static으로 만들라 2 | 3 | ## 중첩 클래스란? 4 | 5 | 중첩 클래스란 다른 클래스 내부에 정의된 클래스를 말한다. 6 | - 두 클래스의 멤버들을 쉽게 접근 가능 7 | - 외부로부터 불필요한 관계 클래스를 감춤으로써 코드의 복잡성 감소 8 | 9 | ### 중첩 클래스의 종류 10 | 11 | ![](https://user-images.githubusercontent.com/47477359/156321932-46a9cf44-603c-4fc3-8411-5293cd43addf.png) 12 | 13 | - static으로 선언한 멤버 클래스를 static 중첩 클래스라고 부른다. 14 | - 정적 멤버 클래스 15 | - non-static 멤버 클래스를 내부 클래스라고 부른다. 16 | - 비정적 멤버 클래스 17 | - 익명 클래스 18 | - 지역 클래스 19 | 20 | ## 정적 멤버 클래스 21 | 22 | 정적 멤버 클래스는 바깥 클래스 내부에 static으로 선언된 클래스이다. 23 | - 바깥 클래스의 `private` 멤버에도 접근 가능 24 | - 흔히 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다. 25 | 26 | ```java 27 | public class Person { 28 | 29 | private final String name; 30 | private final int age; 31 | private final int weight; 32 | private final int height; 33 | 34 | public static class Builder { 35 | private final String name; 36 | private final int age; 37 | 38 | private int weight = 0; 39 | private int height = 0; 40 | 41 | public Builder(String name, int age) { 42 | this.name = name; 43 | this.age = age; 44 | } 45 | 46 | public Builder weight(int val) {weight = val; return this;} 47 | 48 | public Builder height(int val) {height = val; return this;} 49 | 50 | public Person build() { 51 | return new Person(this); 52 | } 53 | } 54 | 55 | private Person(Builder builder) { 56 | name = builder.name; 57 | age = builder.age; 58 | weight = builder.weight; 59 | height = builder.height; 60 | } 61 | } 62 | ``` 63 | ```java 64 | Person JJANG9 = new Person.Builder("짱구", 7) 65 | .weight(80).height(200).build(); 66 | ``` 67 | 68 | ## 비정적 멤버 클래스 69 | 70 | 비정적 멤버 클래스란 바깥 클래스 내부에 static 키워드 없이 선언된 클래스이다. 71 | - 바깥 인스턴스 없이는 존재할 수 없다. 72 | - 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출한다거나 바깥 인스턴스를 참조할 수 있다. 73 | 74 | ```java 75 | public class Outer { 76 | String name = "짱구"; 77 | 78 | public void run() { 79 | Inner inner = new Inner(); 80 | inner.run(); 81 | } 82 | 83 | public class Inner { 84 | public void run() { 85 | System.out.println(Outer.this.name); 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | ## 결론 92 | 93 | **멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.** 94 | - static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 되고 심각한 경우 GC가 바깥 인스턴스를 수거하지 못하는 경우가 발생한다. (메모리 누수) 95 | 96 | ## 익명 클래스 97 | 98 | 익명 클래스는 사용 시점에 인스턴스화 되어 한번만 사용할 수 있게 만든 클래스이다. 99 | 익명이라 이름이 없기 때문에 생성자를 가질 수 없고 단 하나의 클래스를 상속받거나 하나의 인터페이스만 구현 가능하다. 100 | - 익명 클래스에 새롭게 정의된 필드와 메서드는 익명 클래스 내부에서만 사용 가능하다. 101 | - 람다가 나오면서 익명 클래스는 빠이~ 102 | 103 | ```java 104 | public class Anonymous { 105 | public void show() { 106 | } 107 | } 108 | 109 | public class Main { 110 | public static void main(String[] args) { 111 | Anonymous anonymous = new Anonymous() { 112 | public void hide() { 113 | System.out.println("는 못말려!"); 114 | } 115 | 116 | @Override 117 | public void show() { 118 | System.out.print("짱구"); 119 | hide(); 120 | } 121 | }; 122 | 123 | //anonymous.hide(); 호출 불가 124 | anonymous.show(); 125 | } 126 | } 127 | ``` 128 | 129 | ## 지역 클래스 130 | 131 | 지역 클래스는 가장 드물게 사용되는 중첩 클래스이다. 132 | - 접근 제한자(public, private) 및 static을 붙일 수 없다. 133 | - 메서드 내부에서만 사용되기 때문에 접근을 제한할 필요가 없다. 134 | - 정적 멤버(정적 필드와 메서드)를 가질 수 없다. 135 | 136 | ```java 137 | public class OuterJJANG9 { 138 | private String outerMessage = "짱"; 139 | 140 | public void method() { 141 | class LocalJJANG9 { 142 | public void print() { 143 | String localMessage = "구"; 144 | System.out.println(outerMessage + localMessage); 145 | } 146 | } 147 | 148 | LocalJJANG9 localJJANG9 = new LocalJJANG9(); 149 | localJJANG9.print(); 150 | } 151 | } 152 | ``` 153 | -------------------------------------------------------------------------------- /handbook/04/25. 톱레벨 클래스는 한 파일에 하나만 담기.md: -------------------------------------------------------------------------------- 1 | # [아이템 25] 톱레벨 클래스는 한 파일에 하나만 담기 2 | 3 | ### 개요 4 | 5 | - 소스 파일 하나에 클래스를 여러개 선언하더라도 자바 컴파일러는 문제가 없음 6 | - `장점이 없으며`, `심각한 위험` 발생 가능 7 | - 한 클래스를 여러가지로 정의할 수 있으며, `어느 소스 파일을 먼저 컴파일하냐에 따라 실행 결과가 달라짐` 8 | 9 | ### Main.java 10 | 11 | ```java 12 | public class Main { 13 | public static void main(String[] args) { 14 | System.out.println("maple: " + MapleStory.INFORMATION + ", lostark :" 15 | + LostArk.INFORMATION); 16 | } 17 | } 18 | ``` 19 | 20 | ### LostArk.java 21 | 22 | ```java 23 | class MapleStory { 24 | public static String INFORMATION = "bad"; 25 | } 26 | 27 | class LostArk { 28 | public static String INFORMATION = "good"; 29 | } 30 | ``` 31 | 32 | ### MapleStory.java 33 | 34 | ```java 35 | class MapleStory { 36 | public static String INFORMATION = "good"; 37 | } 38 | 39 | class LostArk { 40 | public static String INFORMATION = "bad"; 41 | } 42 | ``` 43 | 44 | - javac [Main.java LostArk](http://Main.java)[.java](http://Dessert.java) 45 | - `MapleStory 클래스를 선언해주지 않은 상황`에서 Main.java에서 `MapleStory.INFORMATION`을 먼저 사용하므로 `MapleStory.java`를 먼저 살펴본다. 46 | - 이후 LostArk.java를 인자로 같이 넘겨주는데, Main.java에서 가져온 `MapleStory.java의 LostArk, MapleStory 클래스`와 `LostArk.java의 LostArk, MapleStory 클래스명이 중복`되므로 오류가 난다고 함 → 책에선 에러로 표기되나 실제 테스트 과정에서는 에러가 아닌 `LostArk.java를 사용`하는걸 확인 47 | - javac LostArk.java Main.java → “maple : bad, lostark : good” 출력 48 | - LostArk.java의 MapleStory, LostArk 클래스를 사용하게 된다. 49 | - javac Main.java LostArk.java MapleStory.java 50 | - 중복 클래스 선언 오류(duplicate class) → 컴파일이 되지 않음. 51 | 52 | --- 53 | 54 | ### 결론 55 | 56 | - 컴파일러에 `어느 소스코드를 먼저 넘겨주냐에 따라 동작이 달라지므로` 해결해야 하는 문제이다. 57 | - 해결책은 `톱레벨 클래스들을 서로 다른 소스 파일로 분리`해 준다. 58 | - (MapleStory.java에는 MapleStory 클래스만, LostArk.java에는 LostArk 클래스만) 59 | - 굳이 `여러 톱레벨 클래스를 한 파일에 담기를 원한다면 정적 멤버 클래스를 고민`해보는게 좋다. 60 | 61 | ```java 62 | public class Main { 63 | public static void main(String[] args) { 64 | System.out.println("maple: " + MapleStory.INFORMATION + ", lostark :" 65 | + LostArk.INFORMATION); 66 | } 67 | 68 | public static class MapleStory { 69 | public static String INFORMATION = "good"; 70 | } 71 | 72 | public static class LostArk { 73 | public static String INFORMATION = "good"; 74 | } 75 | } 76 | ``` 77 | 78 | - ~~사실 IDE에서 대부분 잡아줌.~~ 79 | 80 | ### 정리 81 | 82 | - 즉, `소스 파일 하나에는 반드시 톱레벨 클래스(or 인터페이스)를 하나만` 담아라. 83 | -------------------------------------------------------------------------------- /handbook/05/26. 로 타입은 사용하지 말라.md: -------------------------------------------------------------------------------- 1 | # 26. 로 타입은 사용하지 말라 2 | 3 | ### 제네릭 타입(제네릭 클래스, 제네릭 인터페이스) 4 | 5 | ```java 6 | List numbers = new ArrayList<>(); 7 | ``` 8 | 9 | - 클래스와 인터페이스 선언에 `타입 매개변수`를 사용한 클래스와 인터페이스 10 | - List → E는 타입 매개변수 11 | - 제네릭은 JDK 1.5부터 지원됨 12 | 13 | ### 로 타입 14 | 15 | ```java 16 | List a = new ArrayList(); 17 | ``` 18 | 19 | - 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않는 타입 20 | - 타입 정보가 전부 지워진 것처럼 동작함 21 | 22 | ### 로 타입의 문제 23 | 24 | ```java 25 | //삽입과정 26 | List stringCollection = new ArrayList(); // String을 넣으려고 만든 컬렉션 27 | stringCollection.add("1"); 28 | stringCollection.add("2"); 29 | // 이런 저런 문자열 값들이 들어다가 30 | stringCollection.add(2); // 컴파일 오류 x 31 | ``` 32 | 33 | 타입 매개변수가 없기 때문에 어떤 타입들을 넣어도 `컴파일에러`, `런타임 에러`가 발생하지 않는다. 34 | 35 | ```java 36 | for (Iterator i = stringCollection.iterator(); i.hasNext(); ) { 37 | String s = (String) i.next(); 38 | //필요한 작업 39 | } 40 | ``` 41 | 42 | 하지만 컬렉션에 담은 값들을 꺼내는 과정에서 String을 담는 컬렉션을 의도했기 때문에 (String)키워드를 통해 `형변환`을 하는데 위에서 넣는 2라는 값 때문에, `ClassCastException`이 발생한다. 43 | 44 | 추가적으로 해당 컬렉션에 이곳 저곳에서 쓰인다면, 어디서 잘못된 값이 들어갔는지 찾기가 힘들 것이다. 45 | 46 | **✔️ 로타입을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.** 47 | 48 | ### 제네릭 타입을 쓰면 컴파일 단계에서 타입 안정성을 가져갈 수 있다. 49 | 50 | ```java 51 | List stringCollection = new ArrayList<>(); 52 | stringCollection.add("1"); 53 | stringCollection.add("2"); 54 | stringCollection.add(2); // 컴파일 에러 발생! 55 | ``` 56 | 57 | `다른 타입의 인스턴스`가 들어오면 `컴파일 에러`를 발생시킨다. 58 | 59 | ### 제네릭 타입 쓰면 되지 그럼 로타입은 대체 왜 허용해주는거지? 60 | 61 | 이유는 간단하다. 사람들이 제네릭을 받아들이는데 오랜 시간이 걸렸고, 이미 로타입으로 작성된 코드들이 너무 많기 때문에, 그 코드들과의 호환성을 위해서 남겨두었다. 62 | 63 | ### 모든 타입을 허용하려면 로타입이 아닌 Object 타입으로 명시하여 사용하라 64 | 65 | 로 타입은 제네릭 타입에서 아예 **발을 뺀** 것이고 66 | Object 제네릭 타입은 **모든 타입을 허용한다는 것을 컴파일러에서 `명시`**한 것이다. 67 | 68 | ```java 69 | List stringCollection = new ArrayList(); //로타입 나 몰라라 70 | stringCollection.add(1); 71 | stringCollection.add("2"); 72 | ``` 73 | 74 | ```java 75 | List stringCollection = new ArrayList<>(); //난 모든 타입을 받을거야!! 76 | stringCollection.add(1); 77 | stringCollection.add("2"); 78 | ``` 79 | 80 | 하지만 모든 타입에 대한 허용을 하려고 메소드의 매개 변수에 `List`타입을 사용하면 문제가 있다. 81 | 82 | ```java 83 | public static void main(String[] args) { 84 | List stringCollection = new ArrayList<>(); 85 | rawTypeMethod(stringCollection); // 컴파일 에러 발생x 86 | objectTypeMethod(stringCollection); //컴파일 에러 87 | } 88 | 89 | private static void rawTypeMethod(final List stringCollection) {} 90 | 91 | private static void objectTypeMethod(final List stringCollection) {} 92 | ``` 93 | 94 | 로 타입 List 는 `List`의 상위 타입이기 때문에 rawTypeMethod는 컴파일 에러가 발생하지 않는다 95 | 하지만 **로타입은 위에서도 살펴봤듯이 런타임 에러가 발생할 상황이 농후하기 때문에 불안정하기 때문에 사용하지 말자.** 96 | 97 | 모든 타입을 받으려고 선언한 `List`는 제네릭의 `하위 타입 규칙` 때문에 `List`을 받지 못하고 98 | 컴파일 에러가 발생한다. 99 | ❓ Object와 String이 부모 자식 관계라서 될 것 같지만, 100 | `List`에는 어떤 타입의 값도 넣을 수 있지만 `List`은 문자열만 넣을 수 있다. 101 | `List`을 `List` 하위 타입이라고 생각한다면 102 | `List`은 자신의 상위 타입 `List`의 모든 일을 할 수 없기 때문에 리스코프 치환 원칙에 어긋난다. 103 | 104 | ### 비한정 와일드카드 타입 105 | 106 | 위의 제약을 없애기 위해 모든 타입을 받을 수 있는 List와일드카드를 사용하면 된다. 107 | 108 | ```java 109 | public static void main(String[] args) { 110 | List stringCollection = new ArrayList<>(); 111 | wildCardTypeMethod(stringCollection); // 컴파일에러 발생x 112 | } 113 | 114 | private static void wildCardTypeMethod(final List stringCollection) { 115 | stringCollection.get(0); 116 | stringCollection.add(1); // 컴파일 에러 117 | stringCollection.add("1"); // 컴파일 에러 118 | } 119 | ``` 120 | 121 | 하지만 여기에도 제약이 생긴다. 와일드 카드로 모든 타입들을 받을 수 있지만, 122 | **`타입 불변식`을 훼손하지 못하게 null외의 아무 값도 넣지 못한다.** 123 | 124 | main에서 stringCollection은 `List`인데, 메소드의 매개변수는 `List`이다. 125 | `List`에선, 모든 제네릭을 다 받을 수 있지만, 해당 컬렉션 객체가 어떤 타입의 제네릭이었는지 알 수 가 없기 때문에 add를 하지 못하게 한다. **잘못된 타입의 값을 넣었다가 타입 불변식을 훼손할 수 있기 때문**이다. 126 | 127 | 하지만 get과 같이 타입 불변을 훼손하지 않는 행동은 할 수 있다. 128 | 129 | 이에 반해 로 타입 List은 **아무 값이나 add** 할 수 있기 때문에 **타입 불변식을 훼손할 수 있다.** 130 | 131 | ### 로타입은 타입 불변식을 훼손할 수 있기 때문에 사용하지 말자. 132 | 133 | 하지만 로 타입을 써야하는 예외가 있다. 134 | 135 | ### 로 타입을 써야하는 예외 1 - class 리터럴 136 | 137 | - class 리터럴에는 배열과 기본타입은 허용하지만 **매개변수화 타입을 사용할 수 없다.** 138 | - `List.class` 허용 139 | `List.class` 허용X 140 | 141 | ### 로 타입을 써야하는 예외 2 - instanceof 연산자 142 | 143 | - 런타임에는 매개변수화 정보가 지워진다. 컴파일 단계에선 잘못된 타입에 대한 체크가 끝나기 때문 144 | - `instanceof List`와 `instanceof List`은 똑같이 동작한다.![] 145 | 146 | 147 | 148 | 참고자료 149 | - Effective Java 3/E -------------------------------------------------------------------------------- /handbook/05/27. 비검사 경고를 제거하라.md: -------------------------------------------------------------------------------- 1 | # 비검사 경고를 제거하라 2 | 3 | ## 비검사 경고(unchecked warning)란? 4 | 5 | > 컴파일러가 타입 안정성을 확신하지 못할 때 발생하는 경고 6 | 7 | "unchecked"라는 용어는 컴파일러와 런타임 시스템이 모든 타입에 대해 타입 안정성을 check할 수 있을만큼 충분한 타입 정보가 없다는 것을 가리킨다. 8 | 9 | 비검사 경고를 가장 흔하게 볼 수 있는 경우는 로타입의 사용이다. 10 | 11 | ```java 12 | TreeSet se t = new TreeSet(); 13 | set.add("abc"); // unchecked warning 14 | set.remove("abc"); 15 | ``` 16 | 17 | add 메서드를 호출할 때 컴파일러는 해당 컬렉션에 String 객체를 추가하는 것이 안전한지 알 수 없다. TreeSet이 String(혹은 그 슈퍼타입)을 담고 있는 컬렉션이라면 안전하겠지만 컴파일러는 정확히 무슨 타입이 들어있는 컬렉션인지 알 수 없다. 그렇기 때문에 비검사 경고가 발생한다. 18 | 19 | 대부분의 비검사 예외는 제네릭 타입을 사용함으로써 제거할 수 있다. 20 | 21 |
22 | 23 | 타입 파라미터로 캐스팅하는 경우에도 비검사 경고가 발생한다. 24 | 25 | ```java 26 | class Wrapper { 27 | private T wrapped; 28 | ... 29 | public boolean equals (Object other) { 30 | ... 31 | Wrapper otherWrapper = (Wrapper) other; // unchecked cast warning 32 | return (this.wrapped.equals(otherWrapper.wrapped)); 33 | } 34 | } 35 | ``` 36 | 37 |
38 | 39 | 와일드카드를 사용할 수 있는 경우라면, 와일드카드를 이용해 비검사 경고를 제거할 수 있다. 40 | 41 | ```java 42 | class Wrapper { 43 | private T wrapped; 44 | ... 45 | public boolean equals (Object other) { 46 | ... 47 | Wrapper otherWrapper = (Wrapper) other; 48 | return (this.wrapped.equals(otherWrapper.wrapped)); 49 | } 50 | } 51 | ``` 52 | 53 |
54 | 55 | 그러나 와일드카드를 사용할 수 없는 경우에는 비검사 경고를 없앨 수 없다. 이 경우 `@SuppressWarnings("unchecked")` 애너테이션을 통해 경고를 없앨 수 있다. 56 | 57 | but! 타입 안정성을 확신할 수 있지만 기술적 이유로 비검사 경고를 제거할 수 없는 경우에만 사용해야한다. 또한 `@SuppressWarning` 애너테이션은 지역변수부터 클래스까지 어디든 선언할 수 있기 때문에 경고가 발생하는 범위에 알맞게 선언해주어야 한다. 58 | 59 | ### 참고자료 60 | 61 | http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ001 -------------------------------------------------------------------------------- /handbook/05/28. 배열보다는 리스트를 사용하라.md: -------------------------------------------------------------------------------- 1 | 2 | ## 배열보다는 리스트를 사용하라 3 | 4 | 배열과 제네릭 타입에는 중요한 두 가지 차이가 있다 5 | 6 | ### 1. 배열은 `공변(covariant)`이다 7 | 8 | 공변이란 함께 변한다는 뜻이고, Sub가 Super의 하위타입일 때, 9 | Sub[]이 Super[]의 하위 타입이 되는 것이다 10 | Crew를 상속하는 BackendCrew와 FrontendCrew가 있다고 하자 11 | 12 | ```java 13 | public class Crew {} 14 | public class BackendCrew extends Crew{} 15 | public class FrontendCrew extends Crew{} 16 | ``` 17 | 18 | ```java 19 | Crew[] crews = new BackendCrew[10]; 20 | crews[0] = new FrontendCrew(); // 여기서 에러를 던진다 21 | ``` 22 | 23 | 이 코드는 컴파일 타임에는 `Crew[]`, 런타임에는 `BackendCrew[]`가 된다 24 | 따라서 런타임에 `ArrayStoreException`을 발생시킨다 25 | 제네릭으로 생성하면 이런 실수를 컴파일 때 발견할 수 있다 26 | 27 | ```java 28 | List crews = new ArrayList(); // 호환 불가로 컴파일되지 않는다 29 | crews.add(new FrontendCrew()); 30 | ``` 31 | 32 | BackendCrew용 저장소에 FrontendCrew를 넣을 수 없는 건 둘 다 동일하다 33 | 하지만 제네릭으로 생성했을 때가 컴파일 때 바로 알 수 있다 34 | 35 |
36 | 37 | ### 2. 배열은 실체화(reify)된다 38 | 39 | 배열은 런타임에 실체화되지만, 제네릭은 타입 정보가 런타임에 소거된다 40 | 코드 상으로 보면 아래와 같다 41 | 42 | ```java 43 | Crew[] crews = new BackendCrew[10]; 44 | ``` 45 | 46 | crew는 런타임에 BackendCrew[]가 된다 47 | 반면 제네릭 타입은 원소 타입을 컴파일 시에만 검사한다 48 | 49 | ```java 50 | // 컴파일 타임(실제 작성한 코드)에서는 원소 타입을 검사한다 51 | ArrayList backendCrews = new ArrayList(); 52 | ArrayList frontendCrews = new ArrayList(); 53 | 54 | // 런타임에는 제네릭 타입이 소거되어서 구분이 불가능하다 55 | ArrayList backendCrews = new ArrayList(); 56 | ArrayList frontendCrews = new ArrayList(); 57 | ``` 58 | 59 | 이런 차이로 배열과 제네릭을 함께 쓰기 힘들다 60 | 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없는데, 타입 안전하지 않기 떄문이다 61 | 62 |
63 | 64 | ### 실체화 불가 타입(non-reifiable type) 65 | 66 | 앞서 코드로 보였듯, 실체화되지 않아 런타임에 컴파일 타임보다 타입 정보를 적게 가지는 타입이다 67 | `E, List, List` 같은 타입이 해당된다 68 | 이들을 배열로 만들 수 없어 아래와 같은 불편한 점이 존재한다 69 | 1. 제네릭 컬렉션에서 자신의 원소 타입을 담은 배열을 반환하는 게 보통은 불가능하다 70 | 아이템 33에서 해결법을 찾을 수 있다 71 | 2. 가변인수 메서드는 호출할 때 마다 매개변수를 담은 배열을 만드는데, 제네릭을 사용할 경우 경고가 발생한다 72 | 아이템 32에서 더 자세한 설명을 볼 수 있다 73 | 74 |
75 | 76 | ### 실전 결론 77 | 78 | 배열로 형변환 할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜰 때가 있다 79 | 대부분은 배열인 E[] 대신 컬렉션인 List를 사용하면 해결된다 80 | 코드가 조금 복잡해지고 성능이 살짝 나빠질 수 있지만, 타입 안전성과 상호운용성은 좋아진다 81 | 82 |
83 | 84 | ### 참고글 85 | - [https://pompitzz.github.io/blog/Java/whyCantCreateGenericsArray.html](https://pompitzz.github.io/blog/Java/whyCantCreateGenericsArray.html#%E1%84%8C%E1%85%A6%E1%84%82%E1%85%A6%E1%84%85%E1%85%B5%E1%86%A8%E1%84%80%E1%85%AA-%E1%84%87%E1%85%A2%E1%84%8B%E1%85%A7%E1%86%AF%E1%84%8B%E1%85%B4-%E1%84%8E%E1%85%A1%E1%84%8B%E1%85%B5%E1%84%8C%E1%85%A5%E1%86%B7) 86 | - [자바의 제네릭 - 해당 아이템보다는 제네릭에 관한 내용이지만 이해에 많은 도움이 된 글, 추천](https://javabom.tistory.com/69) 87 | 88 | 89 | -------------------------------------------------------------------------------- /handbook/05/29. 이왕이면 제네릭 타입으로 만들라.md: -------------------------------------------------------------------------------- 1 | # 29. 이왕이면 제네릭 타입으로 만들라 2 | `소주캉` 3 | ## 제네릭 타입이 필요한 경우 4 | - 클라이언트에서 직접 형변환해야 하는 타입이 있다면 `제네릭 타입`으로 만들자. 5 | - 클라이언트가 형변환 없이 사용할 수 있다면 더 안전하고 쓰기 편해진다. 6 | ## 제네릭 타입이 필요한 예시 7 | - 임의의 타입을 받을 수 있게 `Object의 배열`로 구현한 `stack`예제 8 | ```java 9 | public class StackTest { 10 | private Object[] elements; // Object의 배열로 구현 11 | private int size = 0; 12 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 13 | 14 | public StackTest() { 15 | this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; 16 | } 17 | 18 | public void push(Object e) { 19 | elements[size++] = e; 20 | } 21 | 22 | public Object pop() { 23 | if (size == 0) { 24 | throw new EmptyStackException(); 25 | } 26 | Object result = elements[--size]; 27 | elements[size] = null; 28 | return result; 29 | } 30 | ``` 31 | - 코드에 오류는 발생하지 않는다. 하지만 클라이언트가 사용할 경우 `명시적 형변환`을 수행해주어야 한다. 32 | - 형변환이 적절하게 이뤄지지 않을 경우 `Runtime오류`가 발생한다. 33 | ```java 34 | public static void main(String[] args) { 35 | StackTest stack = new StackTest(); 36 | stack.push(5); 37 | int element = (int)stack.pop(); // 클라이언트가 직접 형변환. 38 | // 잠재적 런타임 오류의 위험이 있다. 39 | } 40 | ``` 41 | ## 제네릭 타입으로 바꾼 경우 42 | - `제네릭 타입`으로 바꿀 경우 타입 오류를 `Compile타임`에 잡아낼 수 있다. 43 | - 클라이언트가 따로 형변환 해줄 필요가 없어 사용하기 쉽고 안전하다. 44 | ```java 45 | public class GenericStackTest { 46 | private E[] elements; 47 | private int size = 0; 48 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 49 | 50 | public GenericStackTest() { 51 | this.elements = new E[DEFAULT_INITIAL_CAPACITY]; // 실체화 불가 타입 에러 52 | } 53 | 54 | public void push(E e) { 55 | elements[size++] = e; 56 | } 57 | 58 | public E pop() { 59 | if (size == 0) { 60 | throw new EmptyStackException(); 61 | } 62 | E result = elements[--size]; 63 | elements[size] = null; 64 | return result; 65 | } 66 | ``` 67 | - `List`, `제네릭 타입 변수`등 `실체화 불가 타입`은 배열을 만들 수 없기 때문에 `컴파일 오류`가 발생한다. 68 |
69 | 실체화 불가 타입이란? 70 | 제네릭의 `타입 정보`가 런타임에는 소거(erasure)되어 원소 타입을 `컴파일타임`에만 검사하며 `런타임`에는 알 수 없다. 71 | 실체화 되지 않아서 런타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다. 72 |
73 | 74 | ### 배열을 사용하는 코드를 제네릭으로 만드는 방법 75 | #### 1. 제네릭 배열 생성 금지 우회 76 | - 배열은 `Object[]`로 생성하고 이를 타입변수 `E[]`로 형변환 한다. 런타임에서 지정한 타입의 배열로 바뀐다(ex: `int[]`) 77 | - 컴파일 오류 대신 `경고`를 내보낸다. 78 | - 비검사 형변환이 **타입 안전한지 개발자가 스스로 증명**해야한다. 79 | - 안전함을 확신했다면 경고를 숨긴다. 80 | ```java 81 | @SuppressWarnings("unchecked") 82 | public GenericStackTest() { 83 | this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; 84 | } 85 | ``` 86 | - 첫 번째 방법이 가독성이 더 좋고 코드가 더 짧아 현업에서 선호된다. 87 | - 그러나 배열의 `런타임 타입: E[]`이 `컴파일 타입 Object[]`과 달라 `힙 오염`을 일으킨다. 88 |
89 | 힙 오염이란? 90 | 실체화 불가 타입은 런타임에서 타입 정보가 소멸되므로 제네릭 배열의 런타임 타입이 컴파일 타임의 타입과 다르게 된다. 91 | 이처럼 매개변수화 타입의 변수가 타입이 다른 객체를 참조하는 것을 힙 오염이라 한다. 92 |
93 | 94 | #### 2. 배열 타입을 Object[]로 변경 95 | - `elements` 필드의 타입을 `E[]`에서 `Object[]`로 변경 96 | - 힙오염에서 안전하다. 97 | - 첫 번째 방식에서는 형변환을 배열 생성시 `단 한번`만 한다. 98 | - 두 번째 방식은 배열에서 원소를 `읽을 때마다 형변환` 해줘야 한다. 99 | ```java 100 | public class GenericStackTest { 101 | private Object[] elements; 102 | private int size = 0; 103 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 104 | 105 | public GenericStackTest() { 106 | this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; 107 | } 108 | 109 | public void push(E e) { 110 | elements[size++] = e; 111 | } 112 | 113 | public E pop() { 114 | if (size == 0) { 115 | throw new EmptyStackException(); 116 | } 117 | @SuppressWarnings("unchecked") E result = (E) elements[--size]; 118 | // 원소를 읽을 때마다 형변환을 해주고 경고를 무시한다. 119 | elements[size] = null; 120 | return result; 121 | } 122 | 123 | ``` 124 | 125 | ### 제네릭 타입으로 바꾼 효과 126 | - 명시적으로 타입을 받으므로 컴파일 타임에 오류를 감지할 수 있다. 127 | - 클라이언트가 직접 형변환 해줄 필요가 없으므로 사용하기 쉽고 안전하다. 128 | ```java 129 | public static void main(String[] args) { 130 | GenericStackTest stack = new GenericStackTest<>(); 131 | stack.push(5); 132 | int element = stack.pop(); // (int)형변환 필요 없음 133 | } 134 | ``` 135 | 136 | -------------------------------------------------------------------------------- /handbook/05/30.이왕이면 제네릭 메서드로 만들라.md: -------------------------------------------------------------------------------- 1 | ## 제네릭 메서드 2 | 3 | 클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 4 | > ex. Collections의 알고리즘 메서드 (binarySearch, sort 등) 5 | 6 | 제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다. 7 | 8 | ### 로타입 사용 - (아이템 26 - 수용 불가) 9 | 10 | ```java 11 | public class BadCase { 12 | 13 | public static Set union(Set s1, Set s2) { 14 | Set result = new HashSet<>(); 15 | result.addAll(s2); 16 | return result; 17 | } 18 | } 19 | ``` 20 | 21 | - 컴파일은 되지만, 경고가 발생한다. (타입 안전하기 않기 때문에) 22 | 23 | ### 제네릭 메서드 24 | 25 | ```java 26 | public class GoodCase { 27 | 28 | public static Set union(Set s1, Set s2) { 29 | Set result = new HashSet<>(s1); 30 | result.addAll(s2); 31 | return result; 32 | } 33 | } 34 | ``` 35 | 36 | - 타입안전하게 만들 수 있다. 37 | - 타입 매개변수 목록은 메서드의 제한자와 반환타입 사이에 온다. 38 | - 타입 매개변수 목록 : `` 39 | - 반환 타입 : `Set` 40 | - 타입 매개변수의 명명규칙은 제네릭 메서드, 제네릭 타입이 모두 같다. 41 | - 한정적 와일드카드 타입 (아이템 31)을 사용하여 더 유연하게 개선할 수 있다. 42 | 43 | ## 제네릭 싱글턴 팩터리 패턴 44 | 45 | 제네릭은 런타임에 타입 정보가 소거되므로, 하나의 객체를 어떤 타입으로든 매개변수화 할 수 있다. 46 | 47 | 이렇게 하기 위해서 요청한 타입 매개 변수에 따라 불변 객체의 타입을 요청한 타임 매개변수에 맞게 변경해주는 정적 팩토리를 만들어야 한다. 48 | 49 | ```java 50 | public class GenericSingleton { 51 | 52 | private static UnaryOperator IDENTITY_FN = (t) -> t; 53 | 54 | @SuppressWarnings("unchecked") 55 | public static UnaryOperator identityFunction() { 56 | return (UnaryOperator) IDENTITY_FN; 57 | } 58 | } 59 | ``` 60 | 61 | - 해당 예시는 항등 함수를 사용하고 있으며, 어떤 타입이어도 타입 안전하기 떄문에 `SuppressWarnings("unchecked")`를 사용해서 오류를 제거한다. 62 | 63 | ## 재귀적 타입 한정 64 | 65 | 상대적으로 드물지만, 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있다. 66 | 67 | 이는 주로 Comparable 인터페이스 (아이템 14)와 함께 쓰인다. 68 | 69 | ```java 70 | public interface Comparable { 71 | 72 | int compareTo(T o); 73 | } 74 | ``` 75 | 76 | 여기서 타입 매개변수 T는 Comparable을 구현한 타입이 비교할 수 있는 원소의 타입을 정의하며, 거의 모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있다. 77 | 78 | ```java 79 | public class RecursiveTypeBoundEx { 80 | 81 | public static > E max(Collection c); 82 | } 83 | ``` 84 | 85 | 타입 한정인 `>`는 "모든 타입 E는 자신과 비교할 수 있다"라고 읽을 수 있다. 즉, 상호 비교 가능하다는 뜻이다. 86 | 87 | ```java 88 | public class RecursiveTypeBoundEx { 89 | 90 | public static > E max(Collection collection) { 91 | if (collection.isEmpty()) { 92 | throw new IllegalArgumentException("비어 있습니다."); 93 | } 94 | 95 | E result = null; 96 | for (E e : collection) { 97 | if (result == null || e.compareTo(result) > 0) { 98 | result = Objects.requireNonNull(e); 99 | } 100 | } 101 | return result; 102 | } 103 | } 104 | ``` 105 | 106 | ## 정리 107 | 108 | - 이번 아이템에서 설명한 내용들은 와일드카드를 사용한 변형 (아이템 31), 셀프 타입 관용구(아이템 2)를 이해하면 더 잘 활용할 수 있을 것이다. 109 | - 명시적으로 형변환하는 메서드보다 제네렉 메서드가 더 안전하고, 사용하기 쉽다. 110 | - 형변환을 해줘야하는 기존 메서드는 제네릭하게 만들자. (아이템 26 연관됨) -------------------------------------------------------------------------------- /handbook/05/31.한정적 와일드카드를 사용해 API 유연성을 높이라.md: -------------------------------------------------------------------------------- 1 | # 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 2 | 3 | ```java 4 | class Storage { 5 | private List list = new ArrayList<>(); 6 | 7 | public void pushAll(Collection src) { 8 | for(E e : src){ 9 | push(e); 10 | } 11 | } 12 | 13 | private void push(E e){ 14 | list.add(e); 15 | } 16 | } 17 | ``` 18 | 19 | 위의 코드는 컴파일이 되긴 하지만, 결함이 존재하고 있는데요. 20 | 21 | Storage로 객체를 생성하고 pushAll 메서드의 매개변수로 List를 넣어주면, 어떻게 될까요? 22 | 23 | ```java 24 | Storage storage = new Storage<>(); 25 | List integers = new ArrayList<>(); 26 | 27 | storage.pushAll(integers); // Compile Error! 28 | ``` 29 | 30 | 이는 제네릭이 **불공변**이기 때문에 컴파일 오류가 뜨게 됩니다. 31 | 32 | 여기서 **불공변이란 Integer가 Number의 하위 타입이어도, List과 List는 아무런 관계가 없다는 것**입니다. 33 | 34 |
35 | 36 | 이 상황을 해결할 수 있는 것이 바로 **한정적 와일드 카드**입니다. 37 | 38 | ## **Collection** 39 | 40 | 위의 의미는 ‘E의 하위 타입의 Collection’이라는 뜻입니다. 41 | 42 | ## **Collection** 43 | 44 | 위의 의미는 ‘E의 상위 타입의 Collection’이라는 뜻입니다. 45 | 46 | 위의 pushAll 메소드를 아래의 코드로 변경해봅시다. 47 | 48 | ```java 49 | public void pushAll(Collection src) { 50 | for(E e : src){ 51 | push(e); 52 | } 53 | } 54 | ``` 55 | 56 | ```java 57 | Storage storage = new Storage<>(); 58 | List integers = new ArrayList<>(); 59 | 60 | storage.pushAll(integers); // OK! 61 | ``` 62 | 63 | 위에서 발생한 컴파일 에러가 없어지는 것을 확인해 볼 수 있습니다. 64 | 65 | Collection를 통해 입력 매개변수 타입으로 Number 자기 자신과 **하위 타입**이 들어올 수 있도록 하였기 때문입니다. 66 | 67 | 이번에는 list에 있는 값들을 매개변수로 주어진 컬렉션에 옮겨 담는 코드를 작성해 봅시다. 68 | 69 | ```java 70 | public void popAll(Collection dst) { 71 | dst.addAll(list); 72 | list.clear(); 73 | } 74 | ``` 75 | 76 | 위의 코드 또한 컴파일이 되긴 하지만, 결함이 존재하고 있습니다. 77 | 78 | ```java 79 | Storage storage = new Storage<>(); 80 | storage.pushAll(Arrays.asList(1,2,3,4)); 81 | 82 | List objects = new ArrayList<>(); 83 | storage.popAll(objects); //Compile Error! 84 | ``` 85 | 86 | List를 넣으면 컴파일 오류가 뜨게됩니다. 87 | 88 | 이번에는 위의 코드를 아래와 같이 변경해 보겠습니다. 89 | 90 | ```java 91 | public void popAll(Collection dst) { 92 | dst.addAll(list); 93 | list.clear(); 94 | } 95 | ``` 96 | 97 | ```java 98 | Storage storage = new Storage<>(); 99 | storage.pushAll(Arrays.asList(1,2,3,4)); 100 | 101 | List objects = new ArrayList<>(); 102 | storage.popAll(objects); //Ok! 103 | ``` 104 | 105 | 위에서 발생한 컴파일 오류가 없어지는 것을 확인해 볼 수 있습니다. 106 | 107 | 입력 매개변수 타입으로 Number 자기 자신과 **상위 타입**이 들어올 수 있도록 하였기 때문입니다. 108 | 109 |
110 | 111 | ## **PECS** 112 | 113 | 여기서 **펙스(PECS) : producer-extends, consumer-super**라는 개념을 확인해 볼 수 있는데요. 114 | 115 | 위에서 살펴본 pushAll에서는 src 매개변수는 Storage에서 사용할 E 인스턴스에 값을 **생성**하고 있기 때문에 상한 제한을 Collection 사용하는 것입니다. 116 | 117 | 반면에 popAll에서는 dst 매개변수는 Storage의 E 인스턴스를 **소비**하므로 Collection를 사용하는 것이 적절합니다. 118 | 119 | → 유연성을 극대화하기 위해, 원소의 생성자나 소비자용 입력 매개변수에 와일드 카드 타입을 사용합시다! 120 | 121 | **아이템 28, 코드 30-2** 122 | 123 | ```java 124 | public static Set union(Set s1, Set s2) { 125 | Set result = new HashSet<>(s1); 126 | result.addAll(s2); 127 | return result; 128 | // result를 제공한다. 129 | } 130 | ``` 131 | 132 | public static Set union(Set s1, Set s2) 133 | 134 | → public static Set union(Set<**? extends E**> s1, Set<**? extends E**> s2) 135 | 136 | ```java 137 | Set integers = Set.of(1, 2, 3); 138 | Set doubles = Set.of(1.0, 2.0, 3.0); 139 | Set numbers = union(doubleSet, integerSet) // 자바 8부터 가능 140 | Set numbers = Union.union(integers, doubles); // 자바 7까지 141 | ``` 142 | 143 |
144 | 145 | ## **타입 매개변수 VS 와일드 카드** 146 | 147 | ```java 148 | public static void swap(List list, int i, int j); 149 | 150 | public static void swap(List list, int i, int j) 151 | ``` 152 | 153 | 메서드를 정의할 때, 둘 중 어느 것을 사용해도 괜찮은 경우가 많다고 합니다. 154 | 155 | 책에서는 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드 카드를 사용하라고 명시되어 있는데요. 156 | 157 | 따라서 타입 매개변수가 한 개인 swap 메서드를 와일드 카드로 선언해 보겠습니다. 158 | 159 | ```java 160 | public static void swap(List list, int i, int j) { 161 | list.set(i, list.set(j, list.get(i)); 162 | } 163 | ``` 164 | 165 | 그런데 이 코드는 컴파일하면 오류가 뜨게 됩니다. 166 | 167 | 이유는 List에는 null 이외의 어떤 값도 넣을 수 없기 때문입니다. 이 경우에는 swapHelper라는 도우미 메서드를 통해 해당 문제를 해결할 수 있습니다. 168 | 169 | ```java 170 | public static void swap(List list, int i, int j) { 171 | swapHelper(i, list.set(j, list.get(i)); 172 | } 173 | 174 | public static void swapHelper(List list, int i, int j) { 175 | list.set(i, list.set(j, list.get(i)); 176 | } 177 | ``` 178 | 179 | → 클라이언트는 복잡한 swapHelper의 기능을 모른 채, swap 메서드를 호출할 수 있다. 180 | 181 | 182 | ### 저자의 결론: 183 | 184 | 한정적 와일드 카드를 적용하여 유연한 API를 설계해 보자! 185 | 186 |
187 | 188 | ### **Reference** 189 | 190 | **조슈아 블로크, 이펙티브 자바 Effective Java 3/E, 2018** 191 | 192 | https://jjingho.tistory.com/76 193 | 194 | https://madplay.github.io/post/use-bounded-wildcards-to-increase-api-flexibility 195 | -------------------------------------------------------------------------------- /handbook/05/32. 제네릭과 가변인수를 함께 쓸 때는 신중해라.md: -------------------------------------------------------------------------------- 1 | ### 제네릭과 가변인수를 함께 쓸 때는 신중해라 2 | 3 | `판다` 4 | 5 | - **어떤 상황을 의미하는가?** 6 | 7 | ```java 8 | // toArray 함수 구현 (제네릭 X) 9 | 10 | static int[] toArray(int... args){ 11 | return args; 12 | } 13 | ``` 14 | 15 | ```java 16 | // toArray 함수 구현 (제네릭 O) 17 | 18 | static T[] toArray(T... args){ // 제네릭 매개변수 배열의 참조를 외부에 노출 19 | return args; 20 | } 21 | ``` 22 | 23 | - **위험성** 24 | - 불가능한 타입 변환 실행으로 인해 heap 오염이 발생할 위험이 항상 존재 25 | 26 | ```java 27 | // 위험한 사용 예시 28 | 29 | static T exampleCode(List... args) { // int -> T 로의 형변환이 불가능한 type T가 들어온다면... 30 | int temp = 77; 31 | Object[] o = args; 32 | o[0] = temp; 33 | T t = args[0].get(0); // Runtime에 ClassCastException 발생!! 34 | return t; 35 | } 36 | ``` 37 | 38 | - 그럼에도 불구하고, 제네릭 가변인수를 파라미터로 받는 메소드에 오류 대신 경고만 뜨는 이유 ⇒ 실무에서 유용하기 때문. ex) Arrays.asList, Collections.toList, ... 39 | - 기존에는 type-safe한 제네릭 가변변수 메소드에 `@SurpressedWarnings("unchecked")` annotation을 일일히 달아서, API를 사용하는 쪽에서 경고를 숨기는 작업을 수행. 40 | 41 | - **해결 방안** 42 | - (1) 자바 7 이후에는 `@SafeVarargs` annotation을 통해 API 작성자가 해당 메소드가 형변환에 대해 안전함을 보장할 수 있도록 하였다. 43 | 44 | ```java 45 | // Arrays.asList 라이브러리의 구조 46 | 47 | @SafeVarargs 48 | @NotNull 49 | @Contract(pure = true) 50 | public static java.util.List asList(@NotNull T... a){ 51 | ... 52 | } 53 | ``` 54 | 55 | - `@SafeVarargs` annotation을 달기 위해서는, 56 | 57 | 1) 가변인수 파라미터로 받은 배열에 아무것도 저장하지 않는다. 58 | 59 | 2) 제네릭 가변인수 배열을 외부에 노출하지 않는다. (단, `@SafeVarargs` annotation이 달린 다른 메소드에는 괜찮다.) 60 | 61 | 62 | - (2) 제네릭 가변인수를 파라미터로 받는 메소드를 사용하지 않는 방법. 63 | 64 | → 제네릭 가변인수 형태를 제네릭의 List 형태로 바꿔서 사용. 65 | 66 | ```java 67 | static T exampleCode(List args) { 68 | int temp = 77; 69 | Object o = args; 70 | o = temp; 71 | T t = args.get(0); 72 | return t; 73 | } 74 | 75 | public static void main(String[] args){ 76 | int test = exampleCode(List.of(11,22,33)); // List.of 자체가 @SafeVarargs가 달린 메소드이므로 사용 가능 77 | } 78 | ``` 79 | 80 | 81 | - **정리** 82 | - 제네릭과 가변변수는 가급적이면 메소드의 파라미터에 함께 사용하지 말 것. 83 | - 사용해야만 한다면, 타입 변환에 대해 안전함을 검토한 후, `@Safevarargs` annotation을 붙여 사용할 것. 84 | - 기능적으로 유용하므로, 위험을 감수하고 사용하는 경우가 있음. 85 | -------------------------------------------------------------------------------- /handbook/06/34. int 상수 대신 열거 타입 써라.md: -------------------------------------------------------------------------------- 1 | # 34. int 상수 대신 Enum 써라 2 | 3 |
4 | 5 | ## **int 상수의 단점** 6 | 7 | 정수 열거 패턴 8 | ```java 9 | public static final int CARD_PATTERN_DIAMOND = 0; 10 | public static final int CARD_PATTERN_HEART = 1; 11 | public static final int CARD_PATTERN_SPADE = 2; 12 | public static final int CARD_PATTERN_CLOVER = 3; 13 | 14 | public static final int CARD_NUMBER_ACE = 0; 15 | public static final int CARD_NUMBER_TWO = 1; 16 | public static final int CARD_NUMBER_THREE = 2; 17 | ... 18 | ``` 19 | 20 | - 타입 안전을 보장할 방법이 없으며 표현력이 좋지 않음 21 | - 예로 위의 `CARD_PATTERN_DIAMOND == CARD_NUMBER_ACE` 를 해버리면 컴파일러는 아무런 에러를 발생시키지 않음 22 | - 각자 이름 공간이 존재하지 않아 중복이 잦음 23 | - 깨지기 쉬움 24 | - 문자열로 출력하기 까다로움 ( 단순 정수에 불과 ) 25 | - 정수 상수 대신 문자열 상수를 쓰려고 한다면 쓰지마라. 최악이다. 26 | 27 | ## **해당 단점을 해결하기 위해 나온 Enum** 28 | ```java 29 | public enum CardPattern { 30 | DIAMOND, 31 | HEART, 32 | SPADE, 33 | CLOVER; 34 | } 35 | ``` 36 | - 자바 Enum은 `완전한 형태의 클래스` 다. 37 | - 내부적으로 Enum 상수 하나당 인스턴스를 생성해 `public static final` 필드로 공개 38 | - 일종의 싱글톤이라고 보면 된다. 39 | 40 | ### Enum의 장점 41 | - 컴파일타임 타입 안전성을 제공함. 42 | - `Draw(CardPattern cardPattern)` 이라는 메서드가 존재한다면 건네받은 참조는 null을 제외하면 CardPattern의 값임이 확실하다. 43 | - 즉, 다른 타입의 값을 넘기려 하면 컴파일 오류가 남 44 | - 열거 타입에는 각자의 이름 공간이 존재한다. 45 | - 상수 이름 앞에 구분을 넣을 필요가 없다. (CARD_PATTERN or CARD_NUMBER) 46 | - 정수 열거 패턴과 달리 클래스이기 때문에 `임의의 메서드나 필드`를 추가할 수 있다. 47 | - 열거타입은 생성자에서 데이터를 받아 인스턴스 필드에 저장한다. (근본적으로 불변이라 final 이어야함) 48 | - 열거타입을 선언한 클래스 혹은 그 패키지에서만 유용한 기능은 private이나 package-private 메서드로 구현한다. (아이템 15) 49 | - 상수 하나를 제거해도 제거한 상수를 참조하지 않는 클라이언트에 아무 영향이 없다. 50 | - 열거 타입은 `values`, `valueOf` 와 같은 정적 메서드들이 제공된다. 51 | - `values` : 정의된 상수들의 값을 배열에 담아 반환 52 | - `valueOf(String)` : 상수 이름을 입력받아 그 이름에 해당하는 상수를 반환해 줌 53 | - 각 상수에 맞게 상수별 메서드 구현이 가능하다 54 | - 네오 수업에서 처럼 각 상수별로 연산을 정의해줄 수 있음 55 | ```java 56 | public enum Operation { 57 | PLUS {public double apply(double x, double y) {return x + y;}}, 58 | MINUS {public double apply(double x, double y) {return x - y;}}, 59 | TIMES {public double apply(double x, double y) {return x * y;}}, 60 | DIVIDE {public double apply(double x, double y) {return x / y;}}; 61 | 62 | public abstract double apply(double x, double y); 63 | } 64 | ``` 65 | 66 | ### 결론 67 | - ✔️ 정수 열거 패턴은 단점이 많다. Enum을 고려하라 68 | -------------------------------------------------------------------------------- /handbook/06/35. ordinal 메서드 대신 인스턴스 필드를 사용하라.md: -------------------------------------------------------------------------------- 1 | # ordinal 메서드 대신 인스턴스 필드를 사용하라 2 | ## 결론 3 | > enum 의 ordinal() 메서드 쓰지마라 4 | 5 | ## int ordinal() 6 | - enum 열거체의 열거된 순서를 기준으로 index 값을 반환해주는 메서드 7 | 8 | ## ordinal() 을 사용하는 경우(?) 9 | - enum 열거체에 특정 상태를 열거된 순서를 이용해서 반환하고 싶을 때 사용하는 경우가 있다. 10 | 11 | ``` 12 | public enum Ensemble { 13 | 14 | SOLO, DUET, TRIO, QUARTET; 15 | 16 | public int numberOfMusicians() { 17 | return ordinal() + 1; 18 | } 19 | 20 | public static void main(String[] args) { 21 | System.out.println(Ensemble.SOLO.numberOfMusicians()); // 1 22 | System.out.println(Ensemble.DUET.numberOfMusicians()); // 2 23 | System.out.println(Ensemble.TRIO.numberOfMusicians()); // 3 24 | System.out.println(Ensemble.QUARTET.numberOfMusicians()); // 4 25 | } 26 | } 27 | ``` 28 | 29 | - 사용하면 안되는 이유 30 | - 유지보수가 힘들어진다. 31 | - 열거 순서가 꼬이면 ordinal() 을 활용한 메서드는 바로 오작동한다. 32 | - 중간에 새로운 열거체를 넣거나 빼야할 때 영향을 받는다. 33 | - 일정한 순서가 지켜지지 않는 열거체는 아예 사용할수도 없고 사용 하려고 해도 더미 열거체를 사용해야하는 등의 웃긴 상황이 발생한다. 34 | 35 | ## 그럼 어떡해야하나? 36 | - 그냥 인스턴스 변수를 만들어서 사용하자. 37 | 38 | ``` 39 | public enum Rank { 40 | 41 | RANK_A("A", 1), 42 | RANK_2("2", 2), RANK_3("3", 3), RANK_4("4", 4), 43 | RANK_5("5", 5), RANK_6("6", 6), RANK_7("7", 7), 44 | RANK_8("8", 8), RANK_9("9", 9), RANK_10("10", 10), 45 | RANK_J("J", 10), RANK_Q("Q", 10), RANK_K("K", 10); 46 | 47 | private final String rank; 48 | private final int point; 49 | 50 | Rank(String rank, int point) { 51 | this.rank = rank; 52 | this.point = point; 53 | } 54 | } 55 | ``` 56 | 57 | ## 결론 58 | >ordinal() 은 EnumSet, EnumMap 같은데서 사용하라고 만든 메서드이기 때문에 우리가 굳이 사용할 경우는 많지 않다. 59 | -------------------------------------------------------------------------------- /handbook/06/36. 비트 필드 대신 EnumSet을 사용하라.md: -------------------------------------------------------------------------------- 1 | # 아이템 36. 비트 필드 대신 EnumSet을 사용하라 2 | 3 | ## 비트 필드 4 | 5 | 비트 필드란 서로 다른 상태(2의 거듭 제곱 값)을 저장하는 상수들의 집합을 말한다. 6 | 7 | ```java 8 | public class Text { 9 | public static final int STYLE_BOLD = 1 << 0; // 1 10 | public static final int STYLE_ITALIC = 1 << 1; // 2 11 | public static final int STYLE_UNDERLINE = 1 << 2; // 4 12 | public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 13 | 14 | public void applyStyles(int styles) {...} 15 | } 16 | 17 | text.applyStyles(STYLE_BOLD | STYLE_ITALIC); 18 | ``` 19 | 20 | - 비트별 연산을 사용하여 집합 연산을 효율적으로 수행할 수 있다. 21 | - 그러나 비트연산을 하나라도 잘못하게 되면 다른 결과가 나올 수 있다. 22 | - 정수 열거 상수의 단점도 포함! 23 | 24 | ## EnumSet 25 | 26 | ```java 27 | public class Text { 28 | public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHOUGH } 29 | 30 | public void applyStyles(Set