Enum

enum.png

enumとは

ユーザが指定した一連の値を保持できる型である。
定義後の列挙は、整数型と非常によく似た使われ方をする。
Javaの場合はenumは立派なクラスである。
public static final メンバを通して、個々の列挙定数に対して一つのインスタンスを公開しているクラスである。[1]

enumを利用するとき

C++の場合、enum変数を簡単に文字列にする方法はないので、文字列と数値の組み合わせの目的で、速度はちょっとぐらい遅くてもいいならmapの方がおすすめ

enumの最大値と最小値

たとえば

enum e{a=3,b=9};

と書いたら、このenumの範囲は0~15になる。
enumの中の最大値よりも大きい最小の2の累乗-1
が最大値である。
最小値はどうやってきまるかというと、
最小値が負数でないは0になる。
最小値が負数の場合は最大値にそのままマイナスつけた値-1である。

enumの型変換

この方法が楽

scast<EnumType1>(enumtype2);

こういう方法もある
enum flag{x=1,y=2,z=4,e=8};
flag f1 = 5;//NG
flag f2=flag(5);//ok
flag f4=flag(99);//NG 99はflagの範囲ではない。

enumの書き方

enumについては5種類の書き方があり、

  1. enum E { e1, e2 };
  2. enum E { e1, e2 } v;
  3. enum { e1, e2 } v;
  4. typedef enum E { e1, e2 } Ea;
  5. typedef enum { e1, e2 } Ea;

(1)は標準的な"型定義のみの"記法です。

(2)は(1)の効果に加えて、変数宣言も同時に行うものです。このときvの型はEです。

(3)は変数宣言を行う記法です。このときはvの型は"無名"になり、プログラム中でvの型を記述することはできません。

(4)は型定義と型の別名宣言を同時に行う書式です。Eの別名Eaを宣言しています。これはenum Eと書かずにE2に書けるようにするためで、単に記述がしやすくなることを狙ったものです。(structの時と同じ。)

(5)は(4)から型の名前を除いたものです。enumのオリジナルの型は無名になってしまいますが、(3)と違い、E2として型名を参照できるので、プログラム中で無名型をE2として再利用することが出来ます。

enumの要素数を把握するテクニック

enum PALETTE{RED,BLUE,GREEN,NUM_COLOR};

一番最後の要素をNUM_XXXにすれば要素数が把握できるよ
後から要素を増やしてもok

メンバのenum

これってpublicにすべき?
複数のクラスで共通だからstaticにすべきかな。

intからenumに変換できないといわれる。

static_castしよう

static_cast<enumの名前>(int型変数);

Javaのenum

良い所まとめ

  • 定数名が印刷できる
  • .values()で配列になれる

javaのenumはクラス

たとえば、enum用の最小のクラスを作るとこうなる
javaのenumはEnumというオブジェクトを継承している。

public enum SailorMoon{
        MOON("月野うさぎ"),
        MARS("火野レイ"),
        MERCURY("水野亜美"),
        JUPITER("木野真琴"),
        VENUS("金野美奈子");
        private final String mName;
        //コンストラクタ
        SailorMoon(String name){
            mName=name;
        }
 
        public String fullname(){return mName;}
}

javaのenumでは定数名が印刷できる! values()で配列になれる!

たとえばこんなふうに、enum変数を直接印刷しようとしたらこんな結果になる

class Main {
    public static void main(String[] args) {
        for(SailorMoon m:SailorMoon.values()){
            System.out.printf("変数名:%s 値:%s%n",m,m.fullname());
        }
    }
}

出力結果
変数名:MOON 値:月野うさぎ
変数名:MARS 値:火野レイ
変数名:MERCURY 値:水野亜美
変数名:JUPITER 値:木野真琴
変数名:VENUS 値:金野美奈子

デバッグに便利だ。

enumのタイプによって違う関数の中身を実装したい場合。

出力に使ったコードはこちら。

class Main {
    public static void main(String[] args) {
        double x=10.0;
        double y=2.0;
        for(Operation op:Operation.values()){
            System.out.printf("%f %s %f =%f%n", x,op,y,op.apply(x, y));
        }
    }
}

よくない

良い

更に良い

public enum Operation {
    PLUS,MINUS,TIMES,DIVIDE;
    //定数で表される算術操作を行う
    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("Unknown op: "+this);
    }
}
public enum Operation {
    PLUS{double apply(double x,double y){return x+y;}},
    MINUS{double apply(double x,double y){ return x-y;}},
    TIMES{double apply(double x,double y){return x*y;}},
    DIVIDE{double apply(double x,double y){return x/y;}};
 
    abstract double apply(double x,double y);
 
}
public enum Operation {
    PLUS("+"){
        double apply(double x,double y){return x+y;}
    },
    MINUS("-"){
        double apply(double x,double y){ return x-y;}
    },
    TIMES("×"){
        double apply(double x,double y){return x*y;}
    },
    DIVIDE("÷"){
        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;}
    abstract double apply(double x,double y);
 
}
出力結果
10.000000 PLUS 2.000000 =12.000000
10.000000 MINUS 2.000000 =8.000000
10.000000 TIMES 2.000000 =20.000000
10.000000 DIVIDE 2.000000 =5.000000
10.000000 PLUS 2.000000 =12.000000
10.000000 MINUS 2.000000 =8.000000
10.000000 TIMES 2.000000 =20.000000
10.000000 DIVIDE 2.000000 =5.000000
10.000000 + 2.000000 =12.000000
10.000000 - 2.000000 =8.000000
10.000000 × 2.000000 =20.000000
10.000000 ÷ 2.000000 =5.000000
給料計算の例
public enum PayrollDay {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY;
    private static final int HOURS_PER_SHIFT=8;
    double pay(double hoursWorked,double payRate){
        double basePay=hoursWorked*payRate;
        double overtimePay;//残業手当を計算
        switch(this){
            case SATURDAY:case SUNDAY:
                overtimePay=hoursWorked*payRate/2;
                break;
            default://平日
                overtimePay=hoursWorked<=HOURS_PER_SHIFT? 0 : (hoursWorked-HOURS_PER_SHIFT)*payRate/2;
                break;
        }
        return basePay+overtimePay;
 
    }
 
}

これの欠点とは?
特別な場合をcaseに追加し忘れること。
public enum PayrollDay {
    MONDAY(PayType.WEEKDAY),TUESDAY(PayType.WEEKDAY),
    WEDNESDAY(PayType.WEEKDAY),THURSDAY(PayType.WEEKDAY),
    FRIDAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND),SUNDAY(PayType.WEEKEND);
 
    private final PayType payType;
 
    PayrollDay(PayType payType){this.payType=payType;}
 
    double pay(double hoursWorked,double payRate){
        return payType.pay(hoursWorked,payRate);
 
    }
    private enum PayType{
        WEEKDAY{
            double overtimePay(double hours,double payRate){
                return hours<=HOURS_PER_SHIFT ?  0: (hours-HOURS_PER_SHIFT)*payRate/2;
            }
        },
        WEEKEND{
            double overtimePay(double hours,double payRate){
                return hours*payRate/2;
            }
        };
        private static final int HOURS_PER_SHIFT=8;
        abstract double overtimePay(double hrs,double payRate);
        double pay(double hoursWorked,double payRate){
            double basePay=hoursWorked*payRate;
            return basePay+overtimePay(hoursWorked,payRate);
        }
    }
}

さらなる進化版はこうなる。何が良くなったのかはよくわからない

import java.util.HashMap;
import java.util.Map;
 
public enum Operation {
 
    PLUS("+"){
        double apply(double x,double y){return x+y;}
    },
    MINUS("-"){
        double apply(double x,double y){ return x-y;}
    },
    TIMES("×"){
        double apply(double x,double y){return x*y;}
    },
    DIVIDE("÷"){
        double apply(double x,double y){return x/y;}
    };
    private static final Map<String,Operation> stringToEnum=new HashMap<String,Operation>();
    static{//定数名からenum定数へのマップを初期化
        for(Operation op:values())
            stringToEnum.put(op.toString(),op);
    }
    private final String symbol;
    Operation(String symbol){this.symbol=symbol;}
    @Override public String toString(){return symbol;}
    abstract double apply(double x,double y);//忘れずに実装してね★のためのabstract
    //文字列に対するOperationを返す。文字列が不正ならnullを返す
    public static Operation fromString(String symbol){
        return stringToEnum.get(symbol);
    }
}

自分は何番目なのか? 確認するのにordinal()を使用してはいけない

保守が大変な良くない例

保守が容易な良い例

package enumclass;
 
public enum Ensemble {
    SOLO,DUET,TRIO,QUARTET,QUINTET,
    SECTET,SEPTET,OCTET,NONET,DECTET;
    public int numberOfMusicians(){return ordinal()+1;}
 
}
package enumclass;
 
public enum Ensemble {
    SOLO(1),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
    SEXTET(6),SEPTET(7),OCTET(8),DOUBLE_QUATET(8),
    NONET(9),DECTET(10),
    TRIPLE_QUATET(12);
    private final int numberOfMusicians;
    Ensemble(int size){this.numberOfMusicians=size;}
    public int numberOfMusicians(){return numberOfMusicians+1;}
 
}
import enumclass.Ensemble;
class Main {
    public static void main(String[] args) {
        double x=10.0;
        double y=2.0;
        Ensemble e=Ensemble.DUET;
            System.out.printf("自分は何番目?=%d",e.numberOfMusicians());
    }
}

javaのEnumクラスを配列の引数として使うには?

ordinal()が便利

public class Main {
    public static void main(String[] args) {
        String[] hello={"a","n","becky","g","oo","BuzzFeed"};
        System.out.println("数:"+MiffyType.NUM+" "+MiffyType.NUM.ordinal());
        System.out.print(hello[MiffyType.NUM.ordinal()]);
    }
}

配列で走査する

for(MiffyType  mt:MiffyType.values()){
            System.out.println(mt);
        }
Bibliography

サポートサイト Wikidot.com