diff --git "a/Ch08/item52/\353\213\244\354\244\221\354\240\225\354\235\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/Ch08/item52/\353\213\244\354\244\221\354\240\225\354\235\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..17cf7ec --- /dev/null +++ "b/Ch08/item52/\353\213\244\354\244\221\354\240\225\354\235\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,218 @@ +### 다중정의(Overloading) 문제 + +```java +static class CollectionClassifier { + public static String classify(Set s) { + return "집합"; + } + + public static String classify(List s) { + return "리스트"; + } + + public static String classify(Collection s) { + return "그 외 컬렉션"; + } +} + +@Test +public void collectionClassifierTest() { + Collection[] collections = { + new HashSet<>(), + new ArrayList<>(), + new HashMap<>().values() + }; + + for (Collection collection : collections) { + System.out.println(CollectionClassifier.classify(collection)); + } +} +``` + +- 예상 : "집합", "리스트", "그 외 컬렉션" +- 실행 결과  : "그 외 컬렉션" +- 오버로드된 메서드 중 어느 메서드를 실행할지는 컴파일타임에 결정된다. +- 컴파일타임의 매개변수를 기준으로 항상 `Collection`을 받는 메서드만 호출되며, 런타임의 타입을 신경쓰지 않는다. +- **재정의(override) 한 메서드는 동적으로 선택되고 다중 정의(overload) 한 메서드는 정적으로 선택되기 때문이다.** + +### 재정의(Overriding) + +```java +static class Wine { + String name() { return "포도주"; } +} + +static class SparklingWine extends Wine { + @Override String name() { return "발포성 포도주"; } +} + +static class Champagne extends SparklingWine { + @Override String name() { return "샴페인"; } +} + +@Test +public void wineTest() { + List wineList = List.of(new Wine(), new SparklingWine(), new Champagne()); + + for (Wine wine : wineList) { + System.out.println("wine.name() = " + wine.name()); + } +} +``` + +- 런타임에 결정된 타입에서 재정의된 메서드가 실행된다. + +### 오버로딩말고 Instance Of 사용하기 + +```java +static class CollectionClassifier2 { + public static String classify(Collection s) { + if (s instanceof Set) { + return "집합"; + } + else if (s instanceof List) { + return "리스트"; + } + + return "그 외 컬렉션"; + } +} + +@Test +public void collectionClassifierTest2() { + Collection[] collections = { + new HashSet<>(), + new ArrayList<>(), + new HashMap<>().values() + }; + + for (Collection collection : collections) { + System.out.println(CollectionClassifier2.classify(collection)); + } +} +``` + +- 오버로딩으로 메서드를 따로 만들지 않고 `instanceof` 연산자를 통해 문제를 해결해도 된다. + + +### 다중 정의에서 주의할 점 + +- **매개변수 수가 같은 다중 정의는 만들지 말자** + - 혹여나 가변인수를 사용한다면, 다중정의 자체를 만들지 말자. +- **다중 정의 대신 메서드 이름을 다르게 짓자** + - `ObjectOutputStream` 클래스의 경우 `writeBoolean()`, `writeInt()`와 같은 이름의 메서드를 제공한다. + +```java + + public void writeBoolean(boolean val) throws IOException { + bout.writeBoolean(val); + } + + /** + * Writes a 32 bit int. + * + * @param val the integer value to be written + * @throws IOException if I/O errors occur while writing to the underlying + * stream + */ + public void writeInt(int val) throws IOException { + bout.writeInt(val); + } + +``` + +- 생성자는 이름을 다르게 지을 수 없으니 두번째 생성자부터는 무조건 다중 정의가 된다 (1개 이상이라면, 무조건 다중정의) +- 생성자의 경우엔 이 경우엔 정적 팩터리라는 대안을 이용하여 생성 의도와 코드를 명확하게 할 수 있다. +- 매개변수 수가 같은 다중정의 메서드가 많더라도, 그 중 어느것이 주어진 매개변수의 집합을 처리할지가 명확히 구분되면 헷갈일 일이 없다.(매개변수 중 하나 이상이 근본적으로 다름) + +### 다중 정의의 함정 1: 오토박싱 + +```java +@Test +public void boxingTest() { + Set set = new TreeSet<>(); + List list = new ArrayList<>(); + + for (int i = -3; i < 3; i++) { + set.add(i); + list.add(i); + } + + System.out.println("set = " + set); + System.out.println("list = " + list); + + for (int i = 0; i < 3; i++) { + set.remove(i); + list.remove(i); + } + + System.out.println("set = " + set); + System.out.println("list = " + list); +} +``` + +**실행 결과** + +```subunit +set = [-3, -2, -1, 0, 1, 2] +list = [-3, -2, -1, 0, 1, 2] +set = [-3, -2, -1] +list = [-2, 0, 2] +``` + +> set.remove(i) + +remove(Object)로, 다중정의된 다른 메서드가 없어 집합에서 0 이상의 수들을 제거한다. + +> list.remove(i) + +remove(int index)로, 지정한 위치의 원소를 제거한다. +리스트의 0번째, 1번째, 2번째 원소가 제거되어 실제 결과가 예상과 다르게 나온 것이다. + +- 그 이유는 `list`에는 `remove(Object element)`와 `remove(int index)` 두가지 메서드가 다중 정의되어 있기 때문이다. + - 이 중 우리가 의도한 것은 첫번째 메서드인데, 두번째 메서드가 적용되었다. +- 반면 `set`에는 `remove(Object element)` 메서드밖에 존재하지 않아서 우리의 의도대로 메서드가 적용됐다. + +- 정상작동하도록 캐스팅하기 - Integer 형변환 추가 +```java + for (int i = 0; i < 3; i++) { + set.remove(i); + list.remove((Integer) i); + } +``` + + +### 다중 정의의 함정 2: 람다와 메서드 참조 + +```java +@Test +public void lambdaTest1() { + new Thread(System.out::println).start(); +} + +@Test +public void lambdaTest2() { + ExecutorService exec = Executors.newCachedThreadPool(); + exec.submit(System.out::println); +} +``` + +- `lambdaTest1()`의 경우 `Thread` 생성자가 `Runnable`을 받기 때문에, `System.out::println`을 그대로 사용할 수 있습니다 +- `lambdaTest2()`의 경우 `exec.submit()` 메서드는 `Runnable`이나 `Callable`을 받을 수 있는데, `System.out::println`은 직접적으로 이들 중 어느 하나와 호환되지 않습니다. +- 즉, **서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안 된다.** + +> - Runnable: 어떤 객체도 리턴하지 않습니다. Exception을 발생시키지 않습니다. +> - Callable: 특정 타입의 객체를 리턴합니다. Exception을 발생킬 수 있습니다. + +https://stackoverflow.com/questions/71126688/confusion-in-overloading-println-method-in-thread-and-executorservice + +### 다중 정의의 함정 피하기 1: 인수 포워드하기 + +```java +public boolean contentEquals(StringBuffer sb) { + return contentEquals((CharSequence)sb); +} +``` + +- 다중 정의로 2개 이상의 타입을 지원할 때, 위와 같이 명시적 캐스팅으로 인수 포워딩하여 정상동작을 유도할 수 있다. + diff --git "a/Ch09/Item57/\354\247\200\354\227\255\353\263\200\354\210\230\354\235\230_\353\262\224\354\234\204\353\245\274_\354\265\234\354\206\214\355\231\224\355\225\230\353\235\274.md" "b/Ch09/Item57/\354\247\200\354\227\255\353\263\200\354\210\230\354\235\230_\353\262\224\354\234\204\353\245\274_\354\265\234\354\206\214\355\231\224\355\225\230\353\235\274.md" new file mode 100644 index 0000000..23ba5b5 --- /dev/null +++ "b/Ch09/Item57/\354\247\200\354\227\255\353\263\200\354\210\230\354\235\230_\353\262\224\354\234\204\353\245\274_\354\265\234\354\206\214\355\231\224\355\225\230\353\235\274.md" @@ -0,0 +1,21 @@ +## 지역변수의 범위를 최소화하라. +클래스와 멤버의 접근 권한을 최소화하면 얻을 수 있는 이점이 많듯, 지역변수의 유효 범위를 최소로 줄이면 코드 가동성과 유지보수성을 높일 수 있고 오류 가능성을 낮출 수 있다. + +#### 1. 가장 처음 쓰일 때 선언하기 + + 변수를 미리 선언해두면 코드가 어수선해지고 가독성이 떨어진다. 또한, 의도한 범위 외에서 그 변수를 사용하면 오류 가능성이 높아진다. + +#### 2. 선언과 동시에 초기화 + +초기화에 필요한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다. 단, try-catch 구문에서의 이 규칙은 예외이다. + +#### 3. 메서드를 작게 유지하고 한 가지 기능에 집중하기 + +하나의 메서드가 여러가지 역할을 가지는 경우 그중 한 기능과만 관련된 지역변수라도 다른 기능을 수행하는 코드에서 접근할 수 있다. 하나의 메서드는 한 가지 일을 잘할 수 있도록 만들면 이런 문제를 해결할 수 있다. + +### for문과 while문 + +반복 변수의 값을 반복문이 종료된 뒤에도 써야되는 상황이라면 while문을 사용해야 한다. 하지만 이런 경우가 아니라면 대부분 for문을 쓰는 편이 낫다. + +while문의 경우 지역변수의 유효범위에 대한 실수가 있어도 오류가 겉으로 드러나지 않는 경우가 있다. for문은 유효 범위가 for문 범위와 일치하여 이러한 오류를 컴파일 타임에 잡아줄 수 있는 장점이 있음 +