(Effective Java) 6장 enum 타입과 애너테이션 (Annotation)
[Languages & Frameworks/Kotlin Java] - [Java] Enum
아이템 34. int 상수 대신 enum 타입을 사용하라
- Planet.java
- 자바에서 enum을 뒷받침하는 아이디어는 다음과 같다. enum 타입 자체는 클래스이며, 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.
1
2
3
4
5
public enum Planet {
MERCURY(3.3, 2.4);
이는 곧
public enum Planet {
public static final Planet MERCURY = new Planet(3.3, 2.4);
- enum 상수는 값 뿐만 아니라 로직도 가지고 있을 수 있다! abstract를 이용해서.
- 위 예제에서는 그냥 abstract method를 사용했는데, lambda를 쓰면 더 깔끔하다!!
- OperationWithLambda.java
- 단, 람다 코드로 명확히 동작을 알 수 없거나 코드 줄 수가 많아지면 람다를 쓰지 않는게 좋다.
- 람다 내에서 인스턴스 멤버에 접근해야 하는 경우 람다 대신 abstract method를 사용해야 한다.
- enum 생성자에 넘겨지는 인수 타입은 컴파일 타임에 추론된다. 반면 인스턴스는 런타임에 만들어진다. 때문에 람다 내에서 인스턴스 멤버에 접근할 수 없다.
- 아래처럼 enum 값에 따라 로직이 달라야 된다고 하여 switch로 분기하는건 좋지 않다.
- 유지보수가 어렵다. enum 타입은 추가했는데 case문을 빼먹는 경우가 생길 수 있기 때문.
- strategy enum 패턴 으로 해결.
1
2
3
4
5
6
7
8
9
10
11
12
int pay(int minutesWorked, int payRate) {
int basePay = minutesWorked * payRate;
int overtimePay;
switch(this) {
case SATURDAY: case SUNDAY:
...
default:
...
}
return basePay + overtimePay;
}
- 다만, switch를 쓰는게 제일 깔끔한 경우도 있다.
- Inverse.java Operation.java
- 역연산자를 Operation에 넣자니, Operation의 핵심과는 거리가 좀 있는 부차적인 정보로 보여 Inverse로 별도 분리한 듯.
- 그러나 inverse() 메서드를 Operation에 넣어도 상관 없어보이긴 한다. 유지보수 측면에서도 그렇고.
- 만약 switch를 안쓴다면 PLUS(MINUS) 처럼 필드로 지정해버려야 하는데, 이건 Operation의 응집도를 더 해친다.
아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라
ordinal()
메서드는 해당 상수가 그 enum에서 몇 번째 위치인지를 반환한다.- 그래서 상수 위치가 변경되면 ordinal 값도 바뀐다! 또는 불연속적인 값이면 중간에 dummy 상수를 넣어야 하는 등 좋지 않다.
- ordinal 쓰지 말고 그냥 숫자 필드 하나 파서 쓰자.
아이템 36. 비트 필드 대신 EnumSet을 사용하라
- 비트 필드란
text.applyStyles(BOLD | ITALIC)
이런 식으로 C에서 많이 쓰던 그 것- 비트 필드 값을 해석하기 어렵다
- 비트 필드의 모든 원소를 순회하기 까다롭다
- API를 수정하지 않고는 비트 수를 늘릴 수 없다
- 그래서 대신 EnumSet을 쓰자!
아이템 37.
enum을 키로 사용하여 key-value 매핑 해야 하는 경우 EnumMap을 사용하라
- 원래 제목은 “ordinal 인덱싱 대신 EnumMap을 사용하라”
- Plant.java 를 보면서 읽기 바람.
Plant
를enum LifeCycle
별로 구분해서 묶으려고 하는 상황이다.- LifeCycle 개수 만큼의 Set<>[]을 만들고 ordinal()로 구분하여 인덱싱을 하겠다는 코드 는 딱 봐도 허술해 보인다.
- enum을 쓰면 되는데 굳이 정수로 돌릴 필요가 없다.
- 잘못된 정수가 들어가면 ArrayIndexOutOfBoundsException이 발생하거나, 예상치 않게 동작할 가능성도 있다.
- EnumMap을 써서 개선한 코드
- 스트림은 사용하지만 EnumMap이 아니라 고유 맵 구현체를 사용하는 코드
- 스트림을 사용하면서 EnumMap을 사용하는 코드
- iteration 코드와 다른 점은 해당 LifeCycle에 속하는 Plant가 하나도 없는 경우, 맵이 생성되지 않는다는 점이다.
- iteration 코드는 일단 개수만큼 맵을 만들고 시작하니 속하는 Plant가 없어도 모든 맵이 존재한다.
- Phase.java (상전이) + 중첩 EnumMap으로 상전이를 표현 + 보통은 이런 문제는 아래 같이 2차원 배열을 써서 해결해야지, 싶은 생각이 제일 먼저 든다. 허나 이는 좋지 못하다!
1
2
3
4
5
6
7
8
9
10
11
12
public enum Transition {
// 아래는 나쁜 코드 예시. 대신 EnumMap을 쓰자.
private static final Transition[][] TRANSITIONS = {
{ null, MELT, SUBLIME },
{ FREEZE, null, BOIL },
{ DEPOSIT, CONDENSE, null }
};
public static Transition from(Phase from, Phase to) {
return TRANSITIONS[from.ordinal()][to.ordinal()];
}
}
아이템 38. 확장할 수 있는 enum 타입이 필요하면 인터페이스를 사용하라
- 매개변수 같은 것 받을 때 enum 타입을 그대로 쓰지 말고, interface로 받아라.
- enum을 확장해야 하는 경우는 잘 없지만, 위와 같은 연산자(+-/*)가 예가 될 수 있음.
- interface Operation.java
- enum BasicOperation.java
- enum ExtendedOperation.java
- 근데 인터페이스만 공유하는거라 ExtendedOperation을 순회해도 BasicOperation이 가지고 있는 연산자들은 나오지 않는다. 타입 호환이 가능하다는 점에서 의미가 있음
아이템 39. 명명 패턴보다 애너테이션을 사용하라
- 명명 패턴이란, 테스트 메서드인 경우 이름을
testXXX
로 시작하도록 한다거나… 하는 것을 말한다. - 이름을 통해 쓰임을 지정하는건 너무 느슨한 방식이다. 우리에게는 애너테이션이 있으니까 이걸 쓰는게 더 낫다.
- 상새 내용은 책 참고. 애너테이션을 새로이 정의할 때 참고할만 하다.source
아이템 40. @Override 애너테이션을 일관되게 사용하라
- 바디가 이미 있는 메서드를 재정의할 때는 당연히 애너테이션을 달아야만 함.
- 추상 메서드를 정의할 때는 애너테이션을 달아도 되고 안달아도 되지만, 일반적으로 그냥 다는게 더 좋은 것 같다.
아이템 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
- 아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 마커 인터페이스(marker interface)라 한다
Serializable
인터페이스가 그 예다.- 애너테이션이 있는데 마커 인터페이스를 사용해야 하는 경우? 있다. 바로 인터페이스를 매개변수 등 타입으로 쓸 때.
- 해당 마커 인터페이스를 구현한 클래스들을 인터페이스 타입으로 처리할 수 있다. 애너테이션은 이게 불가함.
This post is licensed under CC BY 4.0 by the author.