diff --git a/developLog/SUMMARY.md b/developLog/SUMMARY.md index fea9e88..978921a 100644 --- a/developLog/SUMMARY.md +++ b/developLog/SUMMARY.md @@ -86,6 +86,7 @@ * [item 50 : 적시에 방어적 복사본을 만들라](programming-lanuage/java/effective-java/8/item-50.md) * [item 51 : 메서드 시그니처를 신중히 설계하라](programming-lanuage/java/effective-java/8/item-51.md) * [item 52 : 다중정의는 신중히 사용하라](programming-lanuage/java/effective-java/8/item-52.md) + * [item 53 : 가변인수는 신중히 사용하라.](programming-lanuage/java/effective-java/8/item-53-..md) * [스터디에서 알아가는 것](programming-lanuage/java/effective-java/undefined.md) * [모던 자바 인 액션](programming-lanuage/java/modern-java-in-action/README.md) * [chaper 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?](programming-lanuage/java/modern-java-in-action/chaper-1.-8-9-10-11.md) diff --git a/developLog/programming-lanuage/java/effective-java/8/item-52.md b/developLog/programming-lanuage/java/effective-java/8/item-52.md index 9abe64e..8d1375b 100644 --- a/developLog/programming-lanuage/java/effective-java/8/item-52.md +++ b/developLog/programming-lanuage/java/effective-java/8/item-52.md @@ -236,24 +236,25 @@ list = [-2, 0, 2] ### 2) 다중 정의의 함정 2: 람다와 메서드 참조 -* **문제 코드**: +**문제 코드**: - ```java - @Test - public void lambdaTest1() { - new Thread(System.out::println).start(); - } +```java +@Test +public void lambdaTest1() { + new Thread(System.out::println).start(); +} - @Test - public void lambdaTest2() { - ExecutorService exec = Executors.newCachedThreadPool(); - exec.submit(System.out::println); // 컴파일 오류 발생 가능 - } - ``` +@Test +public void lambdaTest2() { + ExecutorService exec = Executors.newCachedThreadPool(); + exec.submit(System.out::println); // 컴파일 오류 발생 가능 +} +``` - * `lambdaTest1()`은 `Runnable`을 요구하기 때문에 문제없이 컴파일된다. - * `lambdaTest2()`에서는 `submit()` 메서드가 `Runnable`과 `Callable`를 모두 받을 수 있어 **컴파일러가 어떤 메서드를 선택해야 할지 모호해** 컴파일 오류가 발생한다. - * **서로 다른 함수형 인터페이스**라도 **같은 위치의 인수**로 받을 경우, 다중 정의는 혼란을 초래할 수 있다. +* `lambdaTest1()`은 `Runnable`을 요구하기 때문에 문제없이 컴파일된다. +* `lambdaTest2()`에서는 `submit()` 메서드가 `Runnable`과 `Callable`를 모두 받을 수 있어 **컴파일러가 어떤 메서드를 선택해야 할지 모호해** 컴파일 오류가 발생한다. +* **서로 다른 함수형 인터페이스**라도 **같은 위치의 인수**로 받을 경우, 다중 정의는 혼란을 초래할 수 있다. + * 이 말은 서로 다른 함수형 인터페이스라도 서로 근본적으로 다르지 않다는 뜻이다. 컴파일할 때 명령줄 스위치로 -Xlint:overloads를 지정하면 이런 종류의 다중정의를 경고해줄 것이다. {% hint style="danger" %} 자바 4와 그 이후에 대해서 diff --git a/developLog/programming-lanuage/java/effective-java/8/item-53-..md b/developLog/programming-lanuage/java/effective-java/8/item-53-..md new file mode 100644 index 0000000..c3c37ec --- /dev/null +++ b/developLog/programming-lanuage/java/effective-java/8/item-53-..md @@ -0,0 +1,138 @@ +# item 53 : 가변인수는 신중히 사용하라. + +## 1. 잘못 구현된 가변 인수 예시 + +**문제 코드**: + +```java +public static int min(int... args) { + if (args.length == 0) { + throw new IllegalArgumentException("인수가 1개 이상 필요합니다."); + } + + int min = args[0]; + for (int i = 0; i < args.length; i++) { + if (args[i] < min) { + min = args[i]; + } + } + + return min; +} + +@Test +public void minTest(){ + int min = min(); // 인수를 넣지 않고 호출 + System.out.println("min = " + min); +} +``` + +* 이 메서드는 **가변 인수로 0개 이상의 인수**를 받을 수 있다. 만약 **아무 인수도 전달되지 않는 경우**, 런타임에 예외(`IllegalArgumentException`)가 발생한다. +* **런타임 예외**는 코드를 실행하기 전까지 문제를 발견할 수 없으므로, **컴파일 타임**에 이 문제가 감지되지 않는다. +* 또한, **for-each**문 대신 일반 **for**문이 사용되어 코드가 불필요하게 복잡하고 덜 깔끔하다. + +## 2. 올바른 가변 인수 구현 + +* **올바른 구현 예**: + + ```java + public static int min2(int firstArg, int... args) { + int min = firstArg; + + for (int arg : args) { + if (min > arg) { + min = arg; + } + } + + return min; + } + ``` + + * 이 방법에서는 첫 번째 매개변수를 **일반 매개변수**로 두고, 나머지는 **가변 인수**로 받는다. 이로 인해 최소 **하나 이상의 인수**가 필요함을 **컴파일 타임에 보장**할 수 있다. + * 즉, 아무런 인수를 주지 않으면 컴파일러가 오류를 표시하므로 **런타임에 실패할 가능성을 줄여든**다. + * 또한, **for-each**문을 사용하여 코드가 더 간결하고 가독성이 높다. + +## 3. 가변 인수와 성능 최적화 + +{% hint style="info" %} +가변인수란? +{% endhint %} + +* **가변 인수(Varargs)**: + * 가변 인수는 **매개변수의 개수가 유동적인 경우**에 유용하며, 자바에서는 `int... args`와 같이 선언하여 사용한다. + * 가변 인수를 사용하는 메서드는 호출 시 **배열을 생성**하고 그 배열에 인수를 저장하기 때문에, 반복적인 호출 시 **성능 저하**가 발생할 수 있다. +* **성능 최적화와 코드 안정성**: + * 올바르게 가변 인수를 사용하는 것은 코드의 안정성을 높이는 데 기여한다. 필수 매개변수를 일반 매개변수로 정의하면 컴파일 타임에 필수 조건이 보장된다. + * 성능 최적화도 중요하다. 특히, 자주 호출되는 메서드에서는 **불필요한 배열 할당을 최소화**함으로써 성능을 개선할 수 있다. + +1. **가변 인수의 성능 문제**: + +* **가변 인수**는 메서드가 호출될 때마다 **배열을 할당하고 초기화**한다. 이 과정은 **큰 비용이 아닐 수도 있지만**, 성능 최적화가 필요한 경우에는 주의가 필요하다. + +2. **성능 최적화 방법**: + +```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) { } +``` + +* 위와 같은 방식으로 **다양한 매개변수 개수에 맞는 메서드를 오버로딩**한다. +* 대부분의 호출이 **3개 이하의 인수**를 사용하는 경우라면, **인수 3개까지는 배열을 생성하지 않도록** 함으로써 **성능을 최적화**할 수 있다. +* **가변 인수**는 **4개 이상의 인수**를 받을 때만 배열을 생성하도록 하여, 배열 생성의 오버헤드를 줄인다. +* **실제 사용 예시 - `EnumSet`**: + * **`EnumSet`의 정적 팩터리 메서드**도 이와 같은 방식으로 최적화되어 있다. 예를 들어, 4개 이상의 인수를 받는 경우에만 추가적인 처리를 하여 불필요한 리소스 사용을 줄인다. + +{% hint style="danger" %} +근데.. .앞장에서 다중 정의는 신중히 쓰라고 하지 않았나? 성능 최적화를 위해 메서드 오버로딩? +{% endhint %} + +맞습니다, 이 부분은 다소 모순적일 수 있습니다. 앞에서 다중 정의(오버로딩, Overloading)는 혼란을 초래할 수 있으므로 사용을 신중히 해야 한다고 했지만, 여기에서는 성능 최적화를 위해 **매개변수 개수에 따른 메서드 오버로딩**을 제안하고 있습니다. + +> 이 모순은 특정 상황에서의 트레이드오프(trade-off)로 이해할 수 있습니다. 각각의 상황에서 **무엇이 더 중요한지**에 따라 접근 방식이 달라지기 때문입니다. + +#### 오버로딩을 피해야 하는 이유 + +* **혼란 가능성**: 오버로딩은 메서드가 컴파일타임에 선택되기 때문에, 어떤 메서드가 호출될지 **명확하지 않은 경우** 혼란을 초래할 수 있습니다. 특히, 매개변수의 타입이 **근본적으로 다르지 않거나** 오토박싱, 제네릭 등의 요소가 개입되면 개발자가 의도한 대로 동작하지 않을 수 있습니다. +* **사용자 혼란**: API 사용자가 어떤 메서드가 호출될지 예상하기 어려운 경우, 개발자가 그 동작을 잘못 이해하고 사용하는 경우가 생길 수 있습니다. + +#### 오버로딩을 사용하는 경우 - 성능 최적화 + +* **성능 최적화의 필요성**: 가변 인수는 메서드가 호출될 때마다 **배열을 생성하고 초기화**하기 때문에, 성능이 중요한 경우 이 비용을 줄이는 것이 필요합니다. +* 이때, 자주 호출되는 메서드의 경우 **매개변수의 개수별로 오버로딩된 메서드를 제공**함으로써 **불필요한 배열 생성**을 피할 수 있습니다. 대부분의 호출이 인수가 적은 경우라면 이러한 최적화는 성능 개선에 크게 기여할 수 있습니다. + +#### EnumSet의 최적화 예시 + +* `EnumSet`의 **정적 팩터리 메서드**도 이러한 방식으로 최적화되어 있습니다. **인수가 적은 경우에는 별도의 처리를 통해 최적화**하고, **4개 이상의 인수**에 대해서만 배열을 생성하거나 추가적인 처리를 합니다. +* 이는 성능 최적화와 코드의 효율성 때문에 도입된 예시로, 오버로딩을 사용하여 특정 성능 이슈를 해결하려는 것입니다. + +#### 요약 및 트레이드오프 설명 + +* **오버로딩을 피해야 할 때**: + * 메서드 간의 선택이 **명확하지 않은 경우**: 예를 들어, 서로 다른 매개변수 타입이 있지만 이들 사이에 **오토박싱이나 제네릭**과 같은 요소가 개입되면 혼란을 일으킬 수 있으므로, 오버로딩을 피하는 것이 좋습니다. + * **API의 가독성과 직관성**이 중요한 경우: API 사용자에게 **명확하게 어떤 메서드가 호출될지 보장할 수 없는 경우** 오버로딩은 지양해야 합니다. +* **오버로딩을 사용해도 좋은 경우**: + * **성능 최적화가 중요한 경우**: 예를 들어, **가변 인수의 배열 생성**이 반복될 때의 **성능 오버헤드**를 줄이기 위해서는 **매개변수 개수에 따른 오버로딩**이 도움이 될 수 있습니다. + * 이런 경우, 오버로딩을 통해 자주 호출되는 메서드에서 **최적화된 코드 경로**를 제공하여 성능을 개선할 수 있습니다. + +결론적으로, **오버로딩**은 **상황에 따라 장단점이 있으며, 어떤 상황에서는 피해야 하지만** 성능 최적화와 같이 특정한 이유가 있다면 **신중하게 사용할 수 있습니다**. 중요한 것은 오버로딩으로 인해 발생할 수 있는 **혼란과 장점을 잘 저울질하여 사용 여부를 결정하는 것**입니다. + +## 📚 핵심 정리 + +1. **가변 인수를 사용하자**: + * 인수 개수가 일정하지 않은 메서드를 정의할 때 **가변 인수**를 사용하는 것이 좋다. +2. **필수 매개변수가 있다면**: + * **필수 매개변수**가 있다면, **가변 인수 앞에 일반 매개변수**로 두어야 한다. 이를 통해 **컴파일 타임에 오류를 감지**할 수 있고, 잘못된 호출을 방지할 수 있다. +3. **성능 고려**: + * 가변 인수를 사용할 때는 성능 문제도 고려해야 한다. 필요한 경우, **매개변수의 개수에 따라 다른 메서드를 오버로딩**하여 배열 할당 비용을 줄일 수 있다. + +> 결론적으로, **가변 인수를 사용할 때는 필수 매개변수를 앞에 두어 컴파일 타임에 오류를 잡도록** 하고, **성능 최적화가 필요하다면 오버로딩**을 통해 배열 생성의 오버헤드를 줄이는 방식을 사용하는 것이 좋다. 이렇게 하면 코드의 **가독성과 안정성**, 그리고 **성능**을 모두 개선할 수 있다. + + + +> 참고 및 출처 +> +> * [https://jake-seo-dev.tistory.com/526?category=906605](https://jake-seo-dev.tistory.com/526?category=906605)