이펙티브자바 정리 5

이봐요이상해씨·2022년 1월 5일
0

목록 보기
5/6

34. int 상수 대신 열거 타입을 사용하라

  • 열거 타입은 클래스이며, 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개함
  • 열거타입은 final(밖에서 접근하는 생성자를 제공하지 않음으로) → 인스턴스는 딱 하나씩 밖에 존재하지 않음을 보장
  • 열거 타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면 된다

가장 좋은 방법

전략 열거 타입 패턴

package com.springandjava.Test.effectivejava.chatper6.item34;

public enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) {
        this.payType = payType;
    }

    int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
    }

//전략 열거 타입
    enum PayType {
        WEEKDAY {
            int overtimePay(int minsWorked, int payRate) {
                return minsWroked <= MINS_PER_SHIFT ? 0:
                        (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            }
        },

        
        WEEKEND {

        };

        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;

    }
}

열거타입을 쓰는 경우?

  • 필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합일 경우
  • 열거 타입에 정의된 상수 갯수가 영원히 고정 불변일 필요는 없다

35. ordinal 메서드 대신 인스턴스 필드를 사용하라

Ordinal 메서드?

  • 해당 상수가 그 열거 타입에서 몇번째 위치인지를 반환함

잘못된 방법

public enum Ensemble {
	SOLO, DUET

	public int numberofMusicians(){
		return ordinal() + 1;
	}
}

ordinal 메서드를 이용하게 되면 열거 순서가 바뀌는 경우, 상수 추가, 중간에 값을 비워둘 수가 없다

해결책

  • 열거 타입 상수에 연결된 값은 ordinal 메서드로 얻지말고 인스턴스 필드에 저장
public enum Ensemble {

    SOLO(1), DUET(2), TRIO(3);

    private final int numberOfMusicians;

    Ensemble(int size){
        this.numberOfMusicians = size;
    }

    public int numberOfMusicians(){
        return numberOfMusicians;
    }
}

36. 비트 필드 대신 EnumSet을 사용해라

public class Text_old {
    public static final int STYLE_BOLD = 1 << 0;
    public static final int STYLE_ITALIC = 1 <<1;

    public void applyStyles(int styles){};
}

다음과 같이 비트별 OR을 사용해서 여러 상수를 하나의 집함으로 모으며, 이렇게 만들어진 집합을 비트 필드라 한다 → 이렇게 작서하게되면 해석하기가 어려움

import java.util.Set;

public class Enumset {
    public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
    
    public void applyStyles(Set<Style> styles){}
    
}

EnumSet.of(Style.BOLD, Style.Italic)

다음과 같이 현대적 기법을 사용해서 EnumSet을 수정

37. ordinal 인덱싱 대신 EnumMap을 사용해라

Enum 활용사례 3가지

EnumMap(EnumSet) 쓰면 좋을까? (vs HashMap)

public enum Phase {
    SOLID, LIQUID, GAS;

    public enum Transition {
        MELT(SOLID, LIQUID);

        private final Phase from;
        private final Phase to;

        Transition(Phase from, Phase to){
            this.from = from;
            this.to = to;
        }

        private static final Map<Phase, Map<Phase, Transition>>
        m = Stream.of(values()).collect(groupingBy(t -> t.from, ()-> new EnumMap<>(Phase.class),
                toMap(t ->t.to, t -> t,(x,y) -> y, () -> new EnumMap<>(Phase.class))));

        public static Transition from(Phase from, Phase to){
            return m.get(from).get(to);
        }
    }
}
  • EnumMap 특징
    • 키가 Enum값이다
    • 내부에서 배열을 사용한다 → 배열의 성능과 타입 안정성

38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용해라


//인터페이스를 정의하고
public interface Operation {
    double apply (double x, double y);
}

//인터페이스를 구현하면서 enum확장이 가능
public enum BasicOperation implements Operation{
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    };

    private final String symbol;

    //this.symbol = 이 클래스 기반으로 생성된 인스턴스 // 매개변수, 맴버변수와 이름 구별
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

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

인터페이스를 선언하고 해당 인터페이스를 구현하는 enum 클래스를 만듦으로서 enum확장이 가능한 것처럼 구현할 수 있다.

39. 명명 패턴보다 애너테이션을 사용해라

명명패턴의 단점

  1. 오타가 나면 안된다
  2. 올바른 프로그램 요소에서만 사용되리라는 보증이 없다
  3. 포로그램 요소를 매개변수로 전달할 마땅한 방법이 없다
//Test가 런타임에도 유지되도록 하게끔 선언하는 에너테이션
@Retention(RetentionPolicy.RUNTIME)

//Test가 반드시 메서드에 선언에서만 사용된다고 알려줌
@Target(ElementType.METHOD)
public @interface Test {

}

40. @Override 애노테이션을 일관되게 사용하라

@Override 는 매서드 선언에서만 달 수 있으며, 이 애너테이션은이 달렸다는 것은 상위 타입의 매서드를 재정의 했음을 뜻한다

재저으이한 모든 매서드에 @Override 어노테이션을 달게되면 컴파일라가 잘못된 오류를 자동적으로 잡아자ㅜㄹ 것이다

41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용해라

아무 메서드도 없고, 자신을 구현하는 클래스가 특정 속성을 갖고 있음을 표시해줄 경우

→ 마커 인터페이스를 사용

마커인터페이스가 마커 애너테이션보다 나은 이유

  1. 마커 인터페이스는 구현한 클래스의 인스턴스를 구분하는 타입으로 쓸 수 있음(어노테이션은 그렇지 못함)
    1. Serializable의 경우가 그 예가 될 수 있다. → 컴파일시 오류 검출이 가능함
  2. 적용 대상을 더 정밀하게 지정할 수 있음
    1. @Target을 ElementType.TYPE 으로 선언한 애너테이션은 모든 타입에 달 수 있다.

적용 대상이 ElementType.TYPE인 애너테이션을 작성하고 있다면 마커인터페이스도 고려해라

0개의 댓글