Javaで列挙型定数を作成する

「列挙可能な定数」のセットは、数値のように数えることができる定数の順序付けられたコレクションです。このプロパティを使用すると、数値のように配列にインデックスを付けることができます。または、forループのインデックス変数として使用することもできます。Javaでは、このようなオブジェクトは「列挙型定数」として最もよく知られています。

列挙型定数を使用すると、コードが読みやすくなります。たとえば、定数RED、GREEN、およびBLUEを可能な値として使用して、Colorという名前の新しいデータ型を定義したい場合があります。アイデアは、Carオブジェクトなど、作成する他のオブジェクトの属性としてColorを使用することです。

クラスカー{カラーカラー; ...}

次に、次のように、明確で読みやすいコードを記述できます。

 myCar.color = RED; 

次のようなものの代わりに:

 myCar.color = 3; 

Pascalのような言語で列挙された定数のさらに重要な属性は、それらがタイプセーフであることです。つまり、color属性に無効な色を割り当てることはできません。常にRED、GREEN、またはBLUEのいずれかである必要があります。対照的に、色変数がintの場合、その数値が有効な色を表していない場合でも、任意の有効な整数を割り当てることができます。

この記事では、次のような列挙型定数を作成するためのテンプレートを提供します。

  • タイプセーフ
  • 印刷可能
  • インデックスとして使用するために注文済み
  • リンク済み、順方向または逆方向にループします
  • 列挙可能

将来の記事では、列挙型定数を拡張して状態依存の動作を実装する方法を学習します。

静的ファイナルを使用してみませんか?

列挙型定数の一般的なメカニズムは、次のような静的なfinalint変数を使用します。

static final int RED = 0; static final int GREEN = 1; static final int BLUE = 2; ..。

静的ファイナルは便利です

それらは最終的なものであるため、値は一定で変更できません。それらは静的であるため、オブジェクトごとに1回ではなく、定義されているクラスまたはインターフェイスに対して1回だけ作成されます。また、整数変数であるため、列挙してインデックスとして使用できます。

たとえば、ループを作成して、顧客のお気に入りの色のリストを作成できます。

for(int i = 0; ...){if(customerLikesColor(i)){favoriteColors.add(i); }}

変数を使用して配列またはベクトルにインデックスを付け、色に関連付けられた値を取得することもできます。たとえば、プレーヤーごとに異なる色のピースがあるボードゲームがあるとします。各カラーピースのdisplay()ビットマップがあり、そのビットマップを現在の場所にコピーするというメソッドがあるとします。ボードにピースを置く1つの方法は、次のようなものです。

PiecePicture redPiece = new PiecePicture(RED); PiecePicture greenPiece = new PiecePicture(GREEN); PiecePicture bluePiece = new PiecePicture(BLUE);

void placePiece(int location、int color){setPosition(location); if(color == RED){display(redPiece); } else if(color == GREEN){display(greenPiece); } else {display(bluePiece); }}

ただし、整数値を使用してピースの配列にインデックスを付けることにより、コードを次のように簡略化できます。

PiecePicture [] piece = {新しいPiecePicture(RED)、新しいPiecePicture(GREEN)、新しいPiecePicture(BLUE)}; void placePiece(int location、int color){setPosition(location); display(piece [color]); }

定数の範囲をループし、配列またはベクトルにインデックスを付けることができることは、静的な最終整数の主な利点です。そして、選択肢の数が増えると、単純化の効果はさらに大きくなります。

しかし、静的決勝は危険です

それでも、静的な最終整数を使用することにはいくつかの欠点があります。主な欠点は、型安全性の欠如です。計算または読み込まれる整数は、意味があるかどうかに関係なく、「色」として使用できます。定義された定数の終わりを過ぎてループするか、すべてをカバーすることをやめることができます。これは、リストに定数を追加または削除したが、ループインデックスの調整を忘れた場合に簡単に発生する可能性があります。

たとえば、色設定ループは次のようになります。

for(int i = 0; i <= BLUE; i ++){if(customerLikesColor(i)){favoriteColors.add(i); }}

後で、新しい色を追加する場合があります。

static final int RED = 0; static final int GREEN = 1; static final int BLUE = 2; static final int MAGENTA = 3;

または、1つ削除することもできます。

static final int RED = 0; static final int BLUE = 1;

いずれの場合も、プログラムは正しく動作しません。色を削除すると、問題に注意を引くランタイムエラーが発生します。色を追加しても、エラーはまったく発生しません。プログラムは、すべての色の選択肢をカバーできなくなります。

もう1つの欠点は、読み取り可能な識別子がないことです。メッセージボックスまたはコンソール出力を使用して現在の色の選択を表示すると、数値が表示されます。そのため、デバッグはかなり困難になります。

読み取り可能な識別子を作成する際の問題は、次のような静的な最終文字列定数を使用して解決される場合があります。

static final String RED = "red" .intern(); ..。

このintern()メソッドを使用すると、内部文字列プールにそれらの内容を持つ文字列が1つだけ存在することが保証されます。ただし、intern()効果的にするには、REDと比較されるすべての文字列または文字列変数でそれを使用する必要があります。それでも、静的な最終文字列は、ループや配列へのインデックス付けを許可せず、型安全性の問題に対処していません。

型安全性

静的な最終整数の問題は、それらを使用する変数が本質的に無制限であるということです。これらはint変数です。つまり、保持する予定の定数だけでなく、任意の整数を保持できます。目標は、Colorタイプの変数を定義して、その変数に無効な値が割り当てられたときに実行時エラーではなくコンパイルエラーが発生するようにすることです。

An elegant solution was provided in Philip Bishop's article in JavaWorld, "Typesafe constants in C++ and Java."

The idea is really simple (once you see it!):

public final class Color { // final class!! private Color() {} // private constructor!!

public static final Color RED = new Color(); public static final Color GREEN = new Color(); public static final Color BLUE = new Color(); }

Because the class is defined as final, it can't be subclassed. No other classes will be created from it. Because the constructor is private, other methods can't use the class to create new objects. The only objects that will ever be created with this class are the static objects the class creates for itself the first time the class is referenced! This implementation is a variation of the Singleton pattern that limits the class to a predefined number of instances. You can use this pattern to create exactly one class any time you need a Singleton, or use it as shown here to create a fixed number of instances. (The Singleton pattern is defined in the book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides, Addison-Wesley, 1995. See the Resources section for a link to this book.)

The mind-boggling part of this class definition is that the class uses itself to create new objects. The first time you reference RED, it doesn't exist. But the act of accessing the class that RED is defined in causes it to be created, along with the other constants. Admittedly, that kind of recursive reference is rather difficult to visualize. But the advantage is total type safety. A variable of type Color can never be assigned anything other than the RED, GREEN, or BLUE objects that the Color class creates.

Identifiers

The first enhancement to the typesafe enumerated constant class is to create a string representation of the constants. You want to be able to produce a readable version of the value with a line like this:

 System.out.println(myColor); 

Whenever you output an object to a character output stream like System.out, and whenever you concatenate an object to a string, Java automatically invokes the toString() method for that object. That's a good reason to define a toString() method for any new class you create.

If the class does not have a toString() method, the inheritance hierarchy is inspected until one is found. At the top of the hierarchy, the toString() method in the Object class returns the class name. So the toString() method always has some meaning, but most of the time the default method will not be very useful.

Here is a modification to the Color class that provides a useful toString() method:

public final class Color { private String id; private Color(String anID) {this.id = anID; } public String toString() {return this.id; }

public static final Color RED = new Color(

"Red"

); public static final Color GREEN = new Color(

"Green"

); public static final Color BLUE = new Color(

"Blue"

); }

This version adds a private String variable (id). The constructor has been modified to take a String argument and store it as the object's ID. The toString() method then returns the object's ID.

One trick you can use to invoke the toString() method takes advantage of the fact that it is automatically invoked when an object is concatenated to a string. That means you could put the object's name in a dialog by concatenating it to a null string using a line like the following:

 textField1.setText("" + myColor); 

Unless you happen to love all the parentheses in Lisp, you will find that a bit more readable than the alternative:

 textField1.setText(myColor.toString()); 

It's also easier to make sure you put in the right number of closing parentheses!

Ordering and indexing

The next question is how to index into a vector or an array using members of the

Color

class. The mechanism will be to assign an ordinal number to each class constant and reference it using the attribute

.ord

, like this:

 void placePiece(int location, int color) { setPosition(location); display(piece[color.ord]); } 

Although tacking on .ord to convert the reference to color into a number is not particularly pretty, it is not terribly obtrusive either. It seems like a fairly reasonable tradeoff for typesafe constants.

Here is how the ordinal numbers are assigned:

public final class Color { private String id; public final int ord;private static int upperBound = 0; private Color(String anID) { this.id = anID; this.ord = upperBound++; } public String toString() {return this.id; } public static int size() { return upperBound; }

public static final Color RED = new Color("Red"); public static final Color GREEN = new Color("Green"); public static final Color BLUE = new Color("Blue"); }

This code uses the new JDK version 1.1 definition of a "blank final" variable -- a variable that is assigned a value once and once only. This mechanism allows each object to have its own non-static final variable, ord, which will be assigned once during object creation and which will thereafter remain immutable. The static variable upperBound keeps track of the next unused index in the collection. That value becomes the ord attribute when the object is created, after which the upper bound is incremented.

For compatibility with the Vector class, the method size() is defined to return the number of constants that have been defined in this class (which is the same as the upper bound).

A purist might decide that the variable ord should be private, and the method named ord() should return it -- if not, a method named getOrd(). I lean toward accessing the attribute directly, though, for two reasons. The first is that the concept of an ordinal is unequivocally that of an int. There is little likelihood, if any, that the implementation would ever change. The second reason is that what you really want is the ability to use the object as though it were an int, as you could in a language like Pascal. For example, you might want to use the attribute color to index an array. But you cannot use a Java object to do that directly. What you would really like to say is:

 display(piece[color]); // desirable, but does not work 

But you can't do that. The minimum change necessary to get what you want is to access an attribute, instead, like this:

 display(piece[color.ord]); // closest to desirable 

instead of the lengthy alternative:

 display(piece[color.ord()]); // extra parentheses 

or the even lengthier:

 display(piece[color.getOrd()]); // extra parentheses and text 

The Eiffel language uses the same syntax for accessing attributes and invoking methods. That would be the ideal. Given the necessity of choosing one or the other, however, I've gone with accessing ord as an attribute. With any luck, the identifier ord will become so familiar as a result of repetition that using it will seem as natural as writing int. (As natural as that may be.)

Looping

次のステップは、クラス定数を反復処理できるようにすることです。最初から最後までループできるようにしたい:

 for(Color c = Color.first(); c!= null; c = c.next()){...} 

または最後から最初に戻る:

 for(Color c = Color.last(); c!= null; c = c.prev()){...} 

これらの変更では、静的変数を使用して、最後に作成されたオブジェクトを追跡し、それを次のオブジェクトにリンクします。