Javaについて徹底解説!

Javaの定数はEnumで! Enumの使い方から考え方までお伝えします

JavaでのEnumは「列挙型」と呼ばれるものですと言われても良く分かりませんよね。列挙と言う言葉自体を、日常生活ではあまり使いません。Enumは、Javaで定数として扱えるものだと思っていただければ、まずはOKです。

もちろん、Enumは単なる定数だけの機能では終わりません。Enumは実はクラスの一種ですから、もっとすごいことが出来ますし、上手く使えばプログラムの品質アップに効果絶大です!!

この記事ではそんなEnumについて初心者向けに説明していきます。Enumの基本から活用方法まで幅広くカバーします。

※この記事のサンプルは、Java 10の環境で動作確認しています。

1.Enumの基本

Enumはenumerationの略で、「列挙、数えあげること、目録、一覧表」(Weblio英和辞典)という意味があります。分かりやすいのは一覧表でしょうか。ですので、JavaEnumも何かの一覧だと考えると分かりやすいでしょう。

ここでは、そんなEnumの基本についてお伝えします。

1-1.Enumの宣言の仕方

Enumは以下の構文で宣言します。Enumを宣言したファイル名は「Enum.java」となります。クラスと同じルールですね。

以下がEnumの簡単な例です。ファイル名はSeason.javaになります。この例では、季節をEnumにしてみました。つまり、このEnumでは「季節(Season)は、春(SPRING)・夏(SUMMER)・秋(AUTUMN)・冬(WINTER)のいずれかである」と宣言しています。

パッと見で、クラスとフィールドの宣言に似ているのが分かるでしょうか。“class”の代わりに“enum”としていますし、SPRING等を記述している箇所も普通のクラスならフィールドの位置です。

Enumと同等のことをクラスで行う場合は、以下が比較的近いものです。ただし、これはEnumと全く同じではなく、Enum独自の便利な機能もたくさんあります。それが通常のクラスとEnumの違いになります。

ちなみに、Enumの値の名称を大文字にしているのは、単にJavaでは定数は英語大文字とするのが慣習だからです。ですので、別に小文字でもコンパイルエラーにはなりません。でも、Javaの標準APIでもEnumの値は全て「大文字かつ単語は“_”つなぎ」なので、このルールを守っておくのが良いでしょう。

1-2.Enumはクラスと同じように扱える

Enumの使い方は、クラスにおけるstaticフィールドとほぼ同じです。Enumの値すなわちインスタンスへのアクセスは、プログラム上からはstaticフィールドへのアクセスと同じ「Enum.値の名称」です。

では、先程作ったEnumSeasonを使ってみましょう。

当然ながら、Seasonであらかじめ決められたもの以外は使えません。

Enumで宣言した値は、Enum自身のインスタンスです。StringIntegerなどではありません。ですので、この例では「季節の一つとしての春夏秋冬」がJava上でもそのまま表現できているのです。

Enumはプリミティブ型やクラスと同じように型として扱えますので、変数として宣言したり、Enumのインスタンスを参照させられます。

もちろん、メソッドへの引数や戻り値ともできます。

このように、Enumは普通のクラスのインスタンスと同じように扱えるものなのです。

1-3.【重要】Enumは新しいインスタンスを生成できない

Enumが普通のクラスと違う最大のポイントは「新しいインスタンスの生成(new)Enumの外部からはできない」ことです。つまり、Enumとして存在し得るインスタンスは、あらかじめ宣言したものだけなのです。

これがEnumの特徴および存在意義で、だからこそEnumの各インスタンスはプログラムの中で1つしかないことが保証される「定数」として扱えるのです。

2.Enumの使用例

2-1.Enum同士の比較(if文・switch)

最も頻繁に使うのは、Enum型の値同士を比較して、どのEnumの値かを調べることです。

例えば、if文・switch文での使用例は以下のとおりです。直感的に「きっとこうなるだろう」というとおりの動きですよね?

2-1-1.【重要】Enum同士の比較は==で確実に行える

Enum同士の比較は、上記例のとおり == で行うのがお手軽かつ確実です。equalsでもいいのですが、タイプ量が多くなりますし、プログラムの見た目がごちゃごちゃしてしまいます。

Javaのequalsについてしっかり理解されている方は、Stringなどの事例から、「==だと、保持する値は同じだけれどインスタンスが違うならfalseになってしまうのでは?」と心配される向きもあるでしょう。

でもEnumに関してなら心配無用です。Enumの値(=インスタンス)Java上で必ず1つだけになることが保証されていますので、Enumのインスタンスの比較は==で良いのです。

2-2.Enumが持つメソッドの使い方

Enumは標準で幾つかのメソッドを持っています。以下で説明するものは、Enumであれば宣言・実装をしないでも共通的に使えるものです。便利に使いましょう。

2-2-1.nameはEnumの名称を戻す

nameはEnumの値の名称をStringとして戻します。変数が参照しているのがどのEnumの値かを表示・確認したり、ログなどに出力するのに使います。

2-2-2.ordinalはEnumの宣言された順番を戻す

ordinalは、Enum上で値が宣言された順番を数値で戻します。順番は0始まりです。

2-2-3.valueOfは名称の文字列のEnumを戻す

valueOfは名称の文字列に対応するEnumを戻します。valueOfEnumstaticメソッドなので、値に対しては使うものではありません。言葉だと少しわかりづらいかもしれませんが、例を見れば一目瞭然です。

これは、例えばメソッドにStringとして渡された名称からEnumの値を取得するのに使えます。

ちなみに、宣言されたもの以外の名称を渡すと例外が発生します。

2-2-4.valuesは全てのEnumの配列を返す

valuesは全てのEnumの配列を返すstaticメソッドです。全ての値に対して何か処理をしたい場合に使います。

3.【応用】Enumの進んだ使い方

3-1.Enumのインスタンスに固有の値を持たせる

Enumのインスタンスに固有の値を持たせたい場合があります。例えば、処理結果の種類を表すEnumであれば、それを数値で表したコードを持たせるなどです。

その場合は、Enumのインスタンスフィールドに、コンストラクタで値を設定するのがお勧めです。実はEnumもクラスの一種なので、コンストラクタ、フィールド、メソッドなどを定義できるのです。

Enumのフィールドは、そのフィールドのコンストラクタを呼び出してインスタンスを生成しているのだと解釈してください。ですから、引数を持つ独自のコンストラクタを定義するなら、引数を与えるのはフィールド定義のところになるのです。

ここでのポイントは、フィールドをprivate finalとしてコンストラクタ内での設定を強制していることと、設定値をコンストラクタの引数としていること、フィールドの値にアクセスすためのgetterメソッドを作っていることです。

こうすることで、このEnumは以下のように使えます。その他のEnumの標準機能(nameordinal)は、普通のEnumと同様に使えます。

3-2.インスタンスごとに固有のメソッド実装を持たせる

Enumには前述のとおりメソッドを実装できます。さらに、それらのメソッドをEnumのインスタンスごとにオーバーライドさせられます。このように、オブジェクトとしての振る舞いを自由にカスタマイズできるのが、EnumStringintによる定数以上の強力な機能を持っているということの証明です。

やり方は二種類あり、標準動作をするメソッドを宣言して個別のインスタンスでオーバーライドする方法、抽象メソッドを宣言して個別のインスタンスで実装する方法です。以下にその二種類の例を示します。

3-3.Enumにインターフェイスを実装する

Enumは継承(extends)はできません。ですが、何かのインターフェイスを実装(implements)はできるのです。

例えば、以下のインターフェイスがあるとして、それをEnumに実装してみます。やり方としては上述したメソッド実装・オーバーライドの方法が使えます。必要に応じてやり方を選んでいきましょう。

当然、この場合はEnumの各インスタンスは、Enumでもあり何かのインターフェイスの実装クラスでもあるので、以下のようなプログラミングができるのです。

3-4.Enum専用のEnumSet/EnumMapを活用する

EnumはSetの要素やMapのキーとしても使えます。HashSetHashMapでも実用上問題はありませんが、Enum専用のEnumSetEnumMapが存在しますので活用しても良いでしょう。

それぞれの専用Set/Mapを使うメリットは、処理の高速性です。Enumは取り得る値の数が固定であることを前提にチューニングがされています。

EnumSetはコンストラクタとnewを用いてインスタンス生成するのではなく、ofを使ってインスタンスを取得する形になります。一旦インスタンスを取得してしまえば、普通のSetとして使えます。

EnumMapはnewしてインスタンスを生成します。これもコンストラクタは独特ですが、使い方は普通のMapと同じです。

3-5.Enumで管理するものは注意深く考えよう

Enumは値をあらかじめ決められるのが大きなメリットですが、一つのEnumに数十~数百以上も値があると、さすがに使い勝手やメンテナンス性が悪くなります。

そのような場合は、複数の異なる意味に分割できるようなものも一つのEnumに無理に押し込めようとしている可能性がありますので、意味的な観点から見直しを行いましょう。

Enumはいわゆる定数クラスや定数インターフェイスのように、一つのものにプログラム上で定数として使うであろう全ての種類の値を押し込むのに使うものではありません。この記事の例のとおり、季節や処理結果など、意味のあるものごとにEnumを定義し、それぞれに適切な値を定義して使うものです。

また、詳細なエラーメッセージやエラーコードなどをEnumで全て表現するのも、値の数が多くなる原因です。その場合はどこまでが具体的なEnumとして表現すべきもので、どこからがマニュアルやJavadocで表現すべきものか考えましょう。例えばエラーの分類はEnumとし、詳細コードはStringintとするなどです。

4.Enumを使うべき理由

4-1.Enumの利点は定数の種類・数を明確にできること

前述したとおり、Enumの主な用途は定数です。定数の種類・数があらかじめ明確になっていて、しかも決められた以外の値は取りえないことが保証されているのが最大の利点です。

定数はstatic finalStringintのフィールドでも同じようなことができますが、Enumに劣っているのは値の範囲や種類を確実・明確に制限できないことです。

もう少し言葉を足すならば、Stringintでは取り得る値・表現できる範囲が広すぎることがほとんどなのです。

4-2.Enumでない定数では引数を確定・強制できない

例えば、季節をStringで以下のように定数化したとします。このStringintの広域変数による定数はC言語などではごく普通で、Javaの現場でも普通に使われます。使い方は Constant.SEASON_SPRING で、見た目上はEnumとあまり変わりません。

以下のメソッドは、引数としてConstant.SEASON_SPRINGWINTERのいずれかを渡されることを前提にしていますが、他のStringを渡してもコンパイルエラーにはなりません。Javaコンパイラの観点では、Stringであれば何でもよいからです。ですから、メソッド側でのチェックが必須です。

それに、想定外の値が渡された場合でも、プログラムを動かしてみないと分かりません。一人だけでプログラムを作っているなら問題はまだ少ないのですが、複数人で作っているとこの意思疎通が地味に大変です。また、インターネットで公開され不特定多数の人が使うようなライブラリならもっと大変です。

4-3.Enumでない戻り値では呼び出し元での解釈が必要

メソッドの戻り値がEnumであることも重要です。Enumであれば、戻り値があらかじめ決められた値のいずれかであることがコンパイル時に確定するのです。

例えば、以下のプログラムでは、メソッドgetSeasonConstant.SEASON_SPRINGWINTER以外をreturnしても、コンパイルエラーにはなりません。例えば、“”だったり、“AAAAAA”だったとしてもです。戻り値がStringでさえあればいいからです。これも実行してみないと正しく実装できているか分かりません。

引数の時と同様に、メソッドの戻り値を信用できるかどうかは使う側からは分かりません。ですので、最悪は想定通りの結果かどうかをチェックしなければならないのです。

4-4.Enumはプログラムの安全性・確実性を高める

Enumであれば、Enumで宣言した値のいずれかだとコンパイル時に確定します。Enumとしてはあらかじめ宣言していない値は使えませんし、自分でインスタンスを生成できないからです。先ほどのようにStringでやりとりするよりも、プログラムの安全性・確実性が高まることが分かるでしょうか。

また、ただのStringintの値であるよりも、Enumなら専用のデータ型であることが明確になります。定数として用いるなら、Enumの方がより望ましい姿だと言えるでしょう。

Stringやintで定数を表現するのは、他のプログラミング言語の習慣を持ち込んでいたり、JavaEnumがなかった時代の名残です。Javaの標準APIでも、Enum導入後は多くのEnumが使用されています。今Javaでプログラミングをするならば、Enumを大いに活用するのが望ましいスタイルなのです。

5.まとめ

EnumはJavaで定数を表現するために用います。Enumは宣言の仕方も使い方も簡単ですが、実体はクラスなので、やろうと思えばメソッドの実装などの様々なカスタマイズが行えるものです。

EnumがJavaに追加されたのは、従来の定数を実現する方法では、どうしてもプログラムの品質に問題が残るからです。

Enumが追加された背景や意図を理解して、効果的にEnumを使えるようになると、バグや意図しない動作を未然に防止できます。ぜひ活用していきましょう!!

『技術力』と『人間力』を高め市場価値の高いエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって遙かに高い水準の成果を出し、関わる全ての人々に感動を与え続ける集団でありたいと考えています。

高い水準で仕事を進めていただくためにも、弊社では次のような環境を用意しています。

  • 定年までIT業界で働くためのスキル(技術力、人間力)が身につく支援
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な人事評価制度
  • 平均残業時間17時間!毎週の稼動確認を徹底しているから実現できる働きやすい環境

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。