1。概要
この記事では、Java列挙とは何か、それらが解決する問題、およびJava列挙を使用して実際にいくつかのデザインパターンを実装する方法について説明します。
enumキーワードはjava5で導入され、常にjava.lang.Enumクラスを継承する特別なタイプのクラスを示します。詳細については、公式ドキュメントを確認してください。
列挙型は定数と比較されることがよくあります。これはおそらく、定数の代わりに実際に多数の列挙型を使用しているためです。では、このアプローチの利点は何ですか?
このように定義された定数は、コードを読みやすくし、コンパイル時のチェックを可能にし、受け入れ可能な値のリストを事前に記録し、に渡された無効な値による予期しない動作を回避します。
次の例では、単純な列挙型ピザ注文のステータスを定義しています。ORDERED、READY、およびDELIVEREDの3つの状態があります。
package shuang.kou.enumdemo.enumtest;
publicenum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
簡単に言うと、上記のコードで定数を定義することは避け、ピザの注文のステータスに関連するすべての定数を列挙型に入れます。
System.out.println(PizzaStatus.ORDERED.name());//ORDERED
System.out.println(PizzaStatus.ORDERED);//ORDERED
System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus
2.カスタム列挙方法
列挙型とは何か、およびそれらの使用方法についての基本的な理解ができたので、列挙型にいくつかの追加のAPIメソッドを定義して、前の例を新しいレベルに引き上げましょう。
publicclass Pizza {
private PizzaStatus status;
publicenum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
returntrue;
}
returnfalse;
}
// Methods that set and get the status variable.
}
3. ==を使用して、列挙型を比較します
列挙型により、JVMには定数インスタンスが1つだけ存在することが保証されるため、上記の例に示すように、「==」演算子を使用して2つの変数を安全に比較できます。さらに、「==」演算子はコンパイルを提供できます。 -時間と実行時のセキュリティ。
まず、次のコードスニペットのランタイムセキュリティを見てみましょう。ここでは、「==」演算子を使用して状態を比較し、両方の値がnullの場合、NullPointerExceptionはスローされません。逆に、equalsメソッドを使用すると、NullPointerExceptionがスローされます。
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
コンパイル時の安全性のために、別の例を見てみましょう。2つの異なる列挙型が比較されます。getStatusメソッドの列挙値が別の列挙値と一致しているため、equalメソッドを使用した比較の結果はtrueと判断されます。タイプしますが、論理的にはfalseである必要があります。この問題は、==演算子を使用することで回避できます。コンパイラは型の非互換性エラーを示すため、次のようになります。
if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);
4.switchステートメントで列挙型を使用します
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return5;
case READY: return2;
case DELIVERED: return0;
}
return0;
}
5.列挙型の属性、メソッド、およびコンストラクター
列挙型で属性、メソッド、コンストラクターを定義することで、より強力にすることができます。
次に、上記の例を拡張して、ピザのある段階から別の段階への移行を実現し、以前に使用されたifステートメントとswitchステートメントを取り除く方法を学びましょう。
publicclass Pizza {
private PizzaStatus status;
publicenum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
returntrue;
}
},
READY (2){
@Override
public boolean isReady() {
returntrue;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
returntrue;
}
};
privateint timeToDelivery;
public boolean isOrdered() {returnfalse;}
public boolean isReady() {returnfalse;}
public boolean isDelivered(){returnfalse;}
public int getTimeToDelivery() {
return timeToDelivery;
}
PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
public boolean isDeliverable() {
returnthis.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
// Methods that set and get the status variable.
}
次のコードは、それがどのように機能するかを示しています。
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
6.EnumSetおよびEnumMap
6.1。EnumSet
EnumSet
列挙Set
型用に特別に設計された 型です。
HashSet
コントラスト、内部ビットベクトル表現の使用に起因し、それはEnum
非常に効率的でコンパクトな表現特定の 定数のセット。
これは、従来のintベースの「ビットフラグ」に代わるタイプセーフな代替手段を提供し、より読みやすく、保守が容易な簡潔なコードを記述できるようにします。
EnumSet
2つの実装を持っている抽象クラスは、次のとおりですRegularEnumSet
、JumboEnumSet
列挙定数の選択のインスタンスの数に依存しています。
多くのシナリオでは、(のような:サブセット化、削除、追加、列挙定数収集動作の使用containsAll
とremoveAll
バッチ操作)がEnumSet
非常に適している、あなたはすべての可能な定数を反復するために必要がある場合は、それを使用しますEnum.values()
。
publicclass Pizza {
privatestatic EnumSet<PizzaStatus> undeliveredPizzaStatuses =
EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
private PizzaStatus status;
publicenum PizzaStatus {
...
}
public boolean isDeliverable() {
returnthis.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery() + " days");
}
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
// Methods that set and get the status variable.
}
次のテストEnumSet
は、特定のシナリオでの強力な機能を示してい ます。
@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
6.2。EnumMap
EnumMap
列挙型定数をキーとして使用する特殊なマッピング実装です。HashMap
対応するものと比較 すると、効率的でコンパクトな実装であり、内部的に配列として表されます。
EnumMap<Pizza.PizzaStatus, Pizza> map;
実際に使用する方法を示す実際の例を簡単に見てみましょう。
publicstatic EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pizzaList) {
EnumMap<PizzaStatus, List<Pizza>> pzByStatus =
new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);
for (Pizza pz : pizzaList) {
PizzaStatus status = pz.getStatus();
if (pzByStatus.containsKey(status)) {
pzByStatus.get(status).add(pz);
} else {
List<Pizza> newPzList = new ArrayList<Pizza>();
newPzList.add(pz);
pzByStatus.put(status, newPzList);
}
}
return pzByStatus;
}
次のテストEnumMap
は、特定のシナリオでの強力な機能を示してい ます。
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
EnumMap<Pizza.PizzaStatus,List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}
7.列挙型でいくつかのデザインパターンを実装します
7.1シングルトンモード
一般に、クラスを使用してシングルトンパターンを実装するのは簡単ではなく、列挙型はシングルトンを実装する簡単な方法を提供します。
「EffectiveJava」と「JavaandPatterns」はどちらもこの方法を強くお勧めします。この方法を使用して列挙を実装する利点は何ですか?
《効果的なJava》
この方法は、パブリックドメインの方法と機能が似ていますが、より簡潔で、無料のシリアル化メカニズムを提供し、複雑なシリアル化やリフレクション攻撃に直面しても、複数のインスタンス化を完全に防ぎます。この方法は広く採用されていませんが、単一要素列挙型がシングルトンを実装するための最良の方法になっています。-「効果的なJava中国語版第2版」
「Javaとパターン」
「Javaとパターン」で、著者は、単一インスタンス制御を実現するための列挙型の使用がより簡潔になり、シリアル化メカニズムが無料で提供され、JVMが基本的に複数のインスタンス化を完全に防止する保証を提供すると書いています。 、シングルトンを実装するための効率的で安全な方法。
次のコードスニペットは、列挙型を使用してシングルトンモードを実装する方法を示しています。
publicenum PizzaDeliverySystemConfiguration {
INSTANCE;
PizzaDeliverySystemConfiguration() {
// Initialization configuration which involves
// overriding defaults like delivery strategy
}
private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
public static PizzaDeliverySystemConfiguration getInstance() {
return INSTANCE;
}
public PizzaDeliveryStrategy getDeliveryStrategy() {
return deliveryStrategy;
}
}
それを使用する方法?次のコードを見てください。
PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();
何され PizzaDeliverySystemConfiguration.getInstance()
て得たことはシングルトンであります PizzaDeliverySystemConfiguration
7.2戦略モード
一般に、戦略パターンは、同じインターフェイスを実装するさまざまなクラスによって実装されます。
これは、新しい戦略を追加することは、新しい実装クラスを追加することを意味することも意味します。列挙を使用すると、このタスクを簡単に実行できます。新しい実装を追加するということは、特定の実装で別のインスタンスを定義することだけを意味します。
次のコードスニペットは、列挙型を使用して戦略パターンを実装する方法を示しています。
publicenum PizzaDeliveryStrategy {
EXPRESS {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in express mode");
}
},
NORMAL {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in normal mode");
}
};
public abstract void deliver(Pizza pz);
}
Pizza
次のメソッドを 追加するには:
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
それを使用する方法?次のコードを見てください。
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
pz.deliver();
assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}
8. Java8と列挙型
PizzaクラスはJava8で書き直すことができます。lambdaメソッドとStreamAPIメソッドがgetAllUndeliveredPizzas()
andgroupPizzaByStatus()
メソッドを非常に簡潔にする方法を確認できます 。
getAllUndeliveredPizzas()
:
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
groupPizzaByStatus()
:
publicstatic EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pzList) {
EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
Collectors.groupingBy(Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
return map;
}
9.列挙型のJSON表現
Jacksonライブラリを使用すると、列挙型のJSONをPOJOとして表現できます。次のコードスニペットは、同じ目的で使用できるJacksonアノテーションを示しています。
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
publicenum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
returntrue;
}
},
READY (2){
@Override
public boolean isReady() {
returntrue;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
returntrue;
}
};
privateint timeToDelivery;
public boolean isOrdered() {returnfalse;}
public boolean isReady() {returnfalse;}
public boolean isDelivered(){returnfalse;}
@JsonProperty("timeToDelivery")
public int getTimeToDelivery() {
return timeToDelivery;
}
private PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
我々は使用することができますPizza
し、 次のように PizzaStatus
:
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));
生成されたピザのステータスは、次のJSONで表示されます。
{
"status" : {
"timeToDelivery" : 2,
"ready" : true,
"ordered" : false,
"delivered" : false
},
"deliverable" : true
}
列挙型のJSONシリアル化/逆シリアル化(カスタマイズを含む)の詳細については、Jackson-Serialize Enumerations intoJSONオブジェクトを参照してください。
10.まとめ
この記事では、基本的な知識から高度なアプリケーションや実用的なアプリケーションのシナリオまで、Java列挙型について説明しました。列挙の力を感じてみましょう。
11.補足
上で述べたように、列挙型で属性、メソッド、コンストラクターを定義することで、より強力にすることができます。
実際の例を紹介します。SMS検証コードを呼び出す場合、いくつかの異なる目的があります。次のように定義します。
publicenum PinType {
REGISTER(100000, "注册使用"),
FORGET_PASSWORD(100001, "忘记密码使用"),
UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");
privatefinalint code;
privatefinal String message;
PinType(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return"PinType{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
}
実際の使用:
System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());
出力:
100001
忘记密码使用
PinType{code=100001, message='忘记密码使用'}
この場合、それは実際の使用において非常に柔軟で便利になります!