web-dev-qa-db-ja.com

列挙型に次の操作がないのはなぜですか?

JavaやC#などの最も一般的なプログラミング言語では、enumsを定義する方法があります。これは基本的に、DayOfWeekなどの値の固定セットを持つデータ型です。

問題は、たとえば、 DayOfWeek.Mondayenumの次の値を取得するにはどうすればよいですか、この特定の場合ではDayOfWeek.Tuesday

すべてのenumsが順序付けられているわけではなく、それらにはさまざまな種類の順序(周期的、部分的など)があるかもしれませんが、ほとんどの場合、単純なnext操作で十分です。実際、enumの値のセットは固定され、制限されているため、言語にそうする手段があると仮定すると、これは完全に宣言的に行うことができます。しかし、ほとんどのプログラミング言語では、それは単に不可能であり、少なくとも理論的にはそう簡単にはできません。

だから、私の質問は:なぜそうなのか? nextenum値を宣言するための単純な構文を提供しない理由は何ですか? C#またはJavaの場合、これは特別なattributeまたはannotationで行うこともできます)ですが、何もありません。私の知る限り。

私は明示的に求めていません回避策です。私は代替策があることを知っています。そもそもなぜ回避策を採用しなければならないのか知りたいだけです。

7
proskor

列挙型に次の操作がないのはなぜですか?

一般化するのは難しいです。

これは、各プログラミング言語のデザイナー/チームが下した決定です。ただし、一部のプログラミング言語doは、列挙型に対して「次の」操作を提供することに注意してください。

Pascalでは、succ関数は列挙型の次の値を返し、pred関数は前の値を返します。しかし、これは古典的な列挙型に対してのみ機能します。 Cスタイルの列挙型には型ドメインに「穴」があり、succ関数とpred関数は使用できません。

リファレンス: http://www.freepascal.org/docs-html/ref/refse12.html#QQ2-27-32

そして実際、これは、列挙型/列挙型を持つ多くの言語に「次の」操作がない理由についての手掛かりを与えます。内部で整数加算を使用して「next」を実装できない場合、これは法外に高価です。


もちろん、nextを直接サポートしていない言語では、それを自分で実装することは常に可能です。必要な場合は、なんらかのヘルパーメソッド/関数として実装できます。

10
Stephen C

Javaでは、列挙型は 参照タイプ です(その小さな 'e'に騙されないでください)。これを使って、defineメソッドのようにきちんとしたことができます。その典型的な例は、Operation enumが次のように定義されている場合です。

_public enum Operation {
  PLUS   { double eval(double x, double y) { return x + y; } },
  MINUS  { double eval(double x, double y) { return x - y; } },
  TIMES  { double eval(double x, double y) { return x * y; } },
  DIVIDE { double eval(double x, double y) { return x / y; } };

  // Do arithmetic op represented by this constant
  abstract double eval(double x, double y);
}
//Elsewhere:
Operation op = Operation.PLUS;
double two = op.eval(1, 1);
_

ご覧のとおり...そうです、メソッドがあるので、プリミティブではありません。それらオブジェクトです。

Java Enumチュートリアル で説明されているように、コンパイラで発生するいくつかの魔法もあります。

コンパイラは、列挙型を作成するときに、いくつかの特別なメソッドを自動的に追加します。たとえば、列挙値のすべての値を含む配列を宣言された順序で返す静的値メソッドがあります。このメソッドは一般に、列挙型の値を反復するためにfor-each構文と組み合わせて使用​​されます。たとえば、以下のPlanetクラスの例のこのコードは、太陽系のすべての惑星を反復処理します。

そして、これがjavadocsで言及されていないことの煩わしさには確かに同意します。

values()呼び出しから返される値は、繰り返し処理できる配列です。

_class Demo {
    enum DayOfWeek {
        U, M, T, W, R, F, S
    }

    public static void main (String[] args) {
        for (DayOfWeek day : DayOfWeek.values()) {
            System.out.println(day.toString());
        }
    }
}
_

これは出力します:

 U 
 M 
 T 
 W 
 R 
 F 
 S 

このコードを ideone で実行します

なぜ次はないのですか?まあ、values()配列を取得する機能があれば、next()メソッドのすべてのユースケースを満たし、もう少し強力です。

本当にnext()メソッドが必要な場合は、配列をIteratorオブジェクト(それ自体が列挙型の代わりとなる)に入れるのに少し時間がかかります-を参照してください- javadoc ):

_import Java.util.*;

class Demo {
    enum DayOfWeek {
        U, M, T, W, R, F, S
    }

    public static void main (String[] args) {
        Iterator days = Arrays.asList(DayOfWeek.values()).iterator();
        while(days.hasNext()) {
            System.out.println(days.next());
        }
    }
}
_

このコードを ideone で実行します

繰り返しになりますが、それが存在しない理由は、必要がないことです。列挙型から配列を非常に簡単に取得して反復するか、その配列をIteratorに変換してnext()を呼び出すことができます。

また、enumEnumerationを類似させすぎて(すでに類似しすぎている)、enumをさらに見栄えよくしようとすると混乱が生じる可能性があります。同じメソッドを呼び出すEnumerationのようにすると、さらに混乱が生じます。とはいえ、これは単なる憶測にすぎません。

基本的に、Java enumはC enumによく似ており、いくつかのメソッドを側面に持つことができます。少し制約があります。 Cでできるように、それらを舞台裏で特定の数にする:

_/* This is C */
enum cardsuit {
    CLUBS    = 1,
    DIAMONDS = 2,
    HEARTS   = 4,
    SPADES   = 8
};
_

人々は時々Pascalを指して succ を列挙型で呼び出すことができると言います、なぜJavaではないのですか? Pascal列挙型は序数型ですが、C列挙型はそうではありません。 Java PascalではなくCから借りています。

独自のnext()またはprev()関数を定義できないということは何もありません。 前方参照の煩わしさ を回避した後で実行するのはかなり簡単です。

_class Demo {
    enum DayOfWeek {
        U, M, T, W, R, F, S;

        private DayOfWeek n;
        private DayOfWeek p;

        static {
            U.n = M; U.p = S;
            M.n = T; M.p = U;
            T.n = W; T.p = M;
            W.n = R; W.p = T;
            R.n = F; R.p = W;
            F.n = S; F.p = R;
            S.n = U; S.p = F;
        }

        public DayOfWeek next() { return this.n; }
        public DayOfWeek prev() { return this.p; }

    }

    public static void main (String[] args) {
        System.out.println(DayOfWeek.M.next());
        System.out.println(DayOfWeek.U.prev());
    }
}
_
 T 
 S 

そう...

  1. Enumは、序数型のパスカルの伝統ではなく、enumのCの伝統から借りられました
  2. 彼らはenumEnumerationを混同したくありませんでした
  3. 反復可能なタイプですべての値を簡単に戻すことができます
  4. next()メソッドを定義して、任意の方法で機能させることができます。
10
user40980

これは、列挙型 (Javaの列挙型)(C#の列挙型) の性質によるものです。それらは言語の固定定数であり、重要なデータ(場合によっては自分の名前またはIDのみ)を保持するものであり、一種のリストではありません。したがって、このようなロジックが必要な場合(多くの場合に該当する可能性があります)、実際にロジックを作成する必要があります。

リンクされたリストの一種であるだけでなく、シングルトンにすることもできます(参照: 実装例:enumの使用 )。それはより多くの使用の問題です。悲しいことに列挙型(少なくともJavaの場合)を拡張することも、それらのインターフェースを直接定義することもできないため、この方法ではトリッカーです。

ある種の定数リストまたはシングルトンに列挙型を使用するのが良い方法であるかどうかは、オープンな議論ですが、実際は可能です。プロジェクトで広く使用されているパターンでない限り、使用しないことに反対します。ローカルで使用すると、ハッキングのようなにおいがします。

2
CsBalazsHungary

技術的には、次の操作を両方の言語で実行する方法がありますが、JavaとC#では異なります。

C#

C#では、列挙型はその中心にある数値型です。あなたは単に整数で加算を実行することができます:

_var day = DayOfWeek.Sunday;
foreach(var n in Enumerable.Range(0, 7))
    Console.WriteLine("Day of week: {0}", day + n);
_

注:上記はday ++でも同じように機能します

出力:

_Day of week: Sunday
Day of week: Monday
Day of week: Tuesday
Day of week: Wednesday
Day of week: Thursday
Day of week: Friday
Day of week: Saturday
_

Java

Javaでは、列挙型を本格的なオブジェクト型として定義できます。その結果、単純に増分することはできません。ただし、StackOverflow いくつかの選択肢を示します

TL; DR:enumをインクリメントするためのややハックな方法がありますが、推奨される方法は、enumにnext()関数を実装することです。

1
Dan Lyons

列挙型は、実行可能ファイルで定数値に置き換えられます。実行時にトラバースすることはできません。

cでは、列挙型はソースコードの一部であり、コンパイラは列挙型を定数intに置き換えます。

     // C source - enum gets replaced with constant
enum _tenum { A, B, C};
_tenum ft;
ft = B;
     // assembler enum is gone. only constant value 1 remains
mov DWORD PTR _ft$[ebp], 1

if (ft == B) {

cmp DWORD PTR _ft$[ebp], 1
jne SHORT $LN1@main

in Java enumもソースファイルの一部であり、バイトコードジェネレータはenumをpublic final static valueに置き換えます。

ここに説明があります http://boyns.blogspot.com/2008/03/Java-15-explained-enum.html

1
Thomas

C#の@ dan-lyons回答は問題ありませんが、次の場合にのみ機能します。

  • enumは連続した整数値を使用して定義されます
  • あなたはそこにいくつの価値があるか知っています
  • 対応する整数とは何かを知っています。

代わりに、Enum.GetValues()を使用して、それが返すコレクションを繰り返すことをお勧めします。

enum MyEnum
{
    Value1 = -4,
    Value2 = 0,
    Value3,       // = 1
    Value4,       // = 2
    Value5 = 100,
}

...

// We need to use Cast<>() because GetValues() only returns
// a non-generic Array, which can hold any type of object.
var values = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>();
foreach(var value in values)
{
    Console.WriteLine($"{value} = {(int)value}");
}

これにより、明示的に定義されたすべての値が、コード化された名前とそれに対応する整数で出力されます。

Value1 = -4
Value2 = 0
Value3 = 1
Value4 = 2
Value5 = 100

値やその数を知る必要はありません。

0
gregsdennis

列挙型でこの種の動作を提供することは、ハッキングを容易にするだけです。

列挙型は配列ではありません。これらは技術的には配列として実装できますが、論理的にはタイプセーフな方法で使用できるグループ化された定数の数に制限があります。グループメンバー間に論理的な順序関係はありません。

列挙型の定義はリストではなく、コレクションでもないため、個々のメンバーにはインデックスがありません。ソースコードに現れる順序は論理的に意味がありません。

MicrosoftはDayOfWeekの実装により、実用的であり、列挙型が何であるか、および以下に使用する必要があるかについて混乱を引き起こしています。

整数にキャストする場合、その値の範囲はゼロ(DayOfWeek.Sundayを示す)から6(DayOfWeek.Saturdayを示す)までです。

なんて便利なんだ…適切な方法は、静的メソッドを使用してWeekクラスを提供することでした。

static DayOfWeek Day(DayOfWeek pivot, int offset)

その後、ロールオーバーの問題は発生せず、列挙型を乱用する必要はありません。

0
Martin Maat