Java SE 5.0 TigerのコアAPIでは、@Deprecatedが採用されている程度しかアノテーションは使われていないことを説明した。コアAPIにおけるアノテーションの本格的な採用はJava EE 5.0やJava SE 6.0 Mustang以降になるのだろう。
まず、もっとも簡単なカスタムアノテーションの実装を通じて、アノテーションがどのように実現されているのかを調べてみよう。
簡単なカスタムアノテーション
もっとも簡単なアノテーションタイプの例をリスト1に示す。リスト1はNothingというカスタムアノテーションを定義するもので、この場合はマーカアノテーションを定義していることになる。
リスト1 アノテーションタイプの例 - Nothing.java
@interface Nothing { }
つぎに、作成したアノテーションを使用したソースコードの例をリスト2に示す。ここではクラス、メソッド、ローカル変数に対してアノテーションを指定している。リスト1およびリスト2をコンパイルして実行すると、Welcome to the annotation worldという文字列が標準出力に出力される。
リスト2 カスタムアノテーションの使用例 - Test.java
@Nothing
public class Test
{
@Nothing
public static void main(String[] anyArguments)
{
@Nothing String message = "Welcome to the annotation world";
System.out.println(message);
}
}
ここで注目するべきは、アノテーションを挿入しているものの、プログラム本来の動きにはまったく影響を与えていないことにある。アノテーションだけでは、プログラムの動作に対してなんら影響を与えない。挿入されているアノテーションを使用するときにはじめてアノテーションが本当の効果を発揮することになる。
アノテーションの仕組みを探る
アノテーションタイプやアノテーションを含んだ.classファイルを逆コンパイルして、実際にアノテーションがどのように実現されているかを調べてみよう。リスト1およびリスト2を逆コンパイルしたものをリスト3およびリスト4に示す。
アノテーションタイプを逆コンパイルした結果リスト3に注目してほしい。ここではjava.lang.annotation.Annotationを継承したインターフェースとして認識されていることがわかる。これがアノテーション定義の実体だ。
リスト3 Nothing.javaをコンパイルしたものを逆コンパイルした結果
import java.lang.annotation.Annotation;
interface Nothing extends Annotation
{
}
次に、アノテーションを挿入したクラスを逆コンパイルした結果だが、こちらにはアノテーションがどこにも表示されていないことがわかる。これは逆コンパイルを実行するアプリケーションがアノテーションを認識していないことが原因なのだが、ようするに、従来の仕組みではアノテーション部分はコードとしては認識されていないということがわかるだろう。
リスト4 Test.javaをコンパイルしたものを逆コンパイルした結果
import java.io.PrintStream;
public class Test
{
public static void main(String args[])
{
String s = "Welcome to the annotation world";
System.out.println(s);
}
}
アノテーションはどこに?
ではアノテーション情報はどこにいったのかということになる。strings(1)を使ってクラスファイルの内容をなでてみると、RuntimeInvisibleAnnotations LNothing;という記述が見える。.classファイルにアノテーションの情報は含まれていることはわかる。
リスト5 Test.classをstrings(1)で処理した結果
<init>
Code
LineNumberTable
main
([Ljava/lang/String;)V
RuntimeInvisibleAnnotations
LNothing;
SourceFile
Test.java
Welcome to the annotation world
Test
java/lang/Object
java/lang/System
Ljava/io/PrintStream;
java/io/PrintStream
println
(Ljava/lang/String;)V
javap(1)コマンドを使って.classファイルを表示すると、たしかにRuntimeInvisibleAnnotationsやLNothing;の記述があることがわかる。Java SE 5.0 Tigerから.classファイルのフォーマットも変更され、アノテーション情報が挿入されている。
リスト6 Test.classをjavap(1)で処理した結果
Compiled from "Test.java"
public class Test extends java.lang.Object
SourceFile: "Test.java"
RuntimeInvisibleAnnotations: length = 0x6
00 01 00 0E 00 00
minor version: 0
major version: 49
Constant pool:
const #1 = Method #6.#17; // java/lang/Object."<init>":()V
const #2 = String #18; // Welcome to the annotation world
const #3 = Field #19.#20; // java/lang/System.out:Ljava/io/PrintStream;
const #4 = Method #21.#22; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #5 = class #23; // Test
const #6 = class #24; // java/lang/Object
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz main;
const #12 = Asciz ([Ljava/lang/String;)V;
const #13 = Asciz RuntimeInvisibleAnnotations;
const #14 = Asciz LNothing;;
const #15 = Asciz SourceFile;
const #16 = Asciz Test.java;
const #17 = NameAndType #7:#8;// "<init>":()V
const #18 = Asciz Welcome to the annotation world;
const #19 = class #25; // java/lang/System
const #20 = NameAndType #26:#27;// out:Ljava/io/PrintStream;
const #21 = class #28; // java/io/PrintStream
const #22 = NameAndType #29:#30;// println:(Ljava/lang/String;)V
const #23 = Asciz Test;
const #24 = Asciz java/lang/Object;
const #25 = Asciz java/lang/System;
const #26 = Asciz out;
const #27 = Asciz Ljava/io/PrintStream;;
const #28 = Asciz java/io/PrintStream;
const #29 = Asciz println;
const #30 = Asciz (Ljava/lang/String;)V;
{
public Test();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=2, Args_size=1
0: ldc #2; //String Welcome to the annotation world
2: astore_1
3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 7: 0
line 9: 3
line 10: 10
RuntimeInvisibleAnnotations: length = 0x6
00 01 00 0E 00 00
}
このアノテーション情報を使って、クラスのロード時や実行時に処理を行おうとするのが、アノテーションのもたらす本質的な効果ということになる。
アノテーションを処理 apt(1)
アノテーションを処理するには、特定のメソッドを実装したアノテーションを処理するクラスを作成する。アノテーションは作成したクラスの処理を通じて処理されるようになる。Java SE 5.0 Tigerからはアノテーションを処理する外部コマンドとしてapt(1) (Annotation Processing Tool)も付属している。
アノテーションは用意された機能を使う分には簡単だが、カスタムアノテーションを定義し、定義したアノテーションを処理する実装を行うのは、なかなか面倒くさい。この点はGeneric Javaと同じだ。Generic Javaやアノテーションは、使い分には便利なEoDだが、作る方はなかなか骨がおれる。
リスト1およびリスト2は簡単な例だが、アノテーション実装の基本となるものだ。このくらいの実装はそのまま覚えてしまおう。
学生のための就職情報サイト「毎日就職ナビ」。4,000社以上の新卒採用情報が常時掲載され、社内の雰囲気が伝わる情報画面、さまざまな項目での会社検索、エントリーや説明会検索など、機能も充実。無料適職診断、就活Q & A、エントリーシート添削講座など、就職活動に役立つ記事も満載です。研究者、エンジニアを目指す学生の方々も是非エントリーしてください。お待ちしています!
毎日コミュニケーションズ 就職情報事業本部はプライバシーマークを取得しています。