Download - Effective java 輪読会 第6章 項目32-34
![Page 1: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/1.jpg)
Effective Java 輪読会Item 32-34
開発部 陳映融 2014/2/5
![Page 2: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/2.jpg)
第6章 enumとアノテーション
項目30 int 定数の代わりに enum を使用する
項目31 序数の代わりにインスタンスフィールドを使用する
項目32 ビットフィールドの代わりに EnumSet を使用する
項目33 序数インデックスの代わりに EnumMap を使用する
項目34 拡張可能な enum をインタフェースで模倣する
項目35 命名パターンよりアノテーションを選ぶ
項目36 常に Override アノテーションを使用する
項目37 型を定義するためにマーカーインタフェースを使用する
2
![Page 3: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/3.jpg)
Item 32ビットフィールドの代わりに EnumSet を使用する
![Page 4: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/4.jpg)
集合での列挙型の要素の使用
4
例えば、文字のスタイルを表現する時
従来の方法だと int enum パターンを使用
今頃の Java では EnumSet を使用するべき
// ビットフィールド列挙定数 - 廃れている!public class Text {
public static final int STYLE_BOLD = 1 << 0; // 1public static final int STYLE_ITALIC = 1 << 1; // 2public static final int STYLE_UNDERLINE = 1 << 2; // 4public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
// パラメータは、0 個以上の STYLE_ 定数のビットpublic void applyStyles(int styles) { ... }
}
// EnumSet - ビットフィールドの最新の置換public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// どんな Set でも渡せるが、 EnumSet が明らかに最善public void applyStyles(Set<Style> styles) { ... }
}
![Page 5: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/5.jpg)
従来の方法の場合 int enum パターン使用
ビット演算で集合操作を効率よく行うことができる
和集合(要素追加): A | B
共通集合(要素存在確認): A & B
要素削除: A & (~B)
要素数が多くなると、数値の解釈が困難
例えば最大 26 個の要素を持つ集合を定義すると
5
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
public static final int ELEM_A = 1 << 0; // 1public static final int ELEM_B = 1 << 1; // 2...public static final int ELEM_Z = 1 << 25; // 2^25 = 33554432 (!)...private int elemSet = 0;...System.out.println(Integer.toBinaryString(elemSet)); // こんなの出されても分からないよ...>_<
![Page 6: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/6.jpg)
従来の方法の場合 int enum パターン使用(続き)
型安全ではない
集合内の要素をイテレートするのは大変
要素数が int や long 型のビット数を超えるとさらに大変…
6
// 枠線設定用定数public static final int UPPER_BORDER = 1 << 4; // 16...text.applyStyles(STYLE_BOLD | UPPER_BORDER); // 関係のない枠線定数を入れても文句を言ってくれない...
// 定数に対応する情報も自前で管理しなければいけないprivate static final String STYLE_NAME[] =
{ "BOLD", "ITALIC", "UNDERLINE", "STRIKETHROUGHT" };...// 要素をイテレートして、対応するスタイル名を出力for (int offset = 0; offset < 4; offset++) {
if ((this.styles & 1 << offset) != 0) {System.out.printf(“%s ”, STYLE_NAME[offset]);
}}
![Page 7: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/7.jpg)
新しい方法の場合 EnumSet使用
型安全、可読性向上
要素数が 64個以下なら、一つの long で表現される
集合操作はビット演算で実装される
要素数が 65 を超えた場合、 longの配列で表現される
要素数が多くても重労働にならない
7
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {/*** Bit vector representation of this set. The 2^k bit indicates the* presence of universe[k] in this set.*/private long elements = 0L;
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {/*** Bit vector representation of this set. The ith bit of the jth* element of this array represents the presence of universe[64*j +i]* in this set.*/private long elements[];
![Page 8: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/8.jpg)
まとめ
8
列挙型が集合内で使用されるだけでは、ビットフィールドで表現する理由にならない
EnumSet はビットフィールドの簡潔性とパフォーマンスと、enum 型の多くの利点を持っている
項目30 「int 定数の代わりに enum を使用する」 を参照
EnumSet の短所は、不変な EnumSet を生成できない
Java7 でも Collections.unmodifiableSet でラップすることになる
![Page 9: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/9.jpg)
Item 33序数インデックスの代わりに EnumMap を使用する
![Page 10: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/10.jpg)
序数で配列インデックス?(1)
10
料理のハーブを表すクラス Herb
庭園内の植物で、種類ごとのハーブのセットを作りたい
ならば、種類をハーブのセットのインデックスにすればいいじゃない?
public class Herb {public enum Type { ANNUAL, PERENNIAL, BIENNIAL }
private final String name;private final Type type;...
}
...Herb[] garden = ...; // 庭園にあるハーブ
![Page 11: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/11.jpg)
実際作ってみる…
11
セットの配列と ordinal() を使用したインデックスで作ると
enum をキーにして EnumMap で作ると
// 配列をインデックスするのに ordinal() を使用 – これはやってはいけない!
Set<Herb>[] herbsByType = // Type.ordinal() でインデックスされる(Set<Herb>[]) new Set[Type.values().length]; // 配列はジェネリックスと互換性がない、警告が出る
for (int i = 0; i < herbsByType.length; i++)herbsByType[i] = new HashSet<>();
// 配列アクセス時に正しいインデックス値を入れないと ArrayIndexOutOfBoundException をスローしてしまうfor (Herb h : garden)
herbsByType[h.type().ordinal()].add(h);
// データを enum と関連付けるために EnumMap を使用
EnumMap<Type, Set<Herb>> herbsByType =new EnumMap<>(Type.class); // 実行時ジェネリック型情報はキー型の Class オブジェクトで提供
for (Type t : Type.values())herbsByType.put(t, new HashSet<Herb>());
for (Herb h : garden)herbsByType.get(h.type()).add(h); // インデックスの境界に気にする必要ないほか、キーの型チェックもある
![Page 12: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/12.jpg)
序数で配列インデックス?(2)
12
物質の相を表す enum 型クラス Phase
洗練されているように見えるが…
遷移表の検証が困難、項目の追加・削除でバグを織り込みやすい
⇒ 配列で実装される「状態転移表」のメンテナンス性は総じて低い
// 配列の配列をインデックスするのに ordinal() を使用 – これはやってはいけないpublic enum Phase {
SOLID, LIQUID, GAS;public enum Transition {
MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
// src の序数で行が、dst の序数で列がインデックスされるprivate static final Transition[][] TRANSITIONS = { // 転移表に誤りがあると大変!
{null, MELT, SUBLIME}, // 間違えると、 ArrayIndexOutOfBoundException や{FREEZE, null, BOIL}, // NullPointerException が出るかもしれないし、{DEPOSIT, CONDENSE, null} // 何も出ないでそのまま見逃してしまうかもしれない (!!)
};public static Transition from(Phase src, Phase dst) {
return TRANSITIONS[src.ordinal()][dst.ordinal()];}
}}
![Page 13: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/13.jpg)
EnumMap で作ってみる
13
public enum Phase {SOLID, LIQUID, GAS;public enum Transition {
// 相転移の転移元と転移先の相を定数固有データとして定義MELT(SOLID,LIQUID), FREEZE(LIQUID,SOLID),BOIL(LIQUID,GAS), CONDENSE(GAS,LIQUID),SUBLIME(SOLID,GAS), DEPOSIT(GAS,SOLID);private final Phase src;private final Phase dst;Transition(Phase src, Phase dst) {
this.src = src;this.dst = dst;
}
// 相転移マップを初期化private static final Map<Phase, Map<Phase, Transition>> m = new EnumMap<>(Phase.class);static {
for (Phase p : Phase.values())m.put(p, new EnumMap<Phase, Transition>(Phase.class));
for (Transition t : Transition.values())m.get(t.src).put(t.dst, t);
}
public static Transition from(Phase src, Phase dst) {return m.get(src).get(dst);
}}
}
![Page 14: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/14.jpg)
enum 値を追加する場合の比較
14
相: PLASMA 追加
相転移: IONIZE(GASPLASMA), DEIONIZE(PLASMAGAS) 追加
配列インデックスに ordinal() 使用時
EnumMap 使用時
public enum Transition {MELT(SOLID,LIQUID), FREEZE(LIQUID,SOLID),BOIL(LIQUID,GAS), CONDENSE(GAS,LIQUID),SUBLIME(SOLID,GAS), DEPOSIT(GAS,SOLID), // それぞれの値の情報が独立して、干渉し合わないIONIZE(GAS,PLASMA), DEIONIZE(PLASMA,GAS); // この一行追加で十分...
}
public enum Transition {MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT, IONIZE, DEIONIZE;private static final Transition[][] TRANSITIONS = {
{null, MELT, SUBLIME, null}, {FREEZE, null, BOIL, null},{DEPOSIT, CONDENSE, null, IONIZE}, {null, null, DEIONIZE, null}
}; // 実際の修正場所 PLASMA の序数に依存、間違えると今までの動作もおかしくなる...
}
![Page 15: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/15.jpg)
まとめ
15
配列をインデックスするために序数を使用することが適切であることはめったにない
代わりに EnumMap を使用する
多次元の関係を表示する場合は EnumMap<...,EnumMap<...>> を使用する
![Page 16: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/16.jpg)
Item 34拡張可能な enum をインタフェースで模倣する
![Page 17: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/17.jpg)
enum 型の拡張性について
17
ほとんど場合は、enum 型の拡張性は間違った考え
もし拡張できたとすると
拡張された型の要素が基底型の要素であり
基底型の要素は拡張された型の要素ではない
⇒ 混乱を生じることに
拡張可能な列挙型を使う場面
オペレーションコード
何らかの操作を表す要素を持つ列挙型
API が提供する操作の集合の拡張
⇒ enum が任意のインタフェースを実装できる事実を利用して達成する
![Page 18: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/18.jpg)
項目30 の Operation 型の場合
18
定数固有メソッド実装を持つ enum 型
操作を模倣するために抽出されたインタフェース定義
public enum Operation {PLUS("+") { public double apply(double x, double y) { return x + y; } },MINUS("-") { public double apply(double x, double y) { return x - y; } },TIMES("*") { public double apply(double x, double y) { return x * y; } },DEVIDE("/") { public double apply(double x, double y) { return x / y; } };
private final String symbol;Operation(String symbol) { this.symbol = symbol; }@Override public String toString() { return symbol; }
abstract double apply(double x, double y); // 模倣するためにインタフェースとして抽出}
public interface Operation {double apply(double x, double y);
}
![Page 19: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/19.jpg)
enum 型によるインタフェース実装
19
基本 enum 型
模倣された拡張 enum 型
public enum BasicOperation implements Operation {PLUS("+") { public double apply(double x, double y) { return x + y; } },MINUS("-") { public double apply(double x, double y) { return x - y; } },TIMES("*") { public double apply(double x, double y) { return x * y; } },DEVIDE("/") { public double apply(double x, double y) { return x / y; } };
private final String symbol;BasicOperation(String symbol) { this.symbol = symbol; }@Override public String toString() { return symbol; }
}
public enum ExtendedOperation implements Operation {EXP("^") { public double apply(double x, double y) { return Math.pow(x, y); } },REMAINDER("%") { public double apply(double x, double y) { return x % y; } };
private final String symbol;ExtendedOperation(String symbol) { this.symbol = symbol; }@Override public String toString() { return symbol; }
}
![Page 20: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/20.jpg)
拡張 enum 型の使用
20
インタフェースが期待された場所、拡張 enum 型も使用可能
境界型トークン使用(項目29)
パラメータ opSet の宣言は、その Class オブジェクトが enum でかつOperation のサブタイプであることを保証
境界ワイルドカード型使用(項目28)
この場合、パラメータ opSet の要素は Operation のサブタイプであることが要求されるが、要素が enum でなくてもよい
その代わり、操作の実装に EnumSet と EnumMap は使用できない
⇒ 複数の実装型を組み合わせる必要がなければ、境界型トークンが良さそう
private static <T extends Enum<T> & Operation> void test(Class<T> opSet, double x, double y) {for (T op : opSet.getEnumConstants())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));}
private static void test(Collection<? extends Operation> opSet, double x, double y) {for (Operation op : opSet)
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));}
![Page 21: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/21.jpg)
インタフェースで模倣する手法の欠点
21
一つの enum 型が他の enum 型から実装を継承できない
Operation 型の場合は操作に関連付けられた記号の保存と取り出しのロジックが重複
重複するコード量が大きい場合、ヘルパークラスや static なヘルパーメソッドでカプセル化できる
public enum BasicOperation implements Operation {...private final String symbol;BasicOperation(String symbol) { this.symbol = symbol; }@Override public String toString() { return symbol; }
}
public enum ExtendedOperation implements Operation {...private final String symbol;ExtendedOperation(String symbol) { this.symbol = symbol; } // 実装を継承できないのでコードが重複@Override public String toString() { return symbol; } // 実装を継承できないのでコードが重複
}
![Page 22: Effective java 輪読会 第6章 項目32-34](https://reader037.vdocuments.pub/reader037/viewer/2022100300/55972c6a1a28ab49708b4817/html5/thumbnails/22.jpg)
まとめ
22
拡張可能な enum 型を書くことできないが
基本の enum 型に伴うインタフェースを書いて、そのインタフェースをその基本の enum 型に実装させることで模倣できる
クライアントがそのインタフェースを実装して独自の enum を作れる
API がインタフェースで書かれたとしたら、基本の enum 型を使用する場所でもそれらの enum 型を使用できる