Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## item22 인터페이스는 타입을 정의하는 용도로만 사용하라

- 인터페이스의 역할: 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해주는 것이다. (인터페이스는 오직 이 용도로만 사용해야 한다.)
- 이 지침을 어긴 케이스: 상수 인터페이스
- 상수 인터페이스: 메서드 없이, 상수를 뜻하는 static final 필드로만 가득 찬 인터페이스

```java
public interface Constants {
static final int WIDTH = 800;
static final int HEIGHT = 600;
static final String TITLE = "My App";
}
//상수 인터페이스를 상속해서 사용
public class MyWindow implements Constants {
public void print() {
System.out.println("창 너비: " + WIDTH);
}
}
```

- 클래스 내부에서 사용하는 상수는 내부 구현에 해당된다. 즉, 상수 인터페이스를 구현하는 것은 이 내부 구현을 클래스의 API로 노출하는 행위다.
- 그 이유는 사용자의 입장에서 위의 코드는 아래의 코드처럼 보인다.

```java
MyWindow window = new MyWindow();
window instanceof Constants //true!
```

즉,외부에서는 *이 클래스가 Constants라는 타입을 구현하고 있다*고 인식하게 된다.

- 상수 인터페이스는 사용자에게 아무런 의미가 없고 오힐 사용자에게 혼란을 주기도 하며, 더 심하게는 클라이언트 코드가 내부 구현에 해당하는 이 상수들에 종속되게 한다.
- 예를 들어,

```java
if (userInput.equals(Constants.TITLE)) {
...
}
```

이런 코드를 사용하다가 Constants.TITLE이 더 이상 필요가 없어져서 지우고 싶어도 이걸 사용하는 모든 사용자의 코드가 깨지거나 이런 오류를 방지하기 위해 억지로 인스페이스를 계속 유지시켜야 한다.

---

- 그러면 이런 상수들의 묶음은 어떻게 표시해야 좋을까?

1. 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해야 한다. 대펴적으로 Integer와 Double에 선언된 MAX/MIN_VALUE가 있다.
2. 열거 타입으로 나타내기 적합한 상수라면 열거 타입으로 만들어 공개하면 된다.
3. 인스턴스화할 수 없는 유틸리티 클래스에 담아서 공개하면 된다.
위의 예시를 유틸리티 클래스를 바꾸어 보면 아래와 같은 코드가 된다.

```java
public class Constants {
private Constants() {} //인스턴스화 방지

public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final String TITLE = "My App";
}
```

- 유틸리티 클래스에 정의된 상수를 클라이언트에서 사용하려면 클래스 이름까지 함께 명시해야 한다. (_Constants.WIDTH_) -> 정적 임포트하여 클래스 이름은 생략할 수도 있다.

---

- 핵심 정리
- _인터페이스는 타입을 정의하는 용도로만 사용해야 한다. 상수 공개용 수단으로 사용하지 말자._
32 changes: 32 additions & 0 deletions item27/item27_비검사 경고를 제거하라.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## item27 비검사 경고를 제거하라

- 모든 비검사 경고를 제거한다면 그 코드는 타입 안정성이 보장된다. 즉, 런타임에 ClassCastException이 발생할 일이 없고, 우리가 의도한 대로 잘 동작하리라 확신할 수 있다.
- 경고를 제거할 수는 없지만 타입 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨길 수 있다.
- 만약 안전하다고 검증된 비검사 경고를 그대로 두면, 진짜 문제를 알리는 새로운 경고가 나와도 눈치채지 못할 수 있다. 따라서 안전함이 검증됐다면 숨겨주는 습관을 가지도록 하자.
- @SuppressWarnings 애너테이션은 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에도 달 수 있지만 항상 가능한 한 좁은 범위에 적용하는 것이 좋다. (보통 변수 선언, 아주 짧은 메서드 혹은 생성자) 클래스같은 큰 범위에 적용하면 심각한 경고를 놓칠 수 있기 때문이다.
- 한 줄이 넘는 메서드나 생성자에 달린 @SuppressWarnings 애너테이션을 발견하면 지역변수 선언 쪽으로 옮기자.
- 예를 들어,

```java
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elements, size, a.getClass());
System.arraycopy(elements, 0, a, 0, size);
if(a.length > size)
a[size] = null;
return a;
}
```

ArrayList의 toArray 메서드를 컴파일 하면 아래와 같은 경고가 발생한다.

```java
ArrayList.java:305: warning: [unchecked] unchecked cast
return (T[]) Arrays.copyOf(elements, size, a.getClass());
required: T[]
found: Object[]
```

@SuppressWarnings 애너테이션은 선언에만 달 수 있기 때문에 return문에는 달지 못한다. 그렇다면 메서드 자체에 달고 싶겠지만, 범위가 필요이상으로 넓어지게 된다. 그 대신 반환값을 담을 지역변수를 하나 선언하고 그 변수에 @SuppressWarnings 애너테이션을 달아주면 된다.

- _@SuppressWarnings 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다._
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## item32 제네릭과 가변인수를 함께 쓸 때는 신중하라.

- 가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있지만, 구현 방식에서 허점이 있다.

-> 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어지는데, 내부로 감춰야 했을 이 배열이 클라이언트에 노출되는 문제가 생겼다.

즉, _varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 컴파일 경고가 발생한다._

-> varargs 매개변수는 배열이다. 여기에 제네릭 타입을 넣으면 제네릭 배열(JAVA에서 지원하지 않음)이 된다. 배열과 제네릭의 타입 시스템 차이에 의해 타입 안전성이 붕괴되고, 이로 인해 *힙 오염*이 발생된다.

```java
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLIsts;
objects[0] = intList; //힙 오염 발생
String s = stringLists[0].get(0); //ClassCastException
//마지막 줄에 컴파일러가 생성한 (보이지 않는) 형변환이 숨어있기 때문이다.
}
```

- 제네릭 배열을 프로그래머가 직접 생성하는 건 허용하지 않으면서 제네릭 varargs 매개변수를 받는 메서드를 선언할 수 있는 이유가 무엇일까? -> 즉, 왜 오류가 아닌 경고로 끝일까?

제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메서드가 실무에서 매우 유용하기 때문이다. 그래서 언어 설계자는 이 모순을 수용하기로 했다. (실제 자바 라이브러리도 이런 메서드를 여럿 제공한다.)

- 자바 7이전에는 @SuppressWarnings("unchecked")로 일일히 경고를 숨겨야 했지만, 자바 7에서 @SafeVarargs 애너테이션(메서드 작성자가 그 메서드가 타입 안정함을 보장하는 장치)이 추가 되어 작성자가 클라이언트 측에서 발생하는 경고를 숨길 수 있게 되었다.
- 메서드가 안전한지는 어떻게 확신할 수 있을까?

-> 메서드가 이 배열에 아무 것도 저장하지 않고 그 배열의 참조가 밖으로 노출되지 않는다면 타입 안전하다. 바꿔 말해서, 이 varargs 매개변수 배열이 호출자로부터 그 메서드로 순수하게 인수들을 전달하는 일만 한다면 그 메서드는 안전하다.

- 단, 이것이 무조건적으로 옳은 것은 아니다.

예를 들어,

```java
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(b, c);
case 2: return toArray(c, a);
}
throw new AssertionError(); //도달할 수 없다.
}

public static void main(String[] args) {
String[] attributes = pickTwo("좋은","빠른","저렴한");
}
```

- pickTwo의 반환값을 attributes에 저장하기 위해 String[]로 형변환하는 코드를 컴파일러가 자동 생성한다는 점을 놓쳤다. Object[]는 String[]의 하위 타입이 아니므로 이 형변환은 실패한다.

_따라서, 제네릭 varargs 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다!_

- 물론 여기에도 예외 2가지가 있다.

1. @SafeVarargs로 제대로 애노테이트된 또 다른 varargs 메서드에 넘기는 것은 안전하다.
2. 그저 이 배열 내용의 일부 함수를 호출만 하는(varags를 받지 않는) 일반 메서드에 넘기는 것도 안전하다.

- @SafeVarargs 애너테이션을 사용할 때 정하는 규칙 : 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 모든 메서드에 @SafeVarargs를 달아야 한다.(안전하지 않은 varargs 메서드는 절대 작성해서는 안 된다.)

- 정리

1. varages 매개변수 배열에 아무것도 저장하지 않는다.
2. 그 배열(혹은 복제본)을 신뢰할 수 없는 코드에 노출하지 않는다.

***

참고

@SafeVarargs 애너테이션이 유일한 정답이 아니다. varargs 매개변수를 List 매개변수로 바꿀 수도 있다. (아이템 28의 내용)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## item40 @Override 애너테이션을 일관되게 사용하라

- @Override는 프로그래머에게 가장 중요한 애너테이션일 것이다. 이것은 메서드 선언에만 달 수 있고, 이 애너테이션이 달렸다는 것은 상위 타입의 메서드를 재정의했다는 것을 의미한다.
- @Override 애너테이션을 일관되게 사용하면 여러 버그들을 예방해준다. 다음의 Bigram 프로그램을 살펴보자.(바이그램, 여기서는 영어 알파벳 2개로 구성된 문자열을 표현)

```java
public class Bigram {
private final char first;
private final char second;

public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second = second;
}
public int hashCode() {
return 31 * first + second;
}

public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();

for(int i = 0; i < 10; i++)
for(char ch = 'a'; ch <= 'z'; ch++)
s.add(new Bigram(ch,ch));
System.out.println(s.size());
}
}
```

- 위의 코드는 a부터 z까지 26개의 바이그램을 10번 반복해 집합에 추가한 다음, 집합의 크기를 출력하려는 것으로 보인다. Set은 중복을 허용하지 않기 때문에 결과는 26으로 나올길 원했던 것 같다. 하지만 실제로는 260이 출력된다. 그 원인을 하나 하나 살펴보자.

1. 작성자는 equals 메서드를 재정의하려 한 것으로 보이고 hashCode도 함께 재정의해야 한다는 사실을 잊지 않았다. 하지만 equals는 재정의가 아니라 _다중정의_ 해버렸다.
2. Object의 equals를 재정의하려면 매개변수 타입을 Object로 해야했는데 그렇게 하지 않았다. 그래서 Objcet에서 상속한 equals와는 별개인 equals를 새로 정의한 꼴이 됐다.
3. Object의 equals는 ==연산자와 똑같이 객체 식별성만을 확인한다. 따럿 같은 소문자를 소유한 바이그램 10개의 각각이 서로 다른 객체로 인식되고 결과가 260이 된 것이다.

이제 이를 해결하기 위해 equals 메서드에 @Override를 붙이면

```java
Bigram.java:10: method does not override or implement a method from a supertype
@Override public boolean equals(Bigram b)
```

꼴의 컴파일 오류가 발생한다. 앞서 말했듯 매개변수 타입이 Object 타입이어야 하는데 Bigram이므로 오류가 발생한 것이다. 이를 올바르게 수정하면 아래와 같은 형태이다.

```java
@Override
public boolean equals(Object o) {
if(!(o instanceof Bigram)) return false;
Bigram b = (Bigram) o;
return b.first == first && b.second == second;
}
```

- 중요!

_상위 클래스의 메서드를 재정의하려는 모든 메서드에 @Override 애너테이션을 달자!_

예외는 한 가지 뿐이다. 구체 클래스에서 상위 클래스의 추상 메서드를 재정의할 때는 굳이 @Override를 달지 않아도 된다. 물론 재정의 메서드 모두에 @Override를 일괄로 붙여두는게 좋아 보인다면 그래도 상관없다.

- 한편, IDE는 @Override를 일관되게 사용하도록 부추기기도 한다. @Override가 달려있지 않은 메서드가 실제로는 재정의를 했다면 경고를 준다. 또 @Override는 클래스뿐 아니라 인터페이스의 메서드를 재정의할 때도 사용할 수 있다.디폴트 메서드를 지원하기 시작하면서, 인터페이스의 메서드를 구현한 메서드에도 @Override를 다는 습관을 들이면 시그니처가 올바른지 재차 확신할 수 있다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
예전에는 함수 타입을 표현할 떄 추상 메서드를 하나만 담은 인터페이스나 클래스를 사용했다.

이때 이런 인터페이스의 인스턴스를 '함수 객체'라고 부른다.

JDK 1.1 이후에는 익명 클래스를 사용해서 함수 객체를 만들었다.

아래 함수를 익명 클래스를 활용한 정렬 함수의 예이다.
```java
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
```

하지만 코드가 너무 길다.

### 람다식

자바8에 오면서 위의 함수 객체는 람다식을 활용해서 만들 수 있게 되었다.

람다는 익명 클래스와 비슷한 기능을 수행하지만 훨씬 명확하고 간결하게 표현할 수 있다.

```java
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
```

여기서 람다식이나 s1,s2의 타입은 따로 명세해주지 않아도 알아서 타입 추론을 해준다.

코드의 간결함을 위해 타입을 더 명확히 명세해줄 필요가 있거나 컴파일러가 오류를 뱉을때를 제외하면 타입 명세를 생략하는 것이 좋다.

> [!info]
> 컴파일러는 타입을 추론하기 위한 대부분의 정보를 제네릭에서 가져온다. 따라서 람다를 사용해야 한다면 더욱이 로 타입을 지양하고 제네릭을 활용해야 한다.


위의 코드에서 비교자 생성 메서드를 활용하면 코드를 줄일 수 있으며, List 인터페이스의 sort 메서드를 활용하면 더 줄어들 수 있다.
```java
Collections.sort(words, comparinglnt(String::length));

words.sort(comparinglnt(String::length));
```

### 활용

아이템 34에서는 Operation 열거 타입에서 각 상수별 apply 메서드를 재정의했다.
```java
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
}

private final String symbol;

Operation(String symbol) { this.symbol = symbol; }

@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
```
- 이렇게 구현하는 방식보다는 열거 타입에 인스턴스 필드를 두는 방식이 좋다.
- 이때 람다를 활용하면 아래와 같은 형태로 구현할 수 있다.


```java
public enum Operation {
PLUS ("+",(x, y) —> x + y),
MINUS ("-",(x, y) -> x - y),
TIMES ("*", (x, y) —> x * y),
DIVIDE("/", (x, y) -> x / y);

private final String symbol;
private final DoubleBinaryOperator op;

Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}

@Override public String toString() { return symbol; }

public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
```
- 각 상수별 동작을 람다로 구현해 생성자에 넘기면, 생성자는 이 람다를 인스턴스 필드에 저장해둔다.
> [!info]
> DoubleBinaryOperator 인터페이스는 함수 인터페이스 중 하나로, 저장된 람다를 double로 실행할 수 있게 한다.


### 주의점

- 코드 자체로 로직이 설명되지 않거나 코드가 길어진다면 쓰면 안된다.
- 람다는 메서드나 클래스와는 달리 이름이 없으며, 문서화도 할 수 없기 때문
- 만약 3줄을 넘어간다면 더 줄이거나 람다를 쓰지 않는것이 좋다.
- 람다는 인스턴스 필드나 메서드를 사용하지 못한다.
- 추상 클래스의 인스턴스를 만들어야 하거나, 추상 메서드가 여러개인 인터페이스의 인스턴스를 만들 때는 익명 클래스를 사용해야 한다.
- 만약 함수 객체가 자기 자신을 참조해야 한다면 익명 클래스를 활용해야 한다.
- 람다는 자기 자신이 아닌 바깥 인스턴스를 가리키기 때문
- 람다(그리고 익명 클래스)는 직렬화 방식이 VM별로 다르기 때문에 절대 삼가야 한다.
- 만약 직렬화가 필요하다면 private 정적 중첩 클래스를 사용해야 한다.


### 정리
> 람다를 활용하면 작은 함수 객체를 간결하게 구현할 수 있다.
> 다만 항상 람다를 남용해서는 안되며, 상황에 알맞게 람다와 익명 클래스를 활용하자.
Loading