はじめに
-
昨年、私が進行していたJavaスタディで整理した内容です。
-
内容に不正確な点がございましたら、ご指摘いただけますと幸いです。
古い内容を編集してアップロードしたため、十分に検証されていない部分や不自然な箇所があるかもしれません。
(今後も継続的に修正してまいります) -
翻訳機の助けを借りて作成した文ですので、誤りがある可能性がございます
Enum の定義方法
public enum Company {
APPLE,
SAMSUNG,
GOOGLE,
MICROSOFT
}
- 各項目は ,(カンマ)で区切ります
- 最後の項目の後にある ; は任意です
- すべての文字は大文字で記述します(慣例)
- 複数の単語で構成される場合は、_(アンダースコア)で区切ります
Company company = Company.APPLE;
このように使用します。
- 列挙型の定数は
static final
として扱われます
コンストラクタとフィールドの定義
- Enum のデフォルトコンストラクタは常に
private
です(コンパイラによって強制的にprivate
に設定されます)。 - Enum はフィールドを持つことができます。
- 各定数はコンストラクタを通じて対応するフィールドを初期化することができます。
enum Color {
RED("赤色"), GREEN("緑色"), BLUE("青色");
private final String description; // フィールドの定義
// コンストラクタの定義
private Color(String description) {
this.description = description;
}
// フィールドの getter メソッド
public String getDescription() {
return description;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Color.RED.getDescription()); // "赤色" を出力
System.out.println(Color.GREEN.getDescription()); // "緑色" を出力
}
}
メソッドの定義
- Enum に一般的なメソッドを定義することができます
- すべての定数で共通して使用するメソッドを定義したり、特定の定数でのみオーバーライドするように設定することもできます
enum Operation {
ADD {
@Override
public int apply(int x, int y) {
return x + y;
}
},
SUBTRACT {
@Override
public int apply(int x, int y) {
return x - y;
}
},
MULTIPLY {
@Override
public int apply(int x, int y) {
return x * y;
}
};
// 抽象メソッドの宣言(定数ごとに実装)
public abstract int apply(int x, int y);
}
public class Main {
public static void main(String[] args) {
System.out.println(Operation.ADD.apply(3, 4)); // 7
System.out.println(Operation.SUBTRACT.apply(10, 5)); // 5
System.out.println(Operation.MULTIPLY.apply(2, 3)); // 6
}
}
静的メソッドの定義
- Enum 内部に静的メソッドを定義することで、列挙型全体に関連する動作を処理することができます
enum Color {
RED, GREEN, BLUE;
public static Color fromString(String colorName) {
return switch (colorName.toUpperCase()) {
case "RED" -> RED;
case "GREEN" -> GREEN;
case "BLUE" -> BLUE;
default -> throw new IllegalArgumentException("不明な色: " + colorName);
};
}
}
public class Main {
public static void main(String[] args) {
Color color = Color.fromString("red");
System.out.println(color); // RED を出力
}
}
継承
Enum クラスは継承できず、列挙型はサブクラスでオーバーライドすることもできません。
(暗黙的にコンパイラによって final
として処理されます)
Enum はなぜ使うのか
int Enum パターン (Java 5 以前)
public class Company {
public static final int APPLE = 1;
public static final int SAMSUNG = 2;
public static final int GOOGLE = 3;
}
問題点
- 型の安全性が不足している
- 他の定数グループと比較が可能になってしまう
if (Company.APPLE == DayOfWeek.MONDAY) // コンパイルエラーが発生しない
- 名前空間の欠如
- すべての定数名が一意でなければならない
- 他の定数グループで同じ名前を使用することができない
Type-saftey
public enum Company {
APPLE, SAMSUNG, GOOGLE
}
public enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY
}
// コンパイルエラーが発生
if (Company.APPLE == DayOfWeek.MONDAY)
- 型の安全性が保証される
- JVM 上でインスタンスは1つだけ存在
- シングルトンが保証される
Enum が提供するメソッド
name()
-
列挙型定数の名前を返します
enum Color { RED, GREEN, BLUE } public class Main { public static void main(String[] args) { System.out.println(Color.RED.name()); // "RED" と出力されます } }
ordinal()
-
列挙型定数の宣言順序を返します (0からNまで)
enum Color { RED, GREEN, BLUE } public class Main { public static void main(String[] args) { System.out.println(Color.GREEN.ordinal()); // 1 と出力 } }
toString()
- 列挙型定数の名前を文字列で返します
- EnumはObjectを継承するため、toStringを継承します
enum Color {
RED("赤"), GREEN("緑"), BLUE("青");
private final String japaneseName;
Color(String japaneseName) {
this.japaneseName = japaneseName;
}
@Override
public String toString() {
return this.japaneseName;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Color.BLUE); // "青"
}
}
equals(Object other)
- 二つの列挙型定数が同じかどうかを比較します
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
Color color1 = Color.RED;
Color color2 = Color.RED;
System.out.println(color1.equals(color2) // true
}
}
→ シングルトンですが、==
で比較しても真となるでしょうか?
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
Color color1 = Color.RED;
Color color2 = Color.RED;
System.out.println(color1 == color2) // true
}
}
- その通り、trueが返ります。しかし、==を使用しない理由は型安全性が不足しているためです
Enumと型安全性
enum Color {
RED, GREEN, BLUE
}
enum Stock {
APPLE, GOOGLE
}
public class Main {
public static void main(String[] args) {
Color color1 = Color.RED;
Stock stock1 = Stock.APPLE;
System.out.println(color1 == stock1); // コンパイル時に判断できない
System.out.println(color1.equals(stock1)); // コンパイル時にエラーが発生する
}
}
- さらにジェネリクスに関連して、この利点があります
EnumSet<Color> colors = EnumSet.of(Color.RED, Color.BLUE);
colors.contains(Color.GREEN); // OK - 同じEnum型
colors.contains(DayOfWeek.MONDAY); // コンパイルエラー - 異なるEnum型
List<Color> colorList = new ArrayList<>();
colorList.add(Color.RED);
colorList.contains(Color.GREEN); // OK - 同じEnum型
colorList.contains(DayOfWeek.MONDAY); // コンパイルエラーにはなりませんが、falseが返されます (equals()比較でfalse)
hashcode()
- ハッシュコードを返します
compareTo(E o)
- 列挙型定数間の順序を比較します
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
System.out.println(Color.RED.compareTo(Color.BLUE)); // 負の値が出力されます (REDがBLUEより前にあるため)
}
}
- 宣言された順序に従って比較します
getDeclaringClass()
- 列挙型定数が属する列挙型クラスのオブジェクトを返します
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
System.out.println(Color.RED.getDeclaringClass()); // class Color が出力
}
}
valueOf(Class<T> enumClass, String name)
- 文字列の名前を用いて、特定の列挙型定数を返します
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
System.out.println(Enum.valueOf(Color.class, "GREEN")); // "GREEN"
}
}
- 列挙型定数の名前が完全に一致する必要があります
describeConstanble()
- 列挙型定数のEnumEscを包んだOptionalオブジェクトを返します
- Java 12以降で使用可能です
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) {
System.out.println(Color.RED.describeConstable()); // Optional[EnumDesc[Color.RED]] が出力
}
}
EnumDesc
clone()
- enumは絶対にコピーされることはありません
- したがって、シングルトンであることが保証されます
readObject(ObjectInputStream in)
- シリアライズおよびデシリアライズの過程で常にシングルトンが維持されます
- つまり、標準のシリアライズは使用できず、例外が投げられます
- Javaのシングルトンおよびenumシングルトンに関する情報は、こちらにまとめました
package com.example.jpystudy;
import java.lang.constant.ClassDesc;
import java.lang.invoke.MethodHandles;
import java.io.*;
enum Color {
RED, GREEN, BLUE
}
public class Main {
public static void main(String[] args) throws Exception {
// シリアライズ
Color color = Color.RED;
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(color);
// デシリアライズ
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
Color deserializedColor = (Color) in.readObject();
// 比較
System.out.println(color == deserializedColor); // true (シングルトン保証)
}
}
finalize()
- 無効化されています
- なぜなら、GCによって終了されるか、終了処理を行う理由がないためです
java.lang.Enum
- java.lang.Enum はすべての列挙型の抽象基本クラスです
- 多重継承は不可能です
- すべてのenumはこのクラスを暗黙的に継承し、列挙型に関連する基本的な動作やメソッドを提供します
- 以前に述べたように、ENUMはOBJECTを継承しています
Enum.set
- Enum型と共に使用するために設計されたSetの実装です
- 列挙型定数を内部的にビットベクトルとして表現することで、非常に効率的な空間および時間性能を提供します
- ビットベクトルは2種類の実装で実現されています
- 64個以下: 単一のlong値(64ビット)をビットベクトルとして使用するRegularEnumSet
- 64個超過: long[]配列を使用するJumboEnumSet
- 一般的なSet実装であるHashSetは、定数オブジェクトとして管理されるため、ビットベクトルを使用するEnumSetよりも多くのメモリを消費します
Bit VectorとEnumSet
- EnumSetは内部的に上記のビット演算を活用し、列挙型定数を効率的に管理します
- EnumSetを使用することで、型安全にEnumの集合を扱うことができます
enum Color {
RED, GREEN, BLUE, YELLOW;
}
public class Main {
public static void main(String[] args) {
EnumSet<Color> colors = EnumSet.noneOf(Color.class); // 空のセットを生成
// REDを追加
colors.add(Color.RED); // 内部的に bitVector |= RED
System.out.println(colors); // [RED]
// GREENを追加
colors.add(Color.GREEN); // 内部的に bitVector |= GREEN
System.out.println(colors); // [RED, GREEN]
// REDを削除
colors.remove(Color.RED); // 内部的に bitVector &= ~RED
System.out.println(colors); // [GREEN]
// 含有判定
boolean containsBlue = colors.contains(Color.BLUE); // 内部的に bitVector & BLUE
System.out.println(containsBlue); // false
}
}
ENUM Set 使用例
enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public class Main {
public static void main(String[] args) {
EnumSet<DayOfWeek> weekdays = EnumSet.of(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
);
weekdays.add(DayOfWeek.SATURDAY);
weekdays.remove(DayOfWeek.MONDAY);
boolean containsSaturday = weekdays.contains(DayOfWeek.SATURDAY);
boolean containsMonday = weekdays.contains(DayOfWeek.MONDAY);
System.out.println(containsSaturday);
System.out.println(containsMonday);
}
}
その他のEnumのすべてのメソッド
- その他のすべてのメソッドについては、こちらを参照してください