Java ‐ int 상수 대신 열거 타입을 사용하라[Effective Java Item 34] - dnwls16071/Backend_Summary GitHub Wiki

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

public static final int name = 0;
public static final int departmentName = 1;
public static final int companyName = 2;
public static final int phoneNumber = 3;
public static final int email 4;
  • 정수 열거 패턴 기법(int enum pattern)에는 단점이 있다.
    • 타입 안전을 보장할 수 없다.
    • 표현력도 좋지 않다.
    • 동등 연산자로 비교하더라도 컴파일러가 아무런 경고 메시지를 출력하지 않는다.
    • 컴파일하면 그 값이 클라이언트 파일에 그대로 새겨지기에 상수 값이 바뀌게 되면 클라이언트도 반드시 다시 컴파일해야만 한다.
  • 이처럼 정수 열거 패턴(int enum pattern)뿐만 아니라 문자열 열거 패턴(string enum pattern) 기법도 있는데, 이 방식은 더 나쁘다.
  • 상수의 의미를 출력할 수 있다는 점에서는 좋으나 하드코딩한 문자열에 오타가 있어도 컴파일러가 확인할 길이 없기에 자연스럽게 런타임 버그가 발생하게 된다. 문자열 비교에 따른 성능 저하 역시 당연한 결과이다.
public static final String blank = "";
public static final String minus = "-";
public static final Stirng atSign = "@";
  • 이러한 열거 패턴을 해결해주는 대안이 바로 EnumType이다.

EnumType

  • 클래스이다.
  • 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.
  • 밖에서 접근할 수 있는 constructor를 제공하지 않기 때문에 인스턴스를 직접 생성하거나 확장 할 수 없기 때문에 EnumType 선언으로 만들어진 인스턴스 들은 딱 하나만 존재함을 보장한다.
  • SingleTon은 원소가 하나뿐인 EnumType이라 할 수 있고, 거꾸로 EnumType은 SingleTon을 일반화한 형태라고 볼 수 있다.
  • 컴파일타임 안정성을 제공한다.
  • 각자의 이름공간이 있어서 이름이 같은 상수도 평화롭게 공존한다.
  • EnumType의 toString 메서드는 출력하기에 적합한 문자열을 내어준다.
  • 추가로 EnumType은 임의의 메서드나 필드를 추가할 수 있고, 임의의 인터페이스를 구현하게 할 수도 있다.

EnumType 예제 코드

public enum IPhone {
    IPONE11(6.1, "A13", 1200),
    IPONE11PRO(5.8, "A13", 1200),
    IPONE11PROMAX(6.5, "A13", 1200),
    IPONESE(4.7, "A13", 700),
    IPONE12(6.1, "A14", 1200),
    IPONE12MINI(5.4, "A14", 1200),
    IPONE12PRO(6.1, "A14", 1200),
    IPONE12PROMAX(6.7, "A14", 1200);

    private final double inch;
    private final String process;
    private final int cameraPixel; 
	
    prvate static final double centimeter = 2.54;
	
    IPone(double inch, String process, int cameraPixel) {
	this.inch = inch;
	this.process = process;
	this.cameraPixel = cameraPixel;
    }

    public double inch();        // private으로 변경 필요
    public String process();     // private으로 변경 필요
    public int cameraPixel();    // private으로 변경 필요

    public double calcurateInchToCentimeter(doublic inch) {
	return inch * centimeter;
    }
}
  • EnumType을 각각 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 초기화해 인스턴스 필드에 저장하면 된다.
  • 이 때, EnumType은 근본적으로 불변이라 모든 필드는 final이어야 한다.
  • 필드를 public으로 선언해도 되지만, private으로 두고 별도의 public 접근자 메서드를 두는게 낫다.

values

  • EnumType은 자신 안에 정의된 상수들의 값을 배열에 담아 반환하는 정적 메서드인 values를 제공한다.

상수별 메서드 구현(constant-specific method implementaltion)

public enum Operation {
    PLUS("+") {
        public douvle apply(double x, double y) { return x + y };
    },

    MINUS("-") {
	public douvle apply(double x, double y) { return x - y };
    },

    TIMES("*") {
	public douvle apply(double x, double y) { return x * y };
    },

    DIVIDE("/") {
	public douvle 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);
}
  • EnumType은 상수별로 다르게 동작하는 코드(=추상 메서드)를 구현하는 더 나은 수단을 제공한다.

전략 열거 타입 패턴

enum PayrollDay {  
    MONDAY(WEEKDAY),  
    TUESDAY(WEEKDAY),  
    WEDNESDAY(WEEKDAY),  
    THURSDAY(WEEKDAY),  
    FRIDAY(WEEKDAY),  
    
    SATURDAY(WEEKEND),  
    SUNDAY(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 minutesWorked, int payRate) {  
	        return minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;  
	    }  
        },  
    
	WEEKEND {  
	    int overtimePay(int minutesWorked, int payRate) {  
	        return minutesWorked * payRate / 2  
	    }  
        };  
    
	abstract int overtimePay(int minutesWorked, int payRate);  
	private static final int MINS_PER_SHIFT = 8 * 60;  
    
	int pay(int minutesWorked, int payRate) {  
	    int basePay = minutesWorked & payRate;  
	        return basePay + overtimePay(minutesWorked, payRate);  
	}  
    }  
}
  • 열거 타입 상수 일부가 같은 동작을 공유한다면 매번 추가하기보다 해당 패턴을 사용하는게 수정을 최소화하자.