Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions 김수빈/4장/item19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
## 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

### 상속용 클래스가 지켜야 할 것들
- 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
- 어떤 순서로 호출하는지, 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다.
- 재정의 가능 메서드란 public, protected 중 final이 아닌 모든 메서드를 말한다.
- 재정의 가능한 메서드를 호출할 수 있는 모든 상황을 문서로 남기는 것이 좋다.
- 백그라운드 스레드나 정적 초기화 과정에서 호출될 수도 있으므로 유의하자.

### Implentation Requirements와 @implSpec 태그 - remove() 의 예
API 문서 메서드 설명 끝에서 발견할 수 있는 문구 중 <code>Implementation Requirements</code>가 있다. <br>이 절은 메서드 주석에 <code>@implSpec</code> 태그를 붙이면 자바독 도구가 생성해준다.

```java
/**
* {@inheritDoc}
*
* @implSpec
* This implementation iterates over the collection looking for the
* specified element. If it finds the element, it removes the element
* from the collection using the iterator's remove method.
*
* <p>Note that this implementation throws an
* {@code UnsupportedOperationException} if the iterator returned by this
* collection's iterator method does not implement the {@code remove}
* method and this collection contains the specified object.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}
```
- <code>@implSpec</code>은 이 클래스를 상속하여 메서드를 재정의했을 때 나타날 효과를 상세히 설명하고 있다.
- 이 주의점을 통해 우리는 어떤 메서드를 어떤 방식으로 상속해야 할지 알 수 있다.
- 상속용 클래스가 지켜야 할 좋은 문서화의 예이다.

### 훅(hook) 메서드 공개하기 - removeRange() 의 예
```java
/**
* Removes from this list all of the elements whose index is between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* Shifts any succeeding elements to the left (reduces their index).
* This call shortens the list by {@code (toIndex - fromIndex)} elements.
* (If {@code toIndex==fromIndex}, this operation has no effect.)
*
* <p>This method is called by the {@code clear} operation on this list
* and its subLists. Overriding this method to take advantage of
* the internals of the list implementation can <i>substantially</i>
* improve the performance of the {@code clear} operation on this list
* and its subLists.
*
* @implSpec
* This implementation gets a list iterator positioned before
* {@code fromIndex}, and repeatedly calls {@code ListIterator.next}
* followed by {@code ListIterator.remove} until the entire range has
* been removed. <b>Note: if {@code ListIterator.remove} requires linear
* time, this implementation requires quadratic time.</b>
*
* @param fromIndex index of first element to be removed
* @param toIndex index after last element to be removed
*/
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
```
- 주석에서 이 메서드가 clear()에 의해 호출됨을 알리고 있다.
- clear()를 고성능으로 만들기 쉽게 하기 위해 이 메서드를 외부로 공개하고 있다.
- 이렇게 특정한 이유로 <code>protected</code> 접근 제어자로 메서드를 노출해야 할 필요가 있는 경우도 있다.

### 상속용 클래스와 그 제약
- 클래스를 상속용으로 설계하려면 엄청난 노력이 들고 그 클래스 안에 제약도 상당하다.
- 상속용으로 설계하지 않은 클래스는 상속을 금지하는 편이 버그를 줄일 수 있다.
- 클래스를 <code>final</code>로 만들어 상속을 금지한다.
- 모든 생성자를 private 혹은 package-private으로 선언하고 public 정적 팩터리를 만든다.
- 혹여나 일반 클래스에서 상속을 허용하고 싶다면, 재정의 가능 메서드는 절대 사용하지 않도록 문서에 표기하자.

### 정리
- 상속용 메서드를 만들 때는 클래스 내부에서 스스로를 어떻게 사용하는지 문서로 남기자.
- 문서화한 것은 그 클래스가 쓰이는 한 반드시 지키자.
- 그렇지 않을 경우 하위 클래스의 오동작을 만들 수 있다.
- 클래스를 확장해야 할 명확한 이유가 떠오르지 않으면 상속을 금지하자.
- 클래스를 <code>final</code>로 만들거나 생성자를 모두 외부에서 접근 불가능하게 바꾸면 된다.
74 changes: 74 additions & 0 deletions 김수빈/4장/item20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
## 추상 클래스보다는 인터페이스를 우선하라

### 추상 클래스와 인터페이스
- 공통점
- 메서드의 시그니쳐만 만들고 구현을 구현 클래스에게 맡긴다.
- 차이점
- <code>추상클래스</code>: 단일 상속만 가능하다. 구현체는 추상클래스의 하위 클래스가 된다.
- <code>인터페이스</code>: 다중 상속이 가능하다. 인터페이스를 구현했다면, 같은 타입으로 취급된다.

### 인터페이스의 장점 활용하기
1. 믹스인 (mixin)
- 믹스인: 구현 클래스에 '선택적 행위'를 제공한다고 선언하는 효과를 준다.
- ex) Comparable, Iterable, AutoCloseable, Serializable
<b>추상 클래스는 이미 다른 클래스를 상속하는 클래스의 경우, 해당 클래스가 두 부모 클래스를 가질 수는 없으므로 믹스인으로 사용될 수 없다.</b>

2. 계층이 없는 타입 프레임워크
```java
public class Item20Test {
interface Singer {
void Sing();
}

interface Songwriter {
void compose(int chartPosition);
}

interface SingerSongWriter extends Singer, Songwriter {
void strum();
}
}
```
- <code>SingerSongWriter</code> 인터페이스처럼 인터페이스는 다른 인터페이스를 상속할 수 있다.
- 인터페이스의 상속은 상속이라는 단어는 사용하지만, 클래스의 상속처럼 부모, 자식 계층이 존재하지 않는다.
- 부모 클래스의 생성자를 호출할 필요 없다.
- 부모 클래스의 구현 내용도 이어받지 않는다.
- 정의된 메서드들만 구현하면 된다.
- 클래스로 이와 같은 구조를 구현하려면, 상하 관계를 따져보며 차례로 단일 상속을 받아야 한다.
- 만든 이후에도 클래스 상속이 갖는 여러가지 제약을 갖게 된다.

3. 골격 구현(템플릿 메서드 패턴) 활용
- 인터페이스로는 타입 정의 및 몇가지 디폴트 클래스를 구현한다.
- 골격 구현 추상 클래스로 나머지 메서드들까지 구현한다.
```java
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);

return new AbstractList<>() {
@Override
public Integer get(int i) {
return a[i];
}

@Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val;
return oldVal;
}

@Override
public int size() {
return a.length;
}
}
}
```
- 이 코드는 골격 구현의 구체 클래스이다.
- 사용자는 몇가지 오버라이딩 메서드만을 통해 원하는 바를 구현했다.
- int 배열을 받아 Integer 인스턴스의 리스트 형태로 보여주는 어댑터이기도 하다.

골격 구현 클래스는 추상 클래스처럼 구현을 도와주는 동시에 추상 클래스로 타입을 정의할 때 따라오는 심각한 제약에서는 자유롭다. <br>인터페이스 디폴트 메서드가 갖는 한계를 추상클래스를 이용해서 벗어난다.


인터페이스를 구현한 클래스에서 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달하여 활용할 수도 있다. <br>이는 래퍼 클래스에서의 활용법과 비슷하다. <br>이 방식을 시뮬레이트한 다중 상속이라 하고, 다중 상속의 많은 장점을 제공하며 동시에 단점은 피하게 해준다.
118 changes: 118 additions & 0 deletions 김수빈/4장/item21.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
## 인터페이스는 구현하는 쪽을 생각해 설계하라


### 기존 인터페이스에 디폴트 메서드 구현을 추가하는 것은 위험한 일이다.

1. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 '삽입' 될 뿐이다.



- 디폴트 메서드 : 인터페이스에 있는 구현 메서드 (메서드 앞에 default 예약어. 구현부 {} 가 있다.)

- 기본 메서드는 이미 구현되어 있기 때문에 호출하여 사용할 수 있다.
```java
public interface MyInterface {
// 추상 메서드
void abstractMethod();

// 기본 메서드
default void defaultMethod() {
// 기본 구현 코드
System.out.println("This is a default method.");
}
}
```
```java
public class MyClass implements MyInterface {
@Override
public void abstractMethod() {
// 추상 메서드 구현 코드
System.out.println("Abstract method implementation.");
}

public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod(); // 출력 : Abstract method implementation.
obj.defaultMethod(); // 출력 : This is a default method.
}
}
```




예시 ) Collection에 있는 removeIf 메서드

```java
// Collection.java
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
```
```java
static class SynchronizedCollection<E> implements Collection<E>, Serializable {

...
public int size() {
synchronized (mutex) {return c.size();}
}
...
}
```
-> removeIf는 동기화 처리가 되어있지 않지만 SyncronizedCollection 인스턴스에서 호출 가능.

-> 모르고 호출하면 동시성 관련 오류 발생할 수 있다. (ConcurrentModificationException)


```java
public class HaveToOverride implements Collection {

// 위험한 default 메서드를 재정의해서, 사용하지 못하도록 한다.
@Override
public boolean removeIf(Predicate filter) {
throw new UnsupportedOperationException();
}
}
```



2. 디폴트 메서드는 기존 구현체에 런타임 오류를 일으킬 수 있다.

- 자바의 메서드 접근 규칙에 따른 오류 발생 : 자바는 인터페이스보다 클래스가, 상속한 클래스가 우선순위를 가진다.
```java
public class SuperClass {
private void hello() {
System.out.println("hello class");
}
}

public interface MarkerInterface {

default void hello() {
System.out.println("hello interface");
}
}
```
```java
public class SubClass extends SuperClass implements MarkerInterface{
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.hello();
}
}
```
위에서 SubClass는 SuperClass를 상속했기 때문에 인터페이스의 메서드보다 클래스의 메서드로 먼저 접근한다.

그런데 접근하고보니 접근한 메서드는 private이었기 때문에 위와 같은 에러가 발생한다.

<b>default 메서드가 추가되었고, 만약 구현체에서 사용하고 싶지 않다면 default 메서드를 오버라이딩 해서 사용하지 못하도록 한다. </b>
41 changes: 41 additions & 0 deletions 김수빈/4장/item22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## 인터페이스는 타입을 정의하는 용도로만 사용하라


### 상수 인터페이스 안티패턴

```java
public interface PhysicalConstants {

static final double AVOGADROS_NUMBER = 6.022_140_857e23;
static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
static final double ELECTRON_MASS = 9.109_383_56e-31;

}
```
1. 클래스 내부에서 사용하는 상수는 내부 구현에 해당. 상수 인터페이스는 내부 구현을 외부로 노출하는 행위.

2. 사용자에게 혼란을 줌

- final이 아닌 클래스가 상수 인터페이스를 구현하게 되면 하위 클래스가 상수들로 오염됨.



### 상수를 정의하는 방법

1. 사용되는 클래스나 인터페이스 자체에 추가.

2. 인스턴스화 할 수 없는 유틸리티 클래스에 담기.

```java
public final class PhysicalConstants {

private PhysicalConstants() { } // 인스턴스화 방지

public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
public static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
public static final double ELECTRON_MASS = 9.109_383_56e-31;

}
```
<b>인터페이스는 타입을 정의하는 용도로만 사용해야 한다. 상수 공개용 수단으로 사용하지 말자.</b>

Loading
Loading