아이템_35 ordinal 메서드 대신 인스턴스 필드를 사용하라
나쁜예시
public enum Rank {
FIRST, SECOND, THIRD;
public static Rank fromOrdinal(int ordinal) {
for (Rank rank : values()) {
if (rank.ordinal() == ordinal) {
return rank;
}
}
throw new IllegalArgumentException("Invalid ordinal: " + ordinal);
}
}
public class Main {
public static void main(String[] args) {
Rank rank = Rank.fromOrdinal(1);
System.out.println(rank); // SECOND
}
}
- 순서가 변경되면 문제가 발생
- enum 상수의 선언 순서가 변경되면 ordinal() 값이 바뀌어 의도하지 않은 동작이 발생할 수 있음.
- 코드의 명확성이 떨어짐
- ordinal()이 숫자를 반환하지만, 그 숫자가 무엇을 의미하는지 알기 어려움.
나쁜예시
public class Text {
public static final int STYLE_BOLD = 1 << 0; // 1
public static final int STYLE_ITALIC = 1 << 1; // 2
public static final int STYLE_UNDERLINE = 1 << 2; // 4
public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
// 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다.
public void applyStyles(int styles) { ... }
}
아이템_36 비트필드 대신 EnumSet을 이용해라
EnumSet - 비트필드를 대처하기
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
// 어떤 Set을 넘겨도 되나, EnumSet 이 가장 좋다.
public void applyStyles(EnumSet<Style> styles) { }
}
> 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해줌
> Set 인터페이스를 완벽히 구현하며, 타입 안전하고, 다른 어떤 Set 구현체와도 함께 사용할 수 있음
> removeAll(차집합)과 retainAll(교집합)같은 대량 작업은 비트를 효율적으로 처리할 수 있는 산술 연산(비트 필드를 사용할 때 쓰는 것과 같은)을 써서 구현함
> 난해한 작업들은 EnumSet이 다 처리해주기 때문에 비트를 직접 다룰 때 겪는 흔한 오류들로부터 해방됨
> 자바 11까지는 아직 불변 EnumSet을 만들 수 없음
> EnumSet 대신 Collections.unmodifiableSet으로 EnumSet을 감싸 사용할 수 있음
(명확성과 성능이 조금 희생되긴 함)
아이템_37 Oridinal을 이용한 인덱스와 EnumMap
예시
public void putEnumMap() {
Map<Rank, String> lottoMap = new EnumMap<>(Rank.class);
lottoMap.put(Rank.FIRST, "1등");
lottoMap.put(Rank.SECOND, "2등");
lottoMap.put(Rank.THIRD, "3등");
System.out.println(lottoMap);
// {THIRD=3등, SECOND=2등, FIRST=1등}
}
Map을 사용하는데 있어서 만약에 Key 값이 Enum이라면 HashMap 대신 EnumMap을 사용해보는 것을 추천한다. 연산이 많은 작업일 수록 성능적으로 차이를 많이 보이게 될 것이며 추가적으로 Enum 클래스에서 선언한 순서를 보장해주기 때문이다.
아이템_38 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
Operation 인터페이스와 이를 확장하는 ExtendedOperation 열거형을 정의하고, test 메서드를 실행하여 동작을 확인해보자
// Operation 인터페이스 정의
public interface Operation {
double apply(double x, double y);
}
// Enum - 기본 연산 정의
public enum BasicOperation implements Operation {
ADD("+") {
public double apply(double x, double y) { return x + y; }
},
SUBTRACT("-") {
public double apply(double x, double y) { return x - y; }
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
// Enum - 확장 연산 정의
public enum ExtendedOperation implements Operation {
MULTIPLY("*") {
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;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
// 테스트 실행 클래스
public class EnumTest {
public static void main(String[] args) {
double x = 10;
double y = 2;
test(ExtendedOperation.class, x, y); // ExtendedOperation을 테스트
}
public static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
for (Operation op : opEnumType.getEnumConstants()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
}
Operation 인터페이스와 이를 확장하는 ExtendedOperation 열거형을 정의하고, test 메서드를 실행하여 동작을 확인하는 코드입니다.
아이템_40 @Override 애너테이션을 일관되게 사용해라
import java.util.HashSet;
import java.util.Set;
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
// 잘못된 equals 메서드 (Object가 아닌 Bigram을 매개변수로 사용)
public boolean equals(Bigram b) {
return b != null && b.first == first && b.second == second;
}
@Override
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> set = new HashSet<>();
for (int i = 0; i < 10; i++) {
set.add(new Bigram('a', 'b'));
}
System.out.println(set.size()); // 기대값: 1, 실제값: 10
}
}
이 클래스에서 equals 메서드를 재정의하려 했지만, 매개변수 타입을 잘못 지정하여 다중정의(overloading)가 발생했습니다. 이로 인해 Set 컬렉션에 중복된 Bigram 객체가 추가되는 버그가 발생했습니다. 만약 @Override 애너테이션을 사용했다면, 컴파일러가 이러한 실수를 감지할 수 있었을 것입니다.
따라서, 상위 클래스나 인터페이스의 메서드를 재정의하는 모든 메서드에 @Override 애너테이션을 사용하는 것이 좋습니다. 이는 실수를 방지하고 코드의 가독성과 유지보수성을 향상시킵니다. 구체 클래스에서 상위 클래스의 추상 메서드를 재정의할 때는 @Override를 생략할 수 있지만, 일관성을 위해 사용하는 것이 바람직합니다.
@Component, @Service, @Repository, @Controller 같은 어노테이션이 붙은 클래스들은 @ComponentScan을 통해 자동으로 스캔되어 빈으로 등록된다. @SpringBootApplication에 기본적으로 @ComponentScan이 포함되어 있
'JAVA' 카테고리의 다른 글
Effective java 정복기 4장 (0) | 2025.01.09 |
---|---|
Effective java 정복기 3장 (1) | 2025.01.06 |
ParallelStream은 무엇일까? (0) | 2024.12.01 |
스프링 첫요청이 처리되는데 오래 걸리는 이유 (0) | 2024.11.25 |