質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

5回答

14026閲覧

Javaのemumのエレガントな使い方

wordconverter

総合スコア30

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

4グッド

11クリップ

投稿2016/04/02 11:45

編集2016/04/02 11:46

Javaのenumのエレガントな使い方を探しています。

これまで、①「定数を型安全かつ便利に使えるクラス」ぐらいの認識しかなく、そこまで価値を感じていませんでした。しかし、②「ストラテジーパターンを利用したいわゆる定数固有メソッド」(effective javaに載っているようなやつ)の存在を知ってから、次第にenumに興味を持つようになりました。

そこで質問です。実際の現場で見たことのあるenumのエレガントな使い方を教えてください。デザインパターンが盛り込まれてクラス数が多い重厚なやつでもいいですし、手軽に使えるシンプルな小技でもいいです。

インターネットで色々調べましたが、①もしくは②の説明が多いと感じましたので、これら以外の事例を希望します。

KiyoshiMotoki, yodel, A-pZ, Ryo👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

Java の Enum は手軽にインターフェースを実装し、ポリモフィズムを作れるところが便利です。

Java

1/** 2 * 会員 3 */ 4class Member { 5 6 private int id; 7 8 private String name; 9 10 private MemberRank memberRank; 11 12 // アクセサ略 13} 14 15/** 16 * 会員ランクを表す列挙 17 */ 18enum MemberRank { 19 20 /** 一般会員 */ 21 GENERAL { 22 @Override 23 public BigDecimal getPrice(BigDecimal catalogPrice) { 24 // 一般会員は定価のまま 25 return catalogPrice; 26 } 27 }, 28 29 /** シルバー会員 */ 30 SILVER { 31 @Override 32 public BigDecimal getPrice(BigDecimal catalogPrice) { 33 // 5%引き 34 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05")); 35 return catalogPrice.subtract(discountPrice); 36 } 37 }, 38 39 /** ゴールド会員 */ 40 GOLD { 41 @Override 42 public BigDecimal getPrice(BigDecimal catalogPrice) { 43 // 10%引き 44 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1")); 45 return catalogPrice.subtract(discountPrice); 46 } 47 }; 48 49 /** 50 * @param catalogPrice 定価 51 * @return ランクに応じた金額 52 */ 53 public abstract BigDecimal getPrice(BigDecimal catalogPrice); 54 55}

このようなものを用意しておくと、ランク毎に if を切ることなく、
拡張性が高いコードになります。

Java

1// getMember はセッションなどからログイン中の会員情報を取得するものとする 2BigDecimal price = getMember().getMemberRank().getPrice(catalogPrice);

###追記1

MemberクラスのmemberRankプロパティに、各会員の状態を持たせる場合、どのような実装があり得ますか?

問題はこれです。
通常よくあるのは、データベースの会員情報から MemberRank を作成したいわけですから、
O/Rマッパーが Member取得時に自動で入れてくれたりもします。
データベースの値を MemberRank Enum に変換してくれます。

そういうのを省いて考えた場合どうなるかというと
例えば、以下のような感じだと台無しなわけです。

java

1if (rank.equals("general")) { 2 member.setMemberRank(MemberRank.GENERAL); 3} else if (rank.equals("silver")) { 4 member.setMemberRank(MemberRank.SILVER); 5} else if (rank.equals("gold")) { 6 member.setMemberRank(MemberRank.GOLD); 7}

こういった場合は、MemberRank に以下のようなファクトリーメソッドを用意するといいです。

java

1public static MemberRank of(String name) { 2 return MemberRank.valueOf(name.toUpperCase()); 3}

そうすると、以下のように設定できます。

java

1// rank は、"general", "silver", "gold" の文字列 2member.setMemberRank(MemberRank.of(rank));

データに "general" などの文字列ではなく、
一般会員 = 1, シルバー会員 = 2, ゴールド会員 = 3 などのように
数値として持ちたいという事も多いでしょう。
その時は、MemberRankを以下のようにします。

java

1/** 2 * 会員ランクを表す列挙 3 */ 4enum MemberRank { 5 6 /** 一般会員 */ 7 GENERAL(1) { 8 @Override 9 public BigDecimal getPrice(BigDecimal catalogPrice) { 10 // 一般会員は定価のまま 11 return catalogPrice; 12 } 13 }, 14 15 /** シルバー会員 */ 16 SILVER(2) { 17 @Override 18 public BigDecimal getPrice(BigDecimal catalogPrice) { 19 // 5%引き 20 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05")); 21 return catalogPrice.subtract(discountPrice); 22 } 23 }, 24 25 /** ゴールド会員 */ 26 GOLD(3) { 27 @Override 28 public BigDecimal getPrice(BigDecimal catalogPrice) { 29 // 10%引き 30 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1")); 31 return catalogPrice.subtract(discountPrice); 32 } 33 }; 34 35 /** 各列挙が持つ値 */ 36 private int value; 37 38 /** 39 * コンストラクタ 40 */ 41 private MemberRank(int value) { 42 this.value = value; 43 } 44 45 /** 46 * @param catalogPrice 定価 47 * @return ランクに応じた金額 48 */ 49 public abstract BigDecimal getPrice(BigDecimal catalogPrice); 50 51 /** 52 * ファクトリー 53 */ 54 public static MemberRank of(int value) { 55 return Stream.of(MemberRank.values()).filter(r -> r.value == value) 56 .findFirst().orElseThrow(IllegalArgumentException::new); 57 } 58}

そうすると、以下のように設定できます。

java

1// rank は、1, 2, 3 の数値 2member.setMemberRank(MemberRank.of(rank));

###追記2

getPriceのようなメソッドを他にもいくつか追加したい場合、どういったクラス構成が望ましいですか?

会員ランク毎に必要な振る舞いであれば、MemberRankで全て実装すべきです。
例えば以下は、購入金額から付与されるポイントを計算するメソッドと
特別会員かどうかを判断するメソッドを追加してみました。
これは、特別会員だけが見れる項目や、特別会員専用ページなどの制御で使うことを想定しています。

java

1/** 2 * 会員ランクを表す列挙 3 */ 4enum MemberRank { 5 6 /** 一般会員 */ 7 GENERAL(1) { 8 @Override 9 public BigDecimal getPrice(BigDecimal catalogPrice) { 10 // 一般会員は定価のまま 11 return catalogPrice; 12 } 13 14 @Override 15 public BigDecimal getPoint(BigDecimal totalPrice) { 16 // 一般会員は購入金額の1%をポイント付与 17 BigDecimal percentage = new BigDecimal("0.01"); 18 return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN); 19 } 20 21 @Override 22 public boolean isSpecialMember() { 23 // 一般会員は特別会員でない 24 return false; 25 } 26 }, 27 28 /** シルバー会員 */ 29 SILVER(2) { 30 @Override 31 public BigDecimal getPrice(BigDecimal catalogPrice) { 32 // 5%引き 33 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05")); 34 return catalogPrice.subtract(discountPrice); 35 } 36 37 @Override 38 public BigDecimal getPoint(BigDecimal totalPrice) { 39 // シルバー会員は購入金額の2%をポイント付与 40 BigDecimal percentage = new BigDecimal("0.02"); 41 return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN); 42 } 43 44 @Override 45 public boolean isSpecialMember() { 46 // シルバー会員は特別会員でない 47 return false; 48 } 49 }, 50 51 /** ゴールド会員 */ 52 GOLD(3) { 53 @Override 54 public BigDecimal getPrice(BigDecimal catalogPrice) { 55 // 10%引き 56 BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1")); 57 return catalogPrice.subtract(discountPrice); 58 } 59 60 @Override 61 public BigDecimal getPoint(BigDecimal totalPrice) { 62 // ゴールド会員は購入金額の3%をポイント付与 63 BigDecimal percentage = new BigDecimal("0.03"); 64 return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN); 65 } 66 67 @Override 68 public boolean isSpecialMember() { 69 // ゴールド会員は特別会員 70 return true; 71 } 72 }; 73 74 /** 75 * @param catalogPrice 定価 76 * @return ランクに応じた金額 77 */ 78 public abstract BigDecimal getPrice(BigDecimal catalogPrice); 79 80 /** 81 * @param totalPrice 購入合計金額 82 * @return ランクに応じたポイント数 83 */ 84 public abstract BigDecimal getPoint(BigDecimal totalPrice); 85 86 /** 87 * 特別会員かどうかを返す。 88 * <pre> 89 * 特別会員とはゴールド会員以上の会員のこと。 90 * (現状、ゴールド会員が最高ランクだが、来年にはプラチナ会員を導入予定) 91 * </pre> 92 * 93 * @return true..特別会員 94 */ 95 public abstract boolean isSpecialMember(); 96 97 /** 各列挙が持つ値 */ 98 private int value; 99 100 /** 101 * コンストラクタ 102 */ 103 private MemberRank(int value) { 104 this.value = value; 105 } 106 107 /** 108 * ファクトリー 109 */ 110 public static MemberRank of(int value) { 111 return Stream.of(MemberRank.values()).filter(r -> r.value == value) 112 .findFirst().orElseThrow(IllegalArgumentException::new); 113 } 114}

投稿2016/04/05 05:05

編集2016/04/06 04:40
root_jp

総合スコア4666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

wordconverter

2016/04/05 15:04

>root_jp様 ご回答ありがとうございます。 getter、setterからなるDTOクラスに、定数固有メソッドが実装されたプロパティを用意したわけですね。なるほど。素晴らしいです。定数固有メソッドの使いどころがよくわかりました。 一点追加で教えて頂ければと思うのですが、MemberクラスのmemberRankプロパティに、各会員の状態を持たせる場合、どのような実装があり得ますか? 事前に、setMemberRank()で同クラスを引数に設定してあげる感じでしょうか?
root_jp

2016/04/05 16:32

追記しました。
wordconverter

2016/04/06 03:46 編集

>root_jp様 ご回答ありがとうございます。 そうですね、「member.setMemberRank(MemberRank.・・・)」の際に分岐をかけてしまうと、せっかく「getMember().getMemberRank().getPrice(catalogPrice)」でポリモーフィズムした意味が薄れてしまいますからね。 引数に文字列を渡すファクトリーメソッドは、他のサイトに事例が多くあったので覚えていましたが、数値を渡すファクトリーメソッドについては探している最中でした。 (※データベースには通常、区分値として数値が入っているはずなので、このメソッドを使う場合が多そうですね。) Streamに関しては不勉強ですが、「public static MemberRank of(int value)」では、MemberRankクラスの全ての定数(GENERAL(1)、SILVER(2)、GOLD(3))を走査して、入力データ(1or2or3)に該当するMemberRankオブジェクトが返却されるというわけですね。(4などが入れば例外がスローされる。) こういった処理は通常、「定数クラス+共通関数クラス」などに書かれそうですが、enumクラス(MemberRank)に集約でき、なおかつそれをDTOクラスにプロパティとして設定してあげるだけで使用可能となるのが非常に便利だと思いました。 ■余力があれば・・・ 余力があれば教えてほしいのですが、getPriceのようなメソッドを他にもいくつか追加したい場合、どういったクラス構成が望ましいですか?
root_jp

2016/04/06 04:44

追記しました。
wordconverter

2016/04/13 12:34

>root_jp様 ご回答ありがとうございます。(返信が遅れてしまってすみません。。) なるほど、業務ロジックをゴリゴリ書くわけでなければ、MemberRankクラスで全てのメソッドを書ききったほうがよさそうですね。あまり処理が長くなるようであれば、enumではない通常のインターフェースのデザインパターンを検討するといった感じでしょうか。 本件で一番知りたいところであった、enumでポリモーフィズムを手軽に実現できる具体的な方法を示してくれましたので、ベストアンサーとさせていただきます。 丁寧なご説明ありがとうございました。
guest

0

①と②の延長線上だと思いますが、個人的によく使うのが下記です。

モードなどを指定するメソッド
→int型ではなくEnumを良く使います。
(タイプセーフなメソッドを作れる。言い方は悪いですが、レベルが低いプログラマでも変な書き方をさせないことができます。int型だからといって全く関係のないint型の定数を代入していたりする人も居たり。。。)

データベースを更新するようなメソッドでDBとカラムを指定するためにEnumとEnumMapを利用
・update(DB名.Column名, String value)
└どのDBのカラムをどの値でアップデートするかを指定
→update(EnumMap<DB名.Column名, value>)
└複数のカラムをアップデートするときはこちらを利用

アップデートするための似たようなメソッドが大量にごろごろ転がっていたのですが、これですっきりしました。

投稿2016/04/02 15:20

Odacchi

総合スコア907

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

wordconverter

2016/04/03 01:46

>Odacchi様 ご回答ありがとうございます。 なるほど、これだと存在しないカラムに関しては、sql実行前にコンパイルエラーにかけてくれる恩恵がありますね。型安全を担保するには、DBの各テーブルとenumクラスが同期がとれている必要がありますが。。カラムの型まで含めてコンパイルエラーにかけてくれたらより嬉しいですね。 ただし、個人レベルではなく、プロジェクト全体で行うことのように見えます。こういったことを担えるフレームワークがありそうですね。テーブルが膨大にあり、なおかつ頻繁にテーブル定義に変更が入るプロジェクトだと、ビルドに時間がかかりそうです。。
guest

0

調べてみたところ、enumをシングルトンパターンで利用する、というアイデアを見つけました。
http://stackoverflow.com/questions/26285520/implementing-singleton-with-an-enum-in-java

enumがインスタンス化不可能(かつ継承不可能)であることを利用したもののようです。

投稿2016/04/02 15:02

KiyoshiMotoki

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

SwingのJComboBoxの選択候補にenumを適用したことがあります。簡潔に書けました。
http://d.hatena.ne.jp/torutk/20080105/p1

投稿2016/04/05 07:36

boochnich

総合スコア194

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

よく使うのは、こういう表現でしょうか?

enum Country {
JAPAN("JP", "JPN"),
;

public final String alpha2;// ISOの2文字 国コード
public final String alpha3; // ISOの3文字 国コード

public Country(String alpha2, String alpha3) {
this.alpha2 = alpha2;
this.alpha3 = alpha3;
}
}

これで、

Country.JAPAN.alpha2.equals("JP"); // true
Country.JAPAN.alpha3.equals("JPN"); // true

と出来ます。
※ iPhoneからの書き込みで面倒なので、アクセッサーメソッドは省略しています(アクセッサーを作ることの是非もありますし)

投稿2016/04/04 14:10

poad1010

総合スコア30

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問