-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 아이템 51 : 메서드 시그니처를 신중히 설계하라 * 아이템 52 : 다중 정의는 신중히 사용하라 * 아이템 52 : 다중 정의는 신중히 사용하라
- Loading branch information
1 parent
31820f7
commit a79059b
Showing
5 changed files
with
228 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,60 @@ | ||
# 메서드 시그니처란? | ||
- 자바에서 프로그래머가 디자인한 메서드 구조 | ||
- 메서드 이름과 파라미터 리스트로 구성됨 | ||
|
||
```java | ||
// 모두 다른 메서드 시그니처 | ||
public void method() {} | ||
public void method2() {} | ||
public void method(int a) {} | ||
public void method(int a, int b) {} | ||
public void method(boolean a) {} | ||
|
||
// 같은 메서드 시그니처 | ||
public void method() {} | ||
public int method() {} | ||
public String method() {} | ||
``` | ||
# 메서드를 설계할 때 주의사항 | ||
## 메서드 이름을 신중히 짓자 | ||
- 표준 명명 규칙을 따른다. | ||
- 이해할 수 있게 한다. | ||
- 같은 패키지에 속한 다른 이름들과 일관되게 짓는다. | ||
- 긴 이름은 피한다. | ||
- 애매하면 자바 라이브러리의 API 가이드를 참조한다. | ||
|
||
## 편의 메서드를 많이 만들지 말자. | ||
- 편의 메서드란 말 그대로 편의를 위해 존재하는 메서드이다. | ||
- 예를 들면, 헬퍼 클래스인 Collections 안에 있는 모든 메서드를 말한다. (min, max, swap 등등..) | ||
- 메서드가 너무 많은 클래스 또는 인터페이스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다. | ||
|
||
## 매개변수 목록은 짧게 유지하자. | ||
- 4개를 넘어가면 매개변수를 기억하기 쉽지 않으므로 4개 이하가 좋다. | ||
- 같은 타입의 매개변수가 여러 개 연달아 나오는 경우는 특히 해롭다. | ||
- 순서를 바꿔 입력해도 그대로 컴파일되고 실행되기 때문이다. | ||
- 매개변수가 길면 아래의 방법으로 줄일 수 있다. | ||
|
||
### 여러 메서드로 쪼개기 | ||
- 예를 들어 지정된 범위의 부분 리스트에서 인덱스를 찾는 경우를 살펴보자. | ||
- 부분 리스트의 시작, 부분 리스트의 끝, 찾을 원소 이렇게 총 3개의 인자를 넘겨야 한다. | ||
- `subList` 메서드를 사용하여 시작, 끝을 입력하고 `indexOf` 메서드로 찾을 원소를 입력받게 쪼갤 수 있다. | ||
|
||
### 도우미 클래스 만들기 | ||
- 매개변수 여러 개를 묶어주는 도우미 클래스를 만들어서 매개변수 수를 줄일 수 있다. | ||
- `Card`를 예로 들면, 카드의 숫자와 무늬를 하나로 묶어(래핑하여) 하나의 매개변수로 주고받을 수 있다. | ||
|
||
### 빌더 패턴 | ||
- 이 방법은 매개변수 중 일부는 생략해도 괜찮을 때 도움이 된다. | ||
- 모든 매개변수를 하나로 추상화한 객체를 정의하고, | ||
- 세터를 이용하여 값을 설정한 다음, | ||
- execute 메서드를 호출하여 유효성 검사를 한 뒤, | ||
- 객체를 넘겨 계산을 수행한다. | ||
|
||
## 매개변수의 타입으로는 클래스보다는 인터페이스가 낫다. | ||
- 매개변수 타입으로 인터페이스 타입을 이용하면, 하위타입을 모두 호환할 수 있으므로 클래스보다 유연하게 사용할 수 있다. | ||
- 예를 들어 메서드에 HashMap을 넘기지 말고 Map을 사용하자. | ||
- 그러면 HashMap 뿐 아니라 TreeMap, ConcurrentHashMap 등 어떤 Map 구현체도 인수로 건넬 수 있다. | ||
|
||
## boolean보다는 원소 2개짜리 열거 타입이 낫다. | ||
- 열거타입을 사용하면 코드를 읽고 쓰기가 더 쉬워지고, 나중에 선택지를 추가하기도 쉽다. | ||
- 메서드의 이름상 boolean이 더 명확한 상황이 아니라면 열거타입을 고려하자. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,168 @@ | ||
# 다중정의(오버로딩)는 신중히 사용하라. | ||
- 아래의 코드를 보고 무엇을 출력할 지 예상해보자. | ||
```java | ||
class Wine { | ||
String name() { | ||
return "포도주"; | ||
} | ||
} | ||
|
||
class SparklingWine extends Wine { | ||
@Override | ||
String name() { | ||
return "발포성 포도주"; | ||
} | ||
} | ||
|
||
class Champagne extends SparklingWine { | ||
@Override | ||
String name() { | ||
return "샴페인"; | ||
} | ||
} | ||
|
||
public class Overriding { | ||
public static void main(String[] args) { | ||
List<Wine> wineList = List.of( | ||
new Wine(), new SparklingWine(), new Champagne()); | ||
|
||
for (Wine wine : wineList) | ||
System.out.println(wine.name()); | ||
} | ||
} | ||
``` | ||
- `"포도주", "발포성 포도주", "샴페인"` | ||
|
||
|
||
- 이 코드는 어떨까? | ||
```java | ||
public class CollectionClassifier { | ||
public static String classify(Set<?> s) { | ||
return "집합"; | ||
} | ||
|
||
public static String classify(List<?> lst) { | ||
return "리스트"; | ||
} | ||
|
||
public static String classify(Collection<?> c) { | ||
return "그 외"; | ||
} | ||
|
||
public static void main(String[] args) { | ||
Collection<?>[] collections = { | ||
new HashSet<String>(), | ||
new ArrayList<BigInteger>(), | ||
new HashMap<String, String>().values() | ||
}; | ||
|
||
for (Collection<?> c : collections) | ||
System.out.println(classify(c)); | ||
} | ||
} | ||
``` | ||
|
||
- `"집합", "리스트", "그 외"`를 차례로 출력할 것 같지만, 실제로는 `"그 외, "그 외", "그 외"`가 출력된다. | ||
|
||
## 메서드 재정의(오버라이딩)과 다중정의(오버로딩) | ||
### 재정의(오버라이딩) | ||
- 해당 객체의 런타임 타입이 어떤 메서드를 호출할지의 기준이 된다. | ||
- 메서드를 재정의한 다음 '하위 클래스의 인스턴스'에서 그 메서드를 호출하면 재정의한 메서드가 실행된다. | ||
- 컴파일 타임에 그 인스턴스의 타입이 무엇이었냐는 상관없다. | ||
|
||
### 다중정의(오버로딩) | ||
- 어느 메서드를 호출할지가 컴파일타임에 정해진다. | ||
- 컴파일타임에는 for문 안의 c는 항상 Collection<?> 타입이다. | ||
- 런타임에는 타입이 매번 달라지지만, 호출할 메서드를 선택하는 데는 영향을 주지 못하낟. | ||
- 따라서 컴파일타임의 매개변수 타입을 기준으로 항상 세 번째 메서드인 `classify(Collection<?>)`만 호출하게 된다. | ||
- 위 문제를 해결하려면 다음과 같이 모든 `classify` 메서드를 하나로 합친 후 `instanceof`로 명시적으로 검사하면 된다. | ||
```java | ||
public static String classify(Collection<?> c) { | ||
if(c instanceof Set) { | ||
return "집합"; | ||
} | ||
if(c instanceof List) { | ||
return "리스트"; | ||
} | ||
return "그 외"; | ||
} | ||
``` | ||
- 매개변수가 같은 다중정의는 만들지 말자. | ||
- 가장 간단한 방법은 메서드 이름을 다르게 지어주는 것이다. | ||
|
||
## 좋은 예시 | ||
- `ObjectOutputStream` 클래스를 살펴보면 모든 `write` 메서드에 다른 이름을 지어주었다. | ||
![img.png](img.png) | ||
- 이 방식의 장점은 `read` 메서드의 이름과 짝을 맞추기 좋다는 것이다. | ||
- 실제로 `ObjectInputStream` 클래스의 `read` 메서드는 다음과 같이 되어 있다. | ||
![img_1.png](img_1.png) | ||
|
||
## 생성자 | ||
- 생성자는 이름을 다르게 지을 수 없어 무조건 다중정의가 된다. | ||
- 이 경우는 매개변수 중 하나 이상이 "근본적으로 다르다"면 된다. | ||
- 근본적으로 다르다는 것은 두 타입의 값이 서로 어느 쪽으로든 형변환할 수 없다는 뜻이다. | ||
- 이 조건만 충족하면 어느 다중정의 메서드를 호출할지가 매개변수들의 런타임 타입만으로 결정된다. | ||
```java | ||
public class ArrayList<E> extends AbstractList<E> | ||
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { | ||
public ArrayList(int initialCapacity) { | ||
if (initialCapacity > 0) { | ||
this.elementData = new Object[initialCapacity]; | ||
} else if (initialCapacity == 0) { | ||
this.elementData = EMPTY_ELEMENTDATA; | ||
} else { | ||
throw new IllegalArgumentException("Illegal Capacity: " + | ||
initialCapacity); | ||
} | ||
} | ||
|
||
public ArrayList(Collection<? extends E> c) { | ||
Object[] a = c.toArray(); | ||
if ((size = a.length) != 0) { | ||
if (c.getClass() == ArrayList.class) { | ||
elementData = a; | ||
} else { | ||
elementData = Arrays.copyOf(a, size, Object[].class); | ||
} | ||
} else { | ||
// replace with empty array. | ||
elementData = EMPTY_ELEMENTDATA; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## 다중정의가 위험한 또다른 이유 | ||
### 제네릭과 오토박싱 | ||
```java | ||
public class SetList { | ||
public static void main(String[] args) { | ||
Set<Integer> set = new TreeSet<>(); | ||
List<Integer> list = new ArrayList<>(); | ||
|
||
for (int i = -3; i < 3; i++) { | ||
set.add(i); | ||
list.add(i); | ||
} | ||
|
||
for (int i = 0; i < 3; i++) { | ||
set.remove(i); | ||
list.remove(i); | ||
} | ||
|
||
System.out.println(set); | ||
System.out.println(list); | ||
} | ||
} | ||
``` | ||
![img_2.png](img_2.png) | ||
|
||
- `set.remove(i)`의 시그니처는 `remove(Object)`이다. | ||
- `list.remove(i)`는 다중정의된 `remove(int index)`를 선택한다. | ||
- 이 `remove`는 '지정한 위치'의 원소를 제거한다. | ||
- 이 문제를 해결하려면 `list.remove`의 인수를 `Integer`로 형변환하면 된다. | ||
- 이러한 문제는 제네릭과 오토박싱이 등장하면서 두 메서드의 매개변수 타입이 근본적으로 다르지 않게 됐기 때문에 발생한 문제이다. | ||
|
||
## 결론 | ||
- 일반적으로 매개변수 수가 같을 때는 다중정의를 피하라. | ||
- 피할 수 없는 경우 형변환을 통해 정확한 다중정의 메서드가 선택되도록 한다. |