effective java 輪読会 第6章 項目35-37
TRANSCRIPT
Effective Java 輪読会 第5回(項目35~37)
2014/1/29
開発部 野口
項目35
命名パターンよりアノテーションを選ぶ
命名パターン
何らかのプログラム要素がツールやフレームワークによる特別な処理を要求していることを示すためのテクニック
例)JUnit テストフレームワークのテストメソッドは「test」で始まることが要求されていた
命名パターンの短所1
誤字に気づかない
tsetSafetyOverride と名付けてしまっても、JUnit
は何も言わない
黙って、実行しないだけ 余談: CppUnit-x では(命名パターンとはちょっと違いますが)関数ポインタを TestSuite に登録するコードが必要で、書き忘れると何も起こらなかったので、うっかりミスがないように、必ず最初はテストを fail させるようにしていました
命名パターンの短所2
適切なプログラム要素にだけそれらが使用されることを保証する方法がない
「testSafetyMechanisms クラス」を定義したとき、そのクラスのメソッドがテストとして実行されるかな?と期待するかもしれないが、されない
命名パターンの短所3
プログラム要素にパラメータ値を関連付ける良い方法を提供していない
「特定の例外をスローした場合にだけ成功する種類のテスト」をスマートに書けない
たとえばtestThrowsException_throws_DataSpiderException()
のように命名規則を用いることもできるが、脆弱 他の型が適切な場所では、文字列を避ける(項目50)
実行時にしか間違いに気づけない
アノテーション
これらの問題をすべて上手く解決します!
// マーカーアノテーション型宣言import java.lang.annotation.*;
/**
* このアノテーションが付けられたメソッドがテストメソッドであることを示す。* パラメータなしの static のメソッドに対してだけ使用すること。*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
解説(1/3)
@Retention / @Target
メタアノテーション
アノテーション型の宣言に対するアノテーション
解説(2/3)
@Retention(RetentionPolicy.RUNTIME)
Test アノテーションを実行時に保持すべきことを示す
このメタアノテーションがないと、Test アノテーションはテストツールから見えなくなる
@Target(ElementType.METHOD)
Test アノテーションがメソッド宣言に対してのみ許されていることを示す
クラス宣言、フィールド宣言、その他のプログラム要素には @Test を適用できない
解説(3/3)
「パラメータなしの static のメソッドに対してだけ使用すること」
これを強制する方法はない
コードを書く人がこれを守らなかった場合、実行時に失敗する
JUnit 4 では、java.lang.Exception がスローされます
(動かしてみましょう)
パラメータを持つアノテーション型(1/2)
import java.lang.annotation.*;
/**
* アノテーションが付けられたメソッドは、成功するには指定された例外を
* スローしなければならないテストメソッドであることを示す。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Exception> value();
}
パラメータを持つアノテーション型(2/2)
パラメータ型は、Class<? extends Exception>
Exception を拡張した何らかのクラスに対するClass オブジェクト
境界型トークンの使用例
(サンプルコードを実際に動かしてみましょう)
まとめ
ソースファイルに情報を追加するためのプログラミングを必要とするツールを書くのであれば、適切なアノテーション型の集合を定義する
今やアノテーションを利用できるので、命名パターンを使用するのは論外
すべてのプログラマは Java プラットフォームが提供している事前に定義されたアノテーション型を使用すべき 例)@Override(項目36)、@SuppressWarnings(項目24)、@Deprecated
IDE や静的解析ツールが提供しているアノテーションを使用することを検討する
項目36
常に Override アノテーションを使用する
Override アノテーション
メソッド宣言にだけ使用できる
アノテーションが付けられたメソッド宣言がスーパータイプの宣言をオーバーライドしていることを示す
非道なバグの多くから保護してくれる
例)Bigram クラス(pp.170)の equals メソッド
Override アノテーションを付けるとき、付けないとき(1/3)
スーパークラスの宣言をオーバーライドしているすべてのメソッド宣言に付けるべき
抽象ではないクラスで、抽象メソッドをオーバーライドしているときは、不要
@Override を付けなくても、オーバーライドしていないときにはコンパイルエラーになるため
付けても問題はない
Override アノテーションを付けるとき、付けないとき(2/3)
コードインスペクション
Override アノテーションが付いていなくてスーパークラスのメソッドをオーバーライドしているメソッドがあれば、IDE によっては警告を出してくれるものがある
Override アノテーションを付けるとき、付けないとき(3/3)
インタフェースへの @Override
リリース 1.6 以降では、インタフェースからの宣言をオーバーライドしているメソッド宣言にもOverride アノテーションを付けることが可能
前述の抽象クラスと同様、具象クラスでのオーバーライドに関しては、不要ただし、抽象クラスやインタフェースにおいては必ず付けるようにする価値はある 例)Set インタフェースは Collection インタフェースに新たなメソッドを追加していない。すべてのメソッドに@Override を付けることで、それを保証できる(誤って新たなメソッドを追加してしまうことがない)
まとめ
スーパータイプの宣言をオーバーライドしているすべてのメソッド宣言に Override アノテーションを使用することで、コンパイラの保護が得られる
具象クラスでは、抽象メソッド宣言をオーバーライドしているメソッドにアノテーションを付ける必要はない
が、付けても害はない
項目37 型を定義するためにマーカーインタフェースを使用する
マーカーインタフェース
メソッド宣言を含んでいないインタフェース
そのインタフェースを実装しているクラスが何らかの特性を持っていると単に指定している
例)Serializable インタフェース
マーカーインタフェースのマーカーアノテーションに対する長所1 マーカーインタフェースは、マークされたクラスのインスタンスが実装している型を定義する
例)ObjectOutputStream.write(Object) がObjectOutputStream.write(Serializable) ならよかったが……。
マーカーアノテーションは型を定義しない
マーカーインタフェースのマーカーアノテーションに対する長所2 より正確に対象を特定できる
そのマーカーが適用可能な唯一のインタフェースをマーカーに拡張させることができる
すべてのマークされた型が、そのマーカーが適用可能な唯一のインタフェースのサブタイプであることも保証される
例)Set インタフェース
マーカーアノテーションのマーカーインタフェースに対する長所1 デフォルトを持つアノテーション型要素を 1
つ以上追加することで、すでに使用された後でもアノテーション型に情報を追加できる
インタフェースの場合、一旦実装された後にメソッドを追加することは一般に不可能
マーカーアノテーションのマーカーインタフェースに対する長所2 大きなアノテーション機構の一部
フレームワークの一貫性に寄与する
マーカーアノテーションとマーカーインタフェースの使い分け(1/2) クラスやインタフェース以外のプログラム要素に対してマーカーが適用される
→マーカーアノテーション
マーカーがクラスとインタフェースだけに適用される
かつ、このマークを持つオブジェクトだけを受け付ける 1 個以上のメソッドを書きたい
→マーカーインタフェース
マーカーアノテーションとマーカーインタフェースの使い分け(2/2) マーカーがクラスとインタフェースだけに適用される
かつ、このマーカーの使用を、特定のインタフェースの要素に永久に制限したい
→マーカーインタフェース
それ以外
→マーカーアノテーション
まとめ
関連付けられた新たなメソッドを持たない型を定義したいのであれば、マーカーインタフェースを使用する
クラスとインタフェース以外のプログラム要素をマークしたいのであれば、マーカーアノテーションを使用する
対象が ElementType.TYPE であるマーカーアノテーションは要注意
要するに、型を定義したいのであれば、インタフェースを使用すること