Javaでタイプセーフ列挙型を使用する方法

従来の列挙型を使用するJavaコードには問題があります。Java 5は、タイプセーフな列挙型の形でより良い代替手段を提供してくれました。この記事では、列挙型とタイプセーフ列挙型を紹介し、タイプセーフ列挙型を宣言してswitchステートメントで使用する方法を示し、データと動作を追加してタイプセーフ列挙型をカスタマイズする方法について説明します。クラスを探索して記事を締めくくります。java.lang.Enum

ダウンロードコードを入手するこのJava101チュートリアルの例のソースコードをダウンロードします。JavaWorld /用にJeffFriesenによって作成されました。

列挙型からタイプセーフな列挙型へ

列挙型は、その値として、関連する定数のセットを指定します。例としては、1週間の日、標準の北/南/東/西のコンパス方向、通貨の硬貨の種類、字句解析プログラムのトークンタイプなどがあります。

列挙型は、従来、整数定数のシーケンスとして実装されてきました。これは、次の方向定数のセットによって示されます。

static final int DIR_NORTH = 0; static final int DIR_WEST = 1; static final int DIR_EAST = 2; static final int DIR_SOUTH = 3;

このアプローチにはいくつかの問題があります。

  • 型の安全性の欠如:列挙型定数は単なる整数であるため、定数が必要な場合は任意の整数を指定できます。さらに、これらの定数に対して、加算、減算、およびその他の数学演算を実行できます。たとえば、(DIR_NORTH + DIR_EAST) / DIR_SOUTH)、これは無意味です。
  • 名前空間が存在しない:列挙型の定数にはDIR_、別の列挙型の定数との衝突を防ぐために、ある種の(うまくいけば)一意の識別子(たとえば)をプレフィックスとして付ける必要があります。
  • 脆弱性:列挙型定数は、リテラル値が(定数プールに)格納されるクラスファイルにコンパイルされるため、定数の値を変更するには、これらのクラスファイルとそれに依存するアプリケーションクラスファイルを再構築する必要があります。そうしないと、実行時に未定義の動作が発生します。
  • 情報の欠如:定数が出力されると、その整数値が出力されます。この出力は、整数値が何を表すかについては何も教えてくれません。定数が属する列挙型も識別しません。

java.lang.String定数を使用することで、「型安全性の欠如」と「情報の欠如」の問題を回避できます。たとえば、を指定できますstatic final String DIR_NORTH = "NORTH";。定数値の方が意味がありますが、Stringベースの定数には「名前空間が存在しない」という問題と脆弱性の問題があります。また、整数比較とは異なり、文字列値を==and!=演算子(参照のみを比較する)と比較することはできません。

これらの問題により、開発者はTypesafeEnumと呼ばれるクラスベースの代替手段を発明しました。このパターンは広く説明され、批判されています。Joshua Blochは、彼の効果的なJavaプログラミング言語ガイド(Addison-Wesley、2001)のアイテム21でパターンを紹介し、いくつかの問題があることを指摘しました。つまり、タイプセーフな列挙型定数をセットに集約するのは厄介であり、列挙型定数はswitchステートメントで使用できません。

次のタイプセーフ列挙型パターンの例について考えてみます。このSuitクラスは、クラスベースの代替手段を使用して、4つのカードスーツ(クラブ、ダイヤ、ハート、スペード)を説明する列挙型を導入する方法を示しています。

public final class Suit // Suitをサブクラス化できないようにする必要があります。{public static final Suit CLUBS = new Suit(); public static final Suit DIAMONDS = new Suit(); public static final Suit HEARTS = new Suit(); public static final Suit SPADES = new Suit(); private Suit(){} //追加の定数を導入できないようにする必要があります。}

このクラスを使用するには、次のようにSuit変数を導入し、それをSuit定数の1つに割り当てます。

スーツスーツ= Suit.DIAMONDS;

その後、尋問する場合がありますsuitswitch、このような声明:

switch(suit){case Suit.CLUBS:System.out.println( "clubs"); ブレーク; ケースSuit.DIAMONDS:System.out.println( "diamonds"); ブレーク; ケースSuit.HEARTS:System.out.println( "hearts"); ブレーク; ケースSuit.SPADES:System.out.println( "spades"); }

ただし、JavaコンパイラがSuit.CLUBSを検出すると、定数式が必要であることを示すエラーが報告されます。次のように問題に対処しようとする場合があります。

スイッチ(スーツ){ケースCLUBS:System.out.println( "clubs"); ブレーク; ケースダイヤモンド:System.out.println( "diamonds"); ブレーク; ケースHEARTS:System.out.println( "hearts"); ブレーク; ケーススペード:System.out.println( "spades"); }

ただし、コンパイラがCLUBSを検出すると、シンボルが見つからなかったことを示すエラーが報告されます。またSuit、パッケージに配置し、パッケージをインポートし、これらの定数を静的にインポートした場合でも、コンパイラーはSuitintに遭遇suitしたときに変換できないと文句を言いswitch(suit)ます。それぞれに関してcase、コンパイラは定数式が必要であることも報告します。

Javaは、switchステートメントを含むTypesafeEnumパターンをサポートしていません。ただし、問題を解決しながらパターンの利点をカプセル化するためのタイプセーフ列挙型言語機能が導入されており、この機能はをサポートしていswitchます。

タイプセーフ列挙型を宣言し、switchステートメントで使用する

Javaコードの単純なタイプセーフ列挙型宣言は、C、C ++、およびC#言語の対応するもののように見えます。

列挙型方向{北、西、東、南}

この宣言では、キーワードenumを使用して、Direction任意のメソッドを追加したり、任意のインターフェイスを実装したりできるタイプセーフ列挙型(特殊な種類のクラス)として導入します。NORTHWESTEAST、およびSOUTH列挙定数は囲み延びる匿名クラス定義定数固有のクラス体として実装されているDirectionクラス。

Directionおよび他の型保証された列挙は伸びる とを含む様々な方法を、継承、およびこのクラスから、。我々は見ていきます、この資料の後半。Enum values()toString()compareTo()Enum

リスト1は、前述の列挙型を宣言し、switchステートメントで使用しています。また、2つの列挙型定数を比較して、どちらの定数が他の定数の前に来るかを判断する方法も示します。

リスト1 :(TEDemo.javaバージョン1)

public class TEDemo {enum Direction {NORTH、WEST、EAST、SOUTH} public static void main(String [] args){for(int i = 0; i <Direction.values()。length; i ++){Direction d = Direction .values()[i]; System.out.println(d); switch(d){case NORTH:System.out.println( "Move north");ブレーク; case WEST:System.out.println( "Move west");ブレーク;ケースEAST:System.out.println( "Move east");ブレーク;ケース南:System.out.println( "南に移動");ブレーク;デフォルト:falseをアサート: "不明な方向"; }} System.out.println(Direction.NORTH.compareTo(Direction.SOUTH)); }}

リスト1は、Directionタイプセーフ列挙型を宣言し、定数メンバーを反復処理してvalues()戻ります。値ごとに、switchステートメント(タイプセーフ列挙型をサポートするように拡張)は、caseの値に対応するを 選択しd 、適切なメッセージを出力します。(たとえば、NORTH列挙型の前に列挙型定数を付けないでください。)最後に、リスト1は、の前Direction.NORTH.compareTo(Direction.SOUTH)にあるかどうかを判断するために評価します。NORTHSOUTH

次のようにソースコードをコンパイルします。

javac TEDemo.java

コンパイルされたアプリケーションを次のように実行します。

java TEDemo

次の出力を確認する必要があります。

北に移動北に移動西に移動東に移動東に移動南に移動-3

The output reveals that the inherited toString() method returns the name of the enum constant, and that NORTH comes before SOUTH in a comparison of these enum constants.

Adding data and behaviors to a typesafe enum

You can add data (in the form of fields) and behaviors (in the form of methods) to a typesafe enum. For example, suppose you need to introduce an enum for Canadian coins, and that this class must provide the means to return the number of nickels, dimes, quarters, or dollars contained in an arbitrary number of pennies. Listing 2 shows you how to accomplish this task.

Listing 2: TEDemo.java (version 2)

enum Coin { NICKEL(5), // constants must appear first DIME(10), QUARTER(25), DOLLAR(100); // the semicolon is required private final int valueInPennies; Coin(int valueInPennies) { this.valueInPennies = valueInPennies; } int toCoins(int pennies) { return pennies / valueInPennies; } } public class TEDemo { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java TEDemo amountInPennies"); return; } int pennies = Integer.parseInt(args[0]); for (int i = 0; i < Coin.values().length; i++) System.out.println(pennies + " pennies contains " + Coin.values()[i].toCoins(pennies) + " " + Coin.values()[i].toString().toLowerCase() + "s"); } }

Listing 2 first declares a Coin enum. A list of parameterized constants identifies four kinds of coins. The argument passed to each constant represents the number of pennies that the coin represents.

The argument passed to each constant is actually passed to the Coin(int valueInPennies) constructor, which saves the argument in the valuesInPennies instance field. This variable is accessed from within the toCoins() instance method. It divides into the number of pennies passed to toCoin()’s pennies parameter, and this method returns the result, which happens to be the number of coins in the monetary denomination described by the Coin constant.

At this point, you’ve discovered that you can declare instance fields, constructors, and instance methods in a typesafe enum. After all, a typesafe enum is essentially a special kind of Java class.

The TEDemo class’s main() method first verifies that a single command-line argument has been specified. This argument is converted to an integer by calling the java.lang.Integer class’s parseInt() method, which parses the value of its string argument into an integer (or throws an exception when invalid input is detected). I’ll have more to say about Integer and its cousin classes in a future Java 101 article.

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()は、参照を介して定数を比較するためにオーバーライドされます。同じID(==)を持つ定数は、同じ内容(equals())である必要があり、IDが異なると内容も異なります。
  • finalize() 定数を確定できないようにするためにオーバーライドされます。
  • hashCode()オーバーライドされるためequals()、オーバーライドされます。
  • toString() 定数の名前を返すためにオーバーライドされます。

Enum独自のメソッドも提供します。これらの方法には、finalcompareTo() (Enum実装java.lang.Comparableインタフェース)、 、getDeclaringClass()name()およびordinal()方法を: