diff --git "a/7\354\240\225_\353\236\214\353\213\244\354\231\200_\354\212\244\355\212\270\353\246\274/\354\225\204\354\235\264\355\205\234 44/\355\221\234\354\244\200 \355\225\250\354\210\230\355\230\225 \354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" "b/7\354\240\225_\353\236\214\353\213\244\354\231\200_\354\212\244\355\212\270\353\246\274/\354\225\204\354\235\264\355\205\234 44/\355\221\234\354\244\200 \355\225\250\354\210\230\355\230\225 \354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" new file mode 100644 index 0000000..2d8def3 --- /dev/null +++ "b/7\354\240\225_\353\236\214\353\213\244\354\231\200_\354\212\244\355\212\270\353\246\274/\354\225\204\354\235\264\355\205\234 44/\355\221\234\354\244\200 \355\225\250\354\210\230\355\230\225 \354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" @@ -0,0 +1,139 @@ +## item 44 + +### 표준 함수형 인터페이스를 사용하라 + +--- + +```java +protected boolean removeEldestEntry(Map.Entry eldest){ + return size() > 100; +} +``` +위 코드는 `LinkedHAshMap` 클래스의 protected 메서드인 `removeEldestEntry` 메서드를 +재정의한 것입니다. `이는 잘 동작하지만, 람다를 사용하면 훨씬 잘 해낼 수 있습니다!` + +자바가 람다를 지원하면서 API를 작성하는 모범 사례도 크게 바뀌었기에, +위의 코드처럼 사위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 `템플릿 메서드 패턴`보다 +같은 효과의 함수 객체를 받는 `정적 팩터리나 생성자 제공`으로 대체되고 있습니다. + +람다는 단 하나의 추상 메서드를 가진 함수형 인터페이스를 구현할 때 사용됩니다. +따라서 위의 코드를 람다로 대체하기 위해 구현한 `표준 함수형 인터페이스`는 다음과 같습니다. + +```java +@FunctionalInterface interface EldestEntryRemovalFunction{ + // removeEldestEntry 에서 size 메서드를 사용하는데 이게 인스턴스 메서드라, + // 팩터리나 생성자를 호출할 때는 맵의 인스턴스가 존재하지 않음을 고려하여 인스턴스 전달 필요 + boolean remove(Map map, Map.Entry eldest); +} +``` + +--- + +### 🙌 위의 인터페이스도 잘 동작하지만, 굳이 사용할 이유는 없습니다. + +왜냐면! 이미 같은 모양의 인터페이스가 준비되어 있기 때문입니다. +java.util.function 패키지를 보면 다양한 용도의 표준 함수형 인터페이스가 담겨 있습니다. +따라서 `필요한 용도에 맞는 게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하라` 고 합니다! + +위의 코드의 경우, 직접 만든 `EldestEntryRemovalFunction` 대신, +표준 인터페이스인 `BiPredicate, Map.Entry>` 를 사용할 수 있습니다. + +--- + +### 🙋‍♀️ 표준 함수형 인터페이스를 활용하는 것의 장점은 뭔가요? + +1) API가 다루는 개념의 수 ↓ > 익히기 쉬움 +2) 제공된 많은 디폴트 메서드 > 다른 코드와의 상호운영성 좋아짐 + - ex) `predicate` 인터페이스의 `predicate` 들을 조합하는 메서드 제공(`and()`, `or()`, `negate()` 등) +
📌 predicate 조합 메서드 + + | 메서드 | 설명 | 예제 | + |--------|------|------| + | `and(Predicate other)` | 두 개의 Predicate를 AND 연산으로 조합 | `p1.and(p2)` | + | `or(Predicate other)` | 두 개의 Predicate를 OR 연산으로 조합 | `p1.or(p2)` | + | `negate()` | 현재 Predicate의 결과를 반대로 반환 | `p1.negate()` | + | `isEqual(Object targetRef)` | 주어진 객체와 같은지 검사하는 Predicate 반환 | `Predicate.isEqual("test")` | +
+ +--- + +### 🙌 표준 함수형 인터페이스 기본 종류 6가지 + +이 기본 인터페이스들은 모두 참조 타입용입니다. + +| 인터페이스 | 매개변수 | 반환값 | 설명 | +|------------------|--------|--------|-------------------------------------------------| +| `UnaryOperator` | T | T | 인수가 1개인 것으로 반환값과 인수 타입이 같은 함수 | +| `BinaryOperator` | (T, T) | T |인수가 2개인 것으로 반환값과 인수 타입이 같은 함수 | +| `Predicate` | T | boolean | 인수 1개를 받아 boolean을 반환하는 함수 | +| `Function` | T | R | 인수와 반환 타입이 다른 함수 | +| `Supplier` | 없음 | T | 인수를 받지 않고 값을 반환(혹은 제공)하는 함수 | +| `Consumer` | T | 없음 | 인수를 하나 받고 반환값은 없는(인수를 소비하는) 함수 | + +위의 기본 인터페이스는 기본 타입인 int, long, double 용으로 각 3개씩 변형이 생겨납니다. +이름은 `BinaryOperator`에서 `LongBinaryOperator` 로 이름 앞에 해당 기본 타입 이름을 붙입니다. +이 변형들 중 유일하게 Function의 변형만 매개변수화 되었습니다. +즉, Function 인터페이스의 기본 타입 변형만 제네릭이 아니라 구체적인 타입을 명시적으로 사용합니다. + +--- + +### 🙋‍♀️ 왜 Function의 변형은 매개변수화 되는건가요? + +`Function` 인터페이스는 입력과 출력의 타입이 다를 수 있기 때문에 그렇습니다! +`Function` 인터페이스는 기본 타입을 반환하는 변형이 9개가 존재합니다. +모두 기본 타입이라면, `SrcToResult` 로 `LongToIntFunction` 으로 진행합니다.(총 6개) +입력이 객체 참조이고 결과가 `int`, `long`, `double` 라면(총 3개), `ToLongFunction` 로 `int[]` 인수를 받아 `long`을 반환한다는 것을 접두어로 `ToResult` 를 붙여 사용합니다. + +--- + +### 🙌 기본 함수형 인터페이스 중 3개에는 인수를 2개씩 받는 변형이 있습니다. + +`BiPredicate` `BiFunction` `BiConsumer` 입니다. +1. BiPredicate + - 두 개의 입력(T, U)을 받아 boolean을 반환하는 함수입니다. +2. BiFunction + - 두 개의 입력(T, U)을 받아 하나의 결과(R)를 반환하는 함수입니다. +3. BiConsumer + - 두 개의 입력(T, U)을 받아 아무것도 반환하지 않는(소비하는) 함수입니다. + +--- + +### 🙌 표준 함수형 인터페이스 사용 시, 주의점!! + +표준 함수형 인터페이스 대부분이 기본 타입만 지원한다고, 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말아야 합니다. +동작은 하지만, 계산량이 많을 때는 성능이 처참히 느려질 수 있습니다. + +--- + +### 🙋‍♀️구조적으로 똑같은 표준 함수형 인터페이스가 있더라도 직접 작성해야 하는 경우가 있나요? + +예를 들어 `Comparator` 인터페이스가 있습니다. 이는 구조적으로 `ToIntBiFunction` 와 동일합니다. + +그럼에도, 독자적인 인터페이스로 살아남아야 하는 이유는 다음과 같습니다. +1. API 에서 자주 사용되는데, 이름이 그 용도를 명확히 설명합니다. +2. 반드시 따라야 하는 규약을 담고 있습니다. +3. 유용한 디폴트 메서드를 듬뿍 제공하고 있습니다. + +따라서, 다음과 같다면 전용 함수형 인터페이스를 구현해야 하는 건 아닌지 진중히 고민해야 합니다. + +--- + +### 🙌 작성하는 게 인터페이스 임을 명심하고 주의해서 설계해야 합니다!!! + +`@FunctionalInterface` 이 달려 있음에 주목해봅시다! +이 애너테이션을 사용하는 이유는 3가지 목적이 있습니다. +1. 해당 클래스의 코드나 설명 문서를 읽을 이에게 람다용으로 설계된 인터페이스라고 알려주는 것 +2. 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일되도록 설정하는 것 +3. 2번의 결과로 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막음 + +따라서 꼭!! 해당 애너테이션을 사용해서 설계해야 합니다. + +--- + +### 🙌 함수형 인터페이스 API 사용시 주의점 + +**서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의 X** + +이유: 클라이언트에게 불필요한 모호함을 안겨 주며, 실제로 문제가 일어나기도 함 + - `ExecutorService` 의 경우, submit 메서드 내부에 `Callable` 와 `Runnable`을 받는 것을 다중 정의 함 + - 그 결과 올바른 메서드를 알려주기 위해 형변환해야 할 때가 자주 생기는 문제 발생함 diff --git "a/8\354\236\245_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234 53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274.md" "b/8\354\236\245_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234 53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274.md" new file mode 100644 index 0000000..f5db282 --- /dev/null +++ "b/8\354\236\245_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234 53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274.md" @@ -0,0 +1,92 @@ +## item 53 + +### 가변인수는 신중히 사용하라 + +--- + +### 🙋‍♀️ 가변인수라는 것은 무엇인가요? + +가변인수라는 것은, 명시한 타입의 인수를 0개 이상 받을 수 있다는 것입니다. +따라서 가변인수 메서드를 호출하면, 아래와 같은 순서로 동작하게 됩니다. + +1. 인수의 개수와 길이가 같은 배열을 만듭니다. +2. 받은 인수를 배열에 저장합니다. +3. 가변인수 메서드에 건네줍니다. + +```java +static int sum(int... args) { + int sum = 0; + for (int arg : args) + sum += arg; + return sum; +} +``` +_`printf`는 가변인수와 한 묶음으로 자바에 도입되었고, 이때 핵심 리플렉션 기능도 재정비되었습니다._ + +--- + +### 🙋‍♀️ 인수를 1개는 꼭 받아야 하는 경우엔 어떻게 해야하나요? + +예를 들어, 최솟값을 찾는 메서드는 인수를 최소한 1개는 받아야 합니다. +다만, 인수의 개수는 런타임에 자동 생성된 배열의 길이로 알 수 있기에 다음과 같이 구현할 수 있습니다. + +```java +static int min(int... args) { + if (args.length == 0) + throw new IllegalArgumentException("인수가 1개 이상 필요합니다."); + int min = args[0]; + for (int i = 1; i < args.length; i++) + if (args[i] < min) + min = args[i]; + return min; +} +``` + +다만 위의 코드는 잘못 구현한 것입니다! 아래와 같은 문제점이 있기 때문입니다! + +``` +1. 인수를 0개만 넣어도 호출이 가능함 +2. 런타임에서야 문제를 인지하게 됨 +3. args 유효성 검사를 명시적으로 진행해야 함(코드가 지저분해 짐) +4. min의 초깃값을 `Integer.MAX_VALUE` 로 설정하지 않고는 `for-each`문도 사용할 수 없음 +``` + +--- + +### 🙌 매개변수를 2개 받게 하면 됩니다! + +즉, 첫 번째로는 평범한 매개변수를 받고, 가변인수는 두 번째로 받으면 앞서의 문제가 사라지게 됩니다. + +```java +static int min(int firstArg, int... remainingArgs) { + int min = firstArg; + for (int arg : remainingArgs) + if (arg < min) + min = arg; + return min; +} +``` + +--- + +### 🙌 다만 성능에 민감한 상황이라면, 가변인수가 걸림돌이 될 수 있습니다. + +가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화합니다. +따라서, 성능에 문제가 생기게 됩니다. + +이는 `다중정의`를 사용해서 해결이 가능합니다! 예를 들어 해당 메서드 호출의 95%가 인수를 3개 이하로 사용한다고 해봅시다. +그렇다면, 인수가 0 ~ 4개인 것까지, 총 5개를 다중정의하게 된다면, 가변 인수를 받는 메서드가 인수 4개 이상인 5%의 호출을 담당하며 성능 최적화가 가능합니다. +즉, 메서드 호출 중 단 5%만이 배열을 생성하게 되는 것입니다. + +```java +public void foo() {} +public void foo(int a1) {} +public void foo(int a1, int a2) {} +public void foo(int a1, int a2, int a3) {} + +public void foo(int a1, int a2, int a3, int... rest) {} +``` + +EnumSet의 정적 팩토리도, 위의 방식으로 비용을 최소화하고 있습니다. + +