効果java item 34.int定数の代わりに列挙タイプを使用する

42390 ワード

item34. int定数の代わりに列挙タイプを使用する


列挙タイプは、一定数の定数値を定義します.
他の値が許可されていない時間
ex)四季、太陽系の惑星、カードゲームのカードの種類など
Javaが列挙タイプをサポートする前に
次のコードは、通常整数宣言を使用します.
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 0;
public static final int APPLE_GRANNY_SMITH = 0;


public static final int APPLE_NAVEL = 0;
public static final int APPLE_TEMPLE = 0;
public static final int APPLE_BLOOD = 0;
整数列挙モード(int enum pattern)技術の欠点
  • タイプのセキュリティは保証できません
  • 不振
  • 同等演算子(==)警告メッセージ
  • は出力されません.
    整数ではなく文字列定数を使用した変形モード
  • と呼ばれる文字列列挙モードの変形はさらに悪い.
  • 定数の意味を出力することができるが、
  • の誤字があっても確認できず、当然ランタイムエラー
  • が発生する.
  • 文字列の比較によりパフォーマンスが低下
    列挙タイプ(enum type):列挙モードの欠点を解消し、多様な利点を提供する代替案
    public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
    public enum Orange { NAVEL, TEMPLE, BLOOD }
    最も簡単な列挙タイプ
  • 完全なクラス
  • は、他の言語の列挙タイプよりも強力です.
  • Java列挙タイプをサポートする考え方
  • 列挙タイプ自体がクラスであり、
  • 定数は、共通の静的finalフィールドとして
  • を開示する独自のインスタンスを作成する.
  • 列挙タイプは、外部からアクセス可能なジェネレータを提供しないため、実際にはfinal
  • である.
  • クライアントはインスタンスを作成または拡張できないため、
  • のインスタンスは1つしかありません.
    単一周:1つの要素の列挙タイプのみ
    列挙タイプ列挙タイプ:一輪汎用タイプ
    列挙タイプの利点
  • コンパイル時タイプセキュリティ
  • にはそれぞれの名前空間があり、名前の同じ定数も平和に共存している.
    (列挙タイプに新しい定数を追加するか、再コンパイルする必要はありません)
  • 列挙タイプのtoStringメソッドは、出力に適した文字列
  • を生成する.
  • は、任意の方法またはフィールド
  • を追加することができる.
  • は任意のインターフェース
  • を実現することができる.
  • 対象方法
  • を高品質に実施する.
  • Compareable(プロジェクト14)、シリアル(12章)
  • を実施
    列挙タイプにメソッドまたはフィールドを追加する方法
    例えば、AppleとOrangeです.
    果物の色を教えたり、果物の画像を返したりする方法を追加することができます.
    別の例:
    太陽系の8つの惑星.
    各惑星には質量と半径があり、この2つの属性を利用して表面重力を計算することができます.
    あるオブジェクトの質量を指定すると、そのオブジェクトが惑星の表面にあるときの重量を計算することもできます.
    public enum Planet {
    	MERCURY(3.302e+23, 2.439e6),
        VENUS(4.869e+24, 6.052e6),
        EARTH(5.975e+24, 6.378e6),
        MARS(6.419e+23, 3.393e6),
        JUPITER(1.899e+27, 7.149e7),
        SATURN(5.685e+26, 6.027e7),
        URANUS(8.683e+25, 2.556e7),
        NEPTUNE(1.024e+26, 2.477e7);
        
        private final double mass; //질량(단위: 킬로그램)
        private final double radius; //반지름(단위: 미터)
        private final double surfaceGravity; //표면중력(단위: m / s^2)
        
        //중력상수(단위:m^3 / kg s^2)
        private static final double G = 6.67300E-11;
        
        //생성자
        Planet(double mass, double radius) {
        	this.mass = mass;
            this.radius = radius;
            surfaceGravity = G * mass / (radius * radius);
        }
        
        public double mass()	{ return mass; }
        public double radius()	{ return radius; }
        public double surfaceGravity()	{ return surfaceGravity; }
        
        public double surfaceWeight (double mass) {
        	return mass * surfaceGravity;	// F = ma
        }
    }
    列挙タイプ定数をそれぞれ特定のデータに関連付けるには、ジェネレータからデータを受信し、インスタンスフィールドに格納するだけです.
    あるオブジェクトの地球上の重量を入力し、8つの惑星上の重量のコードを出力します.
    public class WeightTable {
    	public static void main(String[] args) {
        	double earthWeight = Double.parseDouble(args[0]);
            double mass = earthWeight / Planet.EARTH.surfaceGravity();
            for (Planet p : Planet.values())
            	System.out.printf("%s에서의 무게는 %f이다. %n",
                	p, p.surfaceWeight(mass));
        }
    }
    列挙タイプは、配列で定義された定数の値を配列に入れる静的メソッドが返す値を提供します.
    これらの値は宣言の順序で保存されます.
  • java.math.RoundingMode & BigDecimal
    http://cris.joongbu.ac.kr/course/java/api/java/math/class-use/RoundingMode.html
  • 値によるブランチの列挙タイプ
    public enum Operation {
    	PLUS, MINUS, TIMES, DIVIDE;
        
        //상수가 뜻하는 연산 수행
        public double apply(double x, double y) {
        	switch(this) {
            	case PLUS:	return x + y;
                case MINUS :	return x - y;
                case TIMES : 	return x * y;
                case DIVIDE :	return x / y;
            }
           	throw new AssertionError("알 수 없는 연산 : " + this);
        }
    }
    ->見栄えが悪く、ランタイムエラーが発生する可能性があります
    各定数で独自のメソッドを再定義します.
    特定定数を実装する方法
    public enum Operation {
    	PLUS {public double apply(double x, double y){return x + y;}},
        MINUS {public double apply(double x, double y){return x - y;}},
        TIMES {public double apply(double x, double y){return x * y;}},
        DIVIDE {public double apply(double x, double y){return x / y;}},
    
    	public abstract double apply(double x, double y);
    }
    定数メソッド実装を定数データと組み合わせることもできる.
    操作のtoStringを再定義して、その操作を表す記号の例を返します.
    public enum Operation {
    	PLUS("+") {
        	public double apply(double x, double y) { return x + y; }
        },
        MINUS("-") {
        	public double apply(double x, double y) { return x - y; }
        },
        TIMES("*") {
        	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;
        
        Operation(String symbol) { this.symbol = symbol; }
        
        @Override public String toString() { return symbol; }
        public abstract double apply(double x, double y);
    }
    TOStringは計算出力をどんなに便利にするか.
    public static void main(String[] args) {
    	double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
        	System.out.printf("%f %s %f = %f%n",
            	x, op, y, op.apply(x, y));
    }
    列挙タイプは、定数名を入力し、名前に対応する定数を返すValueOf(String)メソッドを自動的に生成します.
    すべての列挙タイプで使用するためにfromStringが実装された例.(toString再定義メソッド)-タイプ名が正しく、すべての定数の文字列が一意であることを確認します.
    private static final Map<String, Operation> stringToEnum = 
    	Stream.of(values()).collect(
        	toMap(Object::toString, e->e));
            
    //지정한 문자열에 해당하는 Operation을 (존재한다면) 반환한다.
    public static Optional<Operation> fromString(String symbol) {
    	return Optional.ofNullable(stringToEnum.get(symbol));
    }
    StringToEnumマッピングに定数を追加した時点を開く
    :列挙タイプ定数を作成して静的フィールドを初期化する場合.
    列挙タイプの静的フィールドでは、列挙タイプの作成者は定数変数にのみアクセスできます.(アイテム24)
    異なる列挙タイプの定数ではコードを共有することは困難である.
    給与計算書
    リストされているタイプは、社員の基本給(1時間あたり)とその日の勤務時間(分単位)で、日給を計算する方法です.
    1週間でタイムアウトが発生すると残業代が発生し、
    週末は無条件に残業代を払う.
    enum PayrollDay {
    	MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
        
        private static final int MINS_PER_SHIFT = 8 * 60;
        
        int pay(int minutesWorked, int payRate) {
        	int basePay = minutesWorked * payRate;
            
            int overtimePay;
            switch(this) {
            	case SATURDAY : case SUNDAY; //주말
                	overtimePay = basePay / 2;
                    break;
                defaul : //주중
                	overtimePay = minutesWorked <= MINS_PER_SHIFT ?
                    	0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
            }
            
            return basePay + overtimePay;
        }
    }
    簡潔ですが、管理の観点からは危険なコードです.
    休暇などの新しい値を列挙タイプに追加する場合は、その値を処理するcase文を忘れずに、ペアで挿入します.
    定数メソッドによる給与の正確な計算方法
  • 残業代を計算するコードをすべての定数
  • に繰り返し入れる.
  • 計算コードを平日と週末の2つの部分に分けて、それぞれ「ヘルプ」メソッドを記述し、各定数に応じて自分の必要なメソッド
  • を呼び出す.
    ->どちらの方法もコードが冗長になり、コードの可読性が低下し、エラーの可能性が増加します.
    最も簡潔な方法
    つまり、
  • の新たなボーナスを追加する場合、残業代を選択する「戦略」である.
    残業代の計算->privateネスト列挙タイプへの移行(PayType)
    PayrollDay列挙タイプのジェネレータから適切な
  • を選択
    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 minsWorked <= MINS_PER_SHIFT ? 0 :
                    	(minsWorked - MINS_PER_SHIFT) * payRate / 2;
                }
            },
            WEEKEND {
            	int overtimePay(int minsWorked, int payRate) {
                	return minsWorked * payRate / 2;
                }
            };
            
            abstract int overtimePay(int mins, int payRate);
            private static final int MINS_PER_SHIFT = 8 * 60;
            
            int pay(int minsWorked, int payRate) {
            	int basePay = minsWorked * payRate;
                return basePay + overtimePay(minsWorked, payRate);
            }
        }
    
    }
    追加したいメソッドが列挙タイプに該当しない場合は、自分で作成した列挙タイプでもこのような方法を採用するべきです.
    既存の列挙タイプで定数に特化した操作をブレンドする場合は、スイッチ文を選択します.
    public static Operation invers(Operation op) {
    	switch(op) {
        	case PLUS : return Operation.MINUS;
            case MINUS : return Operation.PLUS;
            case TIMES : return Operation.DIVIDE;
            case DIVIDE : return Operation.TIMES;
            default : throw new AssertionError("알 수 없는 연산 : " + op);
        }
    }
    だから私にいつ列挙タイプを使わせたいですか?
    必要な要素が定数のセットである場合、コンパイル時に知ることができます.
    常に列挙タイプを使用
    太陽系惑星は、1週間の間、チェスの言うように本質的にリストされているタイプです.
    メニュー項目、演算コード、コマンドラインタグなど、コンパイル時に許可されているすべての値を書き込むことができます.
    列挙タイプで定義された定数個数は、常に一定である必要はありません.
    コアの整理
    列挙タイプは確かに整数定数より優れている.
    読みやすく、より安全で、より強力です.
    ほとんどの列挙タイプでは、明示的な構造関数やメソッドは使用されませんが、各定数を特定のデータに関連付けたり、各定数に異なる動作を与えたりする場合に必要です.
    1つの方法は定数によって異なる場合がある.
    この列挙タイプでは,switch文ではなく定数固有の方法で実現する.
    列挙タイプ定数の一部が同じ動作を共有する場合、ポリシー列挙タイプモードが使用されます.