본문 바로가기

JAVA

Effective java 정복기 6장

728x90

아이템_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) { }
}

EnumSet 의 장점

> 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해줌

> Set 인터페이스를 완벽히 구현하며, 타입 안전하고, 다른 어떤 Set 구현체와도 함께 사용할 수 있음

> removeAll(차집합)과 retainAll(교집합)같은 대량 작업은 비트를 효율적으로 처리할 수 있는 산술 연산(비트 필드를 사용할 때 쓰는 것과 같은)을 써서 구현함

> 난해한 작업들은 EnumSet이 다 처리해주기 때문에 비트를 직접 다룰 때 겪는 흔한 오류들로부터 해방됨

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이 포함되어 있

 

 

728x90

'JAVA' 카테고리의 다른 글

Effective java 정복기 4장  (0) 2025.01.09
Effective java 정복기 3장  (1) 2025.01.06
ParallelStream은 무엇일까?  (0) 2024.12.01
스프링 첫요청이 처리되는데 오래 걸리는 이유  (0) 2024.11.25