-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* [Item52]: 다중정의는 신중히 사용하라 * [Item57]: 지역변수의 범위를 최소화 하라
- Loading branch information
1 parent
48db2a2
commit fdb9b0c
Showing
2 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Wine> 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<Integer> set = new TreeSet<>(); | ||
List<Integer> 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개 이상의 타입을 지원할 때, 위와 같이 명시적 캐스팅으로 인수 포워딩하여 정상동작을 유도할 수 있다. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
## 지역변수의 범위를 최소화하라. | ||
클래스와 멤버의 접근 권한을 최소화하면 얻을 수 있는 이점이 많듯, 지역변수의 유효 범위를 최소로 줄이면 코드 가동성과 유지보수성을 높일 수 있고 오류 가능성을 낮출 수 있다. | ||
|
||
#### 1. 가장 처음 쓰일 때 선언하기 | ||
|
||
변수를 미리 선언해두면 코드가 어수선해지고 가독성이 떨어진다. 또한, 의도한 범위 외에서 그 변수를 사용하면 오류 가능성이 높아진다. | ||
|
||
#### 2. 선언과 동시에 초기화 | ||
|
||
초기화에 필요한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다. 단, try-catch 구문에서의 이 규칙은 예외이다. | ||
|
||
#### 3. 메서드를 작게 유지하고 한 가지 기능에 집중하기 | ||
|
||
하나의 메서드가 여러가지 역할을 가지는 경우 그중 한 기능과만 관련된 지역변수라도 다른 기능을 수행하는 코드에서 접근할 수 있다. 하나의 메서드는 한 가지 일을 잘할 수 있도록 만들면 이런 문제를 해결할 수 있다. | ||
|
||
### for문과 while문 | ||
|
||
반복 변수의 값을 반복문이 종료된 뒤에도 써야되는 상황이라면 while문을 사용해야 한다. 하지만 이런 경우가 아니라면 대부분 for문을 쓰는 편이 낫다. | ||
|
||
while문의 경우 지역변수의 유효범위에 대한 실수가 있어도 오류가 겉으로 드러나지 않는 경우가 있다. for문은 유효 범위가 for문 범위와 일치하여 이러한 오류를 컴파일 타임에 잡아줄 수 있는 장점이 있음 | ||
|