|
| 1 | +# 13. 제네릭 |
| 2 | + |
| 3 | +## 주요 키워드 및 개념 정리 |
| 4 | + |
| 5 | +### 제네릭 사용 이유 |
| 6 | + |
| 7 | +#### 컴파일 시 강한 타입 체크를 할 수 있다. |
| 8 | + |
| 9 | +자바 컴파일러는 잘못 사용된 타입 때문에 발생하는 문제점 제거 위해 제네릭 코드에 대해 강한 타입 체크를 한다. |
| 10 | + |
| 11 | +#### 타입 변환(casting)을 제거한다. |
| 12 | + |
| 13 | +비제네릭 코드는 불필요한 타입 변환을 하기 때문에 성능에 악영향을 미친다. 제네릭 타입을 사용하면 저장될 값의 타입을 제한하기 때문에 타입 변환을 할 필요가 없다. |
| 14 | + |
| 15 | +### 제네릭 사용 |
| 16 | + |
| 17 | +#### 제네릭 타입 |
| 18 | + |
| 19 | +`class<T>, interface<T>` |
| 20 | +제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다. |
| 21 | + |
| 22 | +#### 제네릭 타입의 구체적인 타입 |
| 23 | + |
| 24 | +실제 코드에서 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다. |
| 25 | + |
| 26 | +제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고, 타입 파라미터로 대체했다가 실제 클래스가 사용될 때 구체적인 타입을 지정해 타입 변환을 최소화한다. |
| 27 | + |
| 28 | +#### 멀티 타입 파라미터 |
| 29 | + |
| 30 | +`class<K,B,...>, interface<K,V,...>` |
| 31 | +제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다. |
| 32 | + |
| 33 | +#### `<>`연산자 |
| 34 | + |
| 35 | +`Product<Tv, String> product = new Product<>();` |
| 36 | +타입 파라미터 부분에 `<>`연산자를 사용하면 자바 컴파일러가 타입 파라미터를 유추해 자동으로 설정해준다. |
| 37 | + |
| 38 | +#### 제네릭 메소드 |
| 39 | + |
| 40 | +매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다. |
| 41 | + |
| 42 | +#### 제네릭 메소드 호출 |
| 43 | + |
| 44 | +1. 명시적으로 구체적 타입 지정 |
| 45 | + ```java |
| 46 | + 리턴타입 변수 = <구체적타입> 메소드명(매개값); |
| 47 | + ``` |
| 48 | +2. 매개값을 보고 구체적 타입 추정 |
| 49 | + ```java |
| 50 | + 리턴타입 변수 = 메소드명(매개값); |
| 51 | + ``` |
| 52 | + |
| 53 | +#### 제한된 타입 파라미터 bounded type parameter |
| 54 | + |
| 55 | +`<T extends 최상위타입>` |
| 56 | +타입 파라미터에 지정되는 구체적인 타입을 제한하려면 제한된 타입 파라미터를 사용하면 된다. |
| 57 | + |
| 58 | +타입 파라미터 뒤에 `extends` 키워드를 붙이고 상위 타입(클래스, 인터페이스)을 명시하면 된다. (인터페이스여도 `implements` 사용X) |
| 59 | + |
| 60 | +**주의**: 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버로 제한된다. (하위 타입에만 있는 멤버는 사용X) |
| 61 | + |
| 62 | +#### 와일드카드 타입 |
| 63 | + |
| 64 | +제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적 타입 대신 와일드카드를 세 가지 형태로 사용할 수 있다. |
| 65 | + |
| 66 | +- `<?>`: 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스, 모든 인터페이스 타입이 올 수 있음. |
| 67 | +- `<? extends 상위타입>`: 상위 클래스 제한. |
| 68 | +- `<? super 하위타입>`: 하위 클래스 제한. |
| 69 | + |
| 70 | +#### 제네릭 타입의 상속과 구현 |
| 71 | + |
| 72 | +제네릭 타입도 부모 클래스가 될 수 있다. 자식 제네릭 타입은 **추가적으로** 타입 파라미터를 가질 수 있다. |
| 73 | + |
| 74 | +제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다. |
| 75 | + |
| 76 | +# 14. 람다식 |
| 77 | + |
| 78 | +## 주요 키워드 및 개념 정리 |
| 79 | + |
| 80 | +### 람다식이란 |
| 81 | + |
| 82 | +#### 람다식 Lambda Expressions |
| 83 | + |
| 84 | +람다식은 익명 함수(anonymous function)를 생성하기 위한 식으로 함수지향 언어에 가깝다. |
| 85 | + |
| 86 | +코드가 간결해지고, 컬렉션의 요소를 필터링 하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있다. |
| 87 | + |
| 88 | +#### 람다식의 형태 |
| 89 | + |
| 90 | +매개 변수를 가진 코드 블록으로 런타임 시에는 익명 구현 객체를 생성한다. |
| 91 | + |
| 92 | +### 타겟 타입과 함수적 인터페이스 |
| 93 | + |
| 94 | +람다식은 단순히 메소드를 선언하는 것이 아니라 **이 메소드를 가지고 있는 객체를 생성**해낸다. (자바는 메소드를 항상 클래스의 구성 멤버로 선언하기 때문) |
| 95 | + |
| 96 | +-> 람다식은 인터페이스 변수에 대입된다. |
| 97 | +-> 람다식은 인터페이스의 익명 구현 객체를 생성한다. |
| 98 | +=> 람다식은 구현 클래스를 생성하고 **객체화**한다. |
| 99 | + |
| 100 | +#### 람다식의 타겟 타입 |
| 101 | + |
| 102 | +람다식이 대입될 인터페이스를 람다식의 타겟 타입(target type)이라 한다. |
| 103 | + |
| 104 | +#### 함수적 인터페이스 functional interface |
| 105 | + |
| 106 | +람다식은 하나의 메소드를 정의한다. 하나의 추상 메소드만 선언된 인터페이스만 람다식의 타겟 타입이 될 수 있다. |
| 107 | + |
| 108 | +이러한 인터페이스를 함수적 인터페이스라고 한다. |
| 109 | + |
| 110 | +#### 함수적 인터페이스 `@FunctionalInterface` |
| 111 | + |
| 112 | +인터페이스 선언 시 `@FunctionalInterface`을 붙이면 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 체킹한다. (선택 사항) |
| 113 | + |
| 114 | +람다식은 타겟 타입인 함수적 인터페이스의 추상 메소드 선언 형태에 따라 작성 방법이 달라진다. |
| 115 | + |
| 116 | +### 클래스 멤버와 로컬 변수 사용 |
| 117 | + |
| 118 | +람다식의 실행 블록에 클래스의 멤버 및 로컬 변수를 사용할 수 있다. 클래스 멤버는 제약 사항 없이 사용 가능하고, 로컬 변수는 제약 사항이 따른다. |
| 119 | + |
| 120 | +#### 클래스의 멤버 사용 |
| 121 | + |
| 122 | +람다식에서 this는 생성되는 익명 객체의 참조가 아니라 **람다식을 실행한 객체의 참조**이다. |
| 123 | + |
| 124 | +#### 로컬 변수 사용 |
| 125 | + |
| 126 | +- 메소드 내 생성된 익명 객체: 실행 끝나도 힙 메모리에 계속 존재 -> 사용O |
| 127 | + |
| 128 | +- 메소드의 매개변수, 로컬변수: 스택메모리에 생성되어 메소드 실행이 끝나면 사라짐 -> 익명 객체에서 사용할 수 X |
| 129 | + |
| 130 | +**=> 익명객체에서 사용된 매개변수, 로컬변수는 final 특성을 가진다. (final 생략가능)** |
| 131 | + |
| 132 | +그래서 매개 변수, 로컬 변수를 람다식에서 읽는 것은 허용되지만 람다식 내부 또는 외부에서 변경할 수 없다. |
| 133 | + |
| 134 | +### 표준 API의 함수적 인터페이스 |
| 135 | + |
| 136 | +자바 8부터 자주 사용되는 함수적 인터페이스는 `java.util.function` 표준 API 패키지로 제공한다. |
| 137 | + |
| 138 | +**목적**: 표준 API 패키지의 함수적 인터페이스를 사용해 메소드 또는 생성자의 매개 타입으로 사용. 람다식을 대입할 수 있도록 하기 위해. |
| 139 | + |
| 140 | +#### 표준 API 패키지 함수적 인터페이스 분류 |
| 141 | + |
| 142 | +인터페이스에 선언된 추상 메소드의 매개값과 리턴값의 유무에 따라 구분한다. |
| 143 | + |
| 144 | +#### Consumer |
| 145 | + |
| 146 | +- 추상메소드: 매개값은 있고, 리턴값은 없음 |
| 147 | +- 리턴값이 없는 `accept()` 사용 |
| 148 | +- 매개값을 소비하는 역할 |
| 149 | + |
| 150 | +#### Supplier |
| 151 | + |
| 152 | +- 추상메소드: 매개값은 없고, 리턴값은 있음 |
| 153 | +- `getXXX()` 사용 |
| 154 | +- 실행 후 호출한 곳으로 데이터를 리턴(공급) |
| 155 | + |
| 156 | +#### Function |
| 157 | + |
| 158 | +- 추상메소드: 매개값도 있고, 리턴값도 있음 |
| 159 | +- `apply()` 사용 |
| 160 | +- 주로 매개값을 리턴값으로 매핑(타입 변환) |
| 161 | + |
| 162 | +#### Operator |
| 163 | + |
| 164 | +- 추상메소드: 매개값도 있고, 리턴값도 있음 |
| 165 | +- `applyXXX()` 사용 |
| 166 | +- 주로 매개값을 연산하고 결과를 리턴 |
| 167 | + |
| 168 | +#### Predicate |
| 169 | + |
| 170 | +- 추상메소드: 매개값은 있고, 리턴 타입은 boolean |
| 171 | +- `testXXX()` 사용 |
| 172 | +- 매개값을 조사해서 true/false 리턴 |
| 173 | + |
| 174 | +#### 표준 API 함수적 인터페이스의 디폴트 및 정적 메소드 |
| 175 | + |
| 176 | +디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 함수적 인터페이스의 성질(하나의 추상 메소드, 람다식으로 익명 구현 객체 생성)을 잃지 않는다. |
| 177 | + |
| 178 | +`java.util.function` 패키지의 함수적 인터페이스는 하나 이상의 디폴트 및 정적 메소드를 가진다. |
| 179 | + |
| 180 | +#### andThen()과 compose() 디폴트 메소드 |
| 181 | + |
| 182 | +Consumer, Function, Operator 종류의 함수적 인터페이스에 있는 디폴트 메소드이다. |
| 183 | + |
| 184 | +두 개의 함수적 인터페이스를 순차적으로 연결 하고 첫 번째 처리 결과를 두 번째 매개값으로 제공해 최종 결과값을 얻을 때 사용한다. |
| 185 | + |
| 186 | +- `andThen()`: 인터페이스A 처리 -> 결과를 인터페이스 B의 매개값으로 제공 -> 인터페이스B가 매개값으로 처리 후 최종 결과 리턴 |
| 187 | +- `compose()`: 인터페이스B 처리 -> 결과를 인터페이스 A의 매개값으로 제공 -> 인터페이스A가 처리 후 최종 결과 리턴 |
| 188 | + |
| 189 | +```java |
| 190 | +//andThen() |
| 191 | +인터페이스AB = 인터페이스A.andThen(인터페이스B); |
| 192 | + |
| 193 | +//compose() |
| 194 | +인터페이스AB = 인터페이스A.compose(인터페이스B) |
| 195 | + |
| 196 | +//최종결과 |
| 197 | +최종결과 = 인터페이스AB.method(); |
| 198 | +``` |
| 199 | + |
| 200 | +#### and(), or(), negate() 디폴트 메소드와 isEqual() 정적 메소드 |
| 201 | + |
| 202 | +Predicate 종류의 함수적 인터페이스(리턴타입 boolean)는 `and()`, `or()`, `negate()` 디폴트 메소드를 가진다. 각각 논리 연산자인 `&&`, `||`, `!` 와 대응된다. |
| 203 | + |
| 204 | +- `and()`: 두 Predicate가 모두 true를 리턴하면 최종적으로 true 리턴하는 Predicate 생성 |
| 205 | +- `or()`: 두 Predicate 중 하나만 true를 리턴해도 최종적으로 true 리턴하는 Predicate 생성 |
| 206 | +- `negate()`: Predicate의 결과의 반대값을 리턴하는 새로운 Predicate 생성 |
| 207 | + |
| 208 | +`Predicate<T>` 함수적 인터페이스는 `isEqual()` 정적 메소드를 추가로 제공한다. |
| 209 | + |
| 210 | +- `isEqual()`: `test()` 매개값인 `sourceObject`와 `isEqual()`의 매개값인 `targetObject`를 `java.util.Objects`클래스의 `equals()`의 매개값으로 제공하고, `Objects.equals(sourceObject, targetObject)`의 리턴값을 얻어 새로운 `Predicate<T>`를 생성한다 _(동등비교)_ |
| 211 | + |
| 212 | +```java |
| 213 | +Predicate<Object> predicate = Predicate.isEqual(targetObject); |
| 214 | +boolean result = predicate.test(sourceObject); |
| 215 | +//Objects.equals(sourceObject, targetObject) 실행됨 |
| 216 | +``` |
| 217 | + |
| 218 | +#### minBy(), maxBy() 정적 메소드 |
| 219 | + |
| 220 | +`BinaryOperator<T>` 함수적 인터페이스는 `minBy()`와 `maxBy()` 정적 메소드를 제공한다. 매개값으로 제공되는 Comparator를 이용해 최대 T와 최소 T를 얻는 `BinaryOperator<T>`를 리턴한다. |
| 221 | + |
| 222 | +- `minBy(Comparator<? super T> comparator)` |
| 223 | +- `maxBy(Comparator<? super T> comparator)` |
| 224 | + |
| 225 | +`Comparator<T>` 함수적 인터페이스 내부에는 `compare()` 메소드가 선언되어 있다.(o1,o2 비교: o1이 작으면 음수, 동일하면 0, o1이 크면 양수 리턴) |
| 226 | + |
| 227 | +```java |
| 228 | +@FunctionalInterface |
| 229 | +public interface Comparator<T> { |
| 230 | + public int compare(T o1, T o2); |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +### 메소드 참조 Method References |
| 235 | + |
| 236 | +메소드 참조는 메소드를 참조해 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적이다. |
| 237 | + |
| 238 | +람다식은 기존 메소드를 단순히 호출만 하는 경우가 많다. 이런 경우 메소드 참조를 이용해 깔끔하게 처리할 수 있다. |
| 239 | + |
| 240 | +`IntBinaryOperator operator = Math :: max;` |
| 241 | + |
| 242 | +#### 정적 메소드와 인스턴스 메소드 참조 |
| 243 | + |
| 244 | +- **정적 메소드 참조**: 클래스 이름 뒤 `::` 기호 붙이고 정적 메소드 이름 기술 |
| 245 | + `클래스 :: 메소드` |
| 246 | +- **인스턴스 메소드 참조**: 먼저 객체를 생성한 다음 참조 변수 뒤에 `::` 기호 붙이고 인스턴스 메소드 이름 기술 |
| 247 | + `참조변수 :: 메소드` |
| 248 | + |
| 249 | +#### 매개 변수의 메소드 참조 |
| 250 | + |
| 251 | +메소드 참조에서 참조하는 메소드가 람다식에서 제공되는 매개 변수의 멤버일 수도 있다. |
| 252 | + |
| 253 | +이런 경우 메소드 참조로 표현하면 매개변수 a의 이름 뒤에 `::` 기호를 붙이고 메소드 이름을 기술하면 된다. |
| 254 | + |
| 255 | +작성 방법은 정적 메소드 참조와 동일 하지만 다른 코드가 실행된다. |
| 256 | + |
| 257 | +```java |
| 258 | +//람다식 |
| 259 | +(a,b) -> {a.instanceMethod(B);} |
| 260 | + |
| 261 | +//메소드 참조 |
| 262 | +클래스 :: instanceMethod |
| 263 | +``` |
| 264 | + |
| 265 | +#### 생성자 참조 |
| 266 | + |
| 267 | +단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치할 수 있다. |
| 268 | + |
| 269 | +클래스 이름 뒤에 `::` 기호를 붙이고 new 연산자를 기술하면 된다. |
| 270 | + |
| 271 | +생성자가 오버로딩 되어 여러개가 있을 경우 컴파일러가 맞는 생성자를 찾아 실행한다. |
| 272 | + |
| 273 | +`클래스 :: new` |
| 274 | + |
| 275 | +## 관심 가는 내용 |
| 276 | + |
| 277 | +#### 람다식 기본 문법 |
| 278 | + |
| 279 | +- `(타입 매개변수, ...) -> { 실행문; ...}` |
| 280 | + ()는 오른쪽 중괄호 {} 블록을 실행하기 위해 필요한 값을 제공하는 역할. `->`는 매개 변수를 이용해 중괄호를 실행한다는 뜻. |
| 281 | +- `(int a) -> {System.out.prinln(a);}` |
| 282 | + 매개 변수 타입은 런타임 시 대입되는 값 따라 자동으로 인식되기 때문에 보통 매개 변수 타입을 지정X |
| 283 | +- `(a) -> {System.out.prinln(a);}` |
| 284 | + 하나의 매개 변수: 괄호 생략 가능 |
| 285 | + 하나의 실행문: 중괄호 생략 가능 |
| 286 | +- `int a -> System.out.prinln(a);` |
| 287 | + 매개 변수가 없다면 빈 괄호 () 반드시 사용. |
| 288 | +- `( ) -> {실행문; ...}` |
| 289 | + 중괄호에서 결과값을 리턴하면 return문으로 결과값 지정 가능 |
| 290 | +- `(x,y) -> {return x + y;}` |
| 291 | + 중괄호에 return문만 있으면 보통 아래처럼 생략해 작성 |
| 292 | +- `x,y -> x + y` |
| 293 | + |
| 294 | +#### 함수적 인터페이스 작성 방법 |
| 295 | + |
| 296 | +인터페이스 이름이 `MyFunctionalInterface`일 때. |
| 297 | + |
| 298 | +람다식이 대입된 인터페이스 참조 변수로 메소드를 호출 하면 람다식 중괄호를 실행 시킴. |
| 299 | + |
| 300 | +- 매개 변수와 리턴값이 없는 람다식 |
| 301 | + 작성: `MyFunctionalInterface fi = () -> {...}` |
| 302 | + 메소드 호출: `fi.method()` |
| 303 | +- 매개 변수가 있는 람다식 |
| 304 | + 작성: `MyFunctionalInterface fi = (x) -> {...} 또는 x -> {...}` |
| 305 | + 메소드 호출: `fi.method(5);` |
| 306 | +- 리턴값이 있는 람다식 |
| 307 | + 작성: `MyFunctionalInterface fi = (x,y) -> {...; return 값;}` |
| 308 | + 메소드 호출: `int result = fi.method(2,5);` |
| 309 | + |
| 310 | +#### 표준 API 함수적 인터페이스들의 메소드들 |
| 311 | + |
| 312 | +각 인터페이스(Consumer, Supplier, Function, Operator, Predicate)는 매개변수와 리턴값(타입, 개수 등)에 따라 여러 인터페이스와 추상 메소드를 가진다. _필요에 따라 찾아 사용할 것._ |
| 313 | + |
| 314 | +#### 표준 API 함수적 인터페이스 사용 흐름 |
| 315 | + |
| 316 | +1. 사용자 메소드 호출(사용)하면서 매개값으로 람다식 사용 -> 람다식은 인터페이스의 익명구현객체 -> 인터페이스의 인스턴스 대입 |
| 317 | + |
| 318 | +2. 사용자 메소드 작성 시 매개값으로 표준API의 함수적 인터페이스 사용 -> 1번 메소드 호출 하면서 익명구현객체인 인스턴스 대입 |
| 319 | + |
| 320 | +3. 사용자 메소드 내부에서 매개변수(표준 API의 함수적 인터페이스)의 메소드 호출 -> 인터페이스의 구현객체인 람다식의 코드블록에서 재정의한 메소드가 호출됨 |
0 commit comments