2009年8月18日火曜日

Tigerでのアノテーション 第1回: Javaコードにメタデータを追加する

http://www.ibm.com/developerworks/jp/java/library/j-annotate1/

2004年 9月 02日
アノテーション(Annotations)はJ2SE 5.0 (Tiger)での新しい機能ですが、コアとなるJava言語に長年待ち望まれていたメタデータ機能をもたらします。2回シリーズの第1回として、今回はBrett McLaughlinが、なぜメタデータがそれほど便利なのかを説明します。またJava言語でのアノテーションについて紹介し、次にTigerに組み込まれているアノテーションについて見て行きます。第2回 (US)ではカスタムのアノテーションについて説明します。
プログラミング、特にJavaプログラミングにおける最新の傾向として、メタデータの使用が挙げられます。メタデータを単純に言えば、データに関するデータです。メタデータはドキュメンテーションの作成やコードの依存性追跡に、あるいはごく基本的なコンパイル時のチェックにも使われます。XDoclet(参考文献)のようなメタデータ用のツールが次々と現れたため、コアとしてのJava言語にこうした機能が追加され、ここしばらくはJavaプログラミング風景の一部となってきています。
J2SE 5.0(別名はTigerで、現在は2番目のベータ・リリースです)が入手できるようになるまでは、コアとしてのJava言語でメタデータ機能に最も近いものはJavadocによる手法でした。この手法では、特別なタグ・セットを使ってコードをマークアップしてからjavadocコマンドを実行し、タグが付加されているクラスを文書化してフォーマットしたHTMLページにタグを変換します。ただしJavadocはドキュメンテーションを生成するという目的以外には、データに到達するための確実、現実的で標準化された方法がないため、不十分なメタデータ・ツールです。HTMLコードがしばしばJavadoc出力に混在されてしまうという事実も、Javadocをドキュメンテーション生成以外の他の用途には使いにくくしています。
Tigerには、はるかに用途の広いメタデータ機能が、アノテーション(annotations)と呼ばれる新しい機能を通してコアのJava言語の中に用意されています。アノテーションはコードに追加する修飾子であり、パッケージ宣言やタイプ宣言、コンストラクター、メソッド、フィールド、パラメーター、変数などに適用することができます。Tigerには組み込みのアノテーションがあり、またカスタム・アノテーションもサポートしているので、自分でアノテーションを書くこともできます。この記事ではメタデータの利点の概要を説明し、Tigerに組み込まれているアノテーションについて紹介します。第2回の記事では、カスタム・アノテーションについて説明して行きます。ここで私はO'Reilly Media, Inc. に対して、Tigerに関する私の著書(参考文献)からコード例を引用することを承諾してくださったことに感謝致します。
メタデータの重要性
一般的に言ってメタデータの利点は、ドキュメンテーション、コンパイラー・チェック、コード解析という、3つの視点から見ることができます。コード・レベルのドキュメンテーションが最もよく引用される使い方です。メタデータによって、メソッドが他のメソッドに依存しているかどうか、またメソッドが不完全かどうか、あるクラスが別のクラスを参照する必要があるかどうか、などが分かるようになります。これは確かに便利なのですが、Java言語にメタデータを追加する理由として、ドキュメンテーションは最も優先度の低いものです。それに、もう既にツールが存在していてほぼ問題なく動作しているのに、誰がドキュメンテーションのツールを書こうと思うでしょうか。

第2回もお忘れなく!
カスタム・アノテーションについて説明した、このシリーズ「第2回」の記事も忘れずに読んでください。
コンパイラー・チェック
メタデータの利点としてもっと重要なのは、コンパイラーがメタデータを使って基本的なコンパイル時のチェックが行えるようになる、という点です。例えば後ほどOverrideアノテーションで説明しますが、Tigerで用意されているアノテーションを使うことによって、あるメソッドが、スーパークラスからの別メソッドをオーバーライドするように規定できるのです。Javaコンパイラーは、メタデータで示した振る舞いが、実際にコード・レベルで起きることを保証できるのです。そう聞くと、この種類のバグを追跡した経験がない人はバカバカしいと思うかも知れません。しかしベテランのJavaプログラマーの大部分は、なぜコードが動かないのかを見つけるために、夜遅くまで働き続けたことが二度や三度はあるのです。あるメソッドが間違ったパラメーターを持っていることにようやく気がつき、実際そのメソッドがスーパークラスからのメソッドをオーバーライドしていないとすると、痛い目に会うのです。メタデータを受け取るツールを使うことによって、このタイプのエラーを簡単に見つけられるようになり、Haloトーナメントを長々と実行する夜を過ごす必要もなくなるのです。

JSR 175
JSR 175,A Metadata Facility for the Java Programming Languageには、コアのJava言語にメタデータを採り入れるにあたっての正式な理由や仕様が出ています(参考文献)。このJSRによると、アノテーションは「プログラムの意味体系には直接の影響は与えない。ただし開発ツールやデプロイメント・ツールはこうしたアノテーションを読み取り、何らかの処理を行う。処理としては、アノテーションを含むプログラムに関連して使用するような追加的Javaソース・ファイルを生成する、またはXML文書やその他の成果物を生成する、などがある。」
コード解析
良質なアノテーションやメタデータ・ツールの機能の中で一番素晴らしいのは、コード解析のために別のデータが使えるという点でしょう。単純な場合では、コード・カタログを構築したり、必要な入力タイプを用意したり、戻りタイプを示したりすることができます。しかし、おそらく皆さんも考えていると思いますが、Javaのリフレクション(reflection)も同じことができます。結局、こうした情報の全てに関してコードを検証できるのです。これは表面的にはそう見えるかも知れませんが、現実的には必ずしもそうではありません。多くの場合メソッドは、実はそのメソッドが必要としていないタイプを入力として受け付けてしまったり、出力として戻してしまったりするのです。例えばパラメーターのタイプがObjectとし、そのメソッドがIntegerでのみ動作する、とします。これは、メソッドがオーバーライドされてスーパークラスが汎用パラメーターでそのメソッドを宣言する場合や、多くのシリアル化が行われているようなシステムでは容易に起こります。どちらの場合でもメタデータはコード解析ツールに対して、パラメーター・タイプはObjectですが、実際に必要なのはIntegerである、と指示できるのです。こうした解析は信じられないほど便利なもので、その重要性は言い尽くせないほどです。
より複雑な場合では、コード解析ツールはコード解析以外にも、あらゆる種類の余分な課題をこなすことができます。今日的な例としてはEJB(Enterprise JavaBean)コンポーネントです。ごく単純なEJBシステムであっても、依存性や複雑さはめまいがするほどです。ホーム・インターフェースやリモート・インターフェース、それにローカル・インターフェースとローカル・ホーム・インターフェースも可能性があり、さらに実装クラスもあります。こうしたクラスを全て同期させるのは王者の苦痛とも言えます。ところがメタデータによってこの問題が解決できるのです。良いツールであれば(ここでもXDocletを挙げるべきでしょう)こうした全ての依存性を管理でき、「コード・レベル」での関連はなくても「論理レベル」では関連があるようなクラスの同期を保証できるのです。これこそメタデータが本領を発揮するところです。




上に戻る


アノテーションの基礎
さて、メタデータがどういう場合に有効かは理解できたと思いますので、Tigerでのアノテーションを紹介することにしましょう。アノテーションは「at」記号(@)で始まり、アノテーション名が後に続きます。次に、(データが必要な時には)name=valueの対としてアノテーションにデータを与えます。こうした表記を使うと、その度にアノテーションを入れていることになります。一つのコードでアノテーションが10や50、あるいはそれ以上の場合もあり得ます。ただし、アノテーションの幾つかは、どれも同じアノテーション・タイプを使うことに気がつくでしょう。このアノテーション・タイプが実際に使われる構造体です。アノテーション自体は、ある特定なコンテキストにおける、そのタイプの特定な使い方になります(囲み記事アノテーションか、アノテーション・タイプかを見てください)

アノテーションか、アノテーション・タイプか
何がアノテーションで何がアノテーション・タイプかに混乱していますか? これを正しく理解するためには、皆さんが既に慣れているJava言語の概念で考えてみれば良いのです。一つのクラス(例えばPerson)を定義することができ、JVMでのそのクラスには(たちの悪いクラスパスの類をしていなければ)常に一つのバージョンしかありません。ところが、ある瞬間においては、そのクラスのインスタンスを10とか20は使っている、ということはあり得ます。Personクラスは相変わらず一つのままですが、様々な方法で何度も使われているのです。アノテーションとアノテーション・タイプについても同じことが言えます。アノテーション・タイプはクラスと似ており、アノテーションは、そのクラスのインスタンスと似ています。
アノテーションは3つの種類に分けることができます。
マーカー・アノテーション は変数を持ちません。アノテーションは名前で指定されて単純に現れ、追加的なデータは何もつきません。例えば@MarkerAnnotationはマーカーアノテーションです。データは含まず、アノテーション名のみです。
単一値アノテーション はマーカーと似ていますが、一つのデータを持っています。持っているのが一つのデータだけなので、(この構文を受け付けるようにアノテーション・タイプが定義されているとすれば)次のようなショートカット構文を使うことができます。@SingleValueAnnotation("my data") @記号を除けば、これは通常のJavaメソッド・コールとよく似ています。
フル・アノテーション は複数のデータ・メンバーを持ちます。そのため次のような、より完全な構文を使用する必要があります(そしてアノテーションはもはや通常のJavaメソッドとは似ていません)。@FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")
一つ以上の値を渡す必要がある時には、デフォルト構文を通してアノテーションに値を与える他に、名前と値の対を使うこともできます。また、中括弧を使って、アノテーション変数に対して値の配列を与えることもできます。リスト1はアノテーションでの値の配列の例です。

リスト1. 配列化した値をアノテーションに使う

@TODOItems({ // Curly braces indicate an array of values is being supplied
@TODO(
severity=TODO.CRITICAL,
item="Add functionality to calculate the mean of the student's grades",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.IMPOTANT,
item="Print usage message to screen if no command-line flags specified",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.LOW,
item="Roll a new website page with this class's new features",
assignedTo="Jason Hunter"
)
})

リスト1の例は見た目よりは簡単です。TODOItemsアノテーション・タイプには、値をとる変数が一つあります。ここで与えられている値はかなり複雑ですが、実は単一値が配列であることを除けば、TODOItemsの使い方は単一値アノテーションのスタイルに一致します。この配列には3つのTODOアノテーションがあり、それぞれに複数の値があります。各アノテーション内で値を区切るにはカンマを使い、一つの配列内で値を区切る場合にもカンマを使います。簡単ですよね?
ここでちょっと先回りしましょう。TODOItemsとTODOはカスタム・アノテーションで、第2回の話題です。ただ私としては、たとえ複雑なアノテーションであっても(そしてリスト1は充分複雑ですが)、それほど気の遠くなるようなものではないことを皆さんに分かって欲しいのです。Java言語での標準アノテーション・タイプに関する限り、あまり複雑に入り組んだものを見るのは稀でしょう。次の3つのセクションで見る通り、Tigerでの基本的なアノテーション・タイプは非常に簡単に使えるのです。




上に戻る


Overrideアノテーション
Tigerに組み込まれている最初のアノテーション・タイプはOverrideです。Overrideは(クラスやパッケージ宣言、あるいは他の構造体などではなく)メソッドに対してのみ使用し、このアノテーションで注釈を付けられたメソッドが、スーパークラスにあるメソッドをオーバーライドすることを表します。リスト2は簡単な例です。

リスト2. Overrideアノテーションの実際

package com.oreilly.tiger.ch06;
public class OverrideTester {
public OverrideTester() { }
@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}
@Override
public int hashCode() {
return toString().hashCode();
}
}

リスト2は簡単に分かると思います。@OverrideアノテーションがtoString()とhashCode()という2つのメソッドに注釈を付け、OverrideTesterクラスのスーパークラス(java.lang.Object)にある方のメソッドをオーバーライドすることを示しています。これは大したことではないと思えるかも知れませんが、実は便利な機能なのです。こうしたメソッドをオーバーライドしない限り、このクラスをコンパイルすることは本当にできないのです。またこのアノテーションによって、toString()をもてあそんでいる時でも、hashCode()の方は一致していることを確認するように知らせるものが、何かしらあることにもなります。
このアノテーション・タイプが本当に役に立つのは、コーディングが遅れたのに慌てて、何かを打ち間違えたような時です(リスト3)。

リスト3. Overrideアノテーションに打ち間違いを捉えさせる

package com.oreilly.tiger.ch06;
public class OverrideTester {
public OverrideTester() { }
@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}
@Override
public int hasCode() {
return toString().hashCode();
}
}

リスト3で、hashCode()はhasCode()と打ち間違いされています。アノテーションによればhasCode()はメソッドをオーバーライドすべきだと言っています。しかしjavacはコンパイルする時に、スーパークラス(ここでもjava.lang.Objectです)にはオーバーライドすべきメソッドとしてhasCode()という名前のメソッドが無いことに気がつきます。その結果、コンパイラーは図1に示すようなエラーを出します。

図1. Overrideアノテーションによるコンパイラー警告



欠けている機能
Deprecatedが、エラーのタイプを示すメッセージを単一値アノテーションの形式で含められるようになっていれば良かったのにと思います。そうすれば、ユーザーが使用すべきでないメソッド(deprecated method)を使った時にはコンパイラーがメッセージを出力することができます。このメッセージによって、そのメソッドを使うとどの程度深刻な結果を引き起こすか、そのメソッドを使えなくなるのはいつか、さらにはどんな代替手段があり得るか、などを示すことができます。残念なことに現在言えることは、次のJ2SEバージョン(「Mustang」と呼ばれています)でこれが実現するかも知れない、という程度です。
この便利な機能を使えば、打ち間違いをすぐに見つけることができます。
Deprecatedアノテーション
次の標準アノテーション・タイプはDeprecatedです。Overrideと同じようにDeprecatedはマーカー・アノテーションです。その名前から想像できる通り、Deprecatedは、もはや使用すべきでないメソッドに注釈付けするために使います。Overrideとは異なり、Deprecatedは使用すべきでないメソッドと同じ行に置く必要があります(なぜかって? 私にも分かりません)。リスト4がこの例です。


リスト4. Deprecatedアノテーションを使う

package com.oreilly.tiger.ch06;
public class DeprecatedClass {
@Deprecated public void doSomething() {
// some code
}
public void doSomethingElse() {
// This method presumably does what doSomething() does, but better
}
}

このクラス自体はコンパイルしても何も特別なことは起こりません。ただしコンパイルした後で、使用すべきでないメソッドをオーバーライドしたり、呼び出したりして使おうとすると、コンパイラーは(アノテーションを処理することによって)そのメソッドを使うべきではないことに気がつき、図2のようなエラー・メッセージを出します。

図2. Deprecatedアノテーションによるコンパイラー警告

Javaコンパイラーに対して通常のdeprecation警告を受けたいと指示するためにも、コンパイラー警告をオンにする必要があることには注意してください。それにはjavacコマンドにある2つのフラグ、-deprecatedと新しい-Xlint:deprecatedフラグのどちらかを使うことができます。




上に戻る


SuppressWarningsアノテーション
Tigerで「無料で」手に入る最後のアノテーション・タイプはSuppressWarningsです。これが何をするのかは難なく分かると思いますが、なぜこのアノテーション・タイプがそれほど重要なのかはそれほど明白ではありません。実はこれはTigerで一新された機能の副産物なのです。例えばgenericsを考えてみてください。genericsによって、特にJavaのコレクションに関して言うと、あらゆる種類のタイプセーフ操作ができるようになりました。ところが今度はgenericsのために、コレクションがタイプセーフ(type-safe)無しに使われると、コンパイラーは警告を投げるようになったのです。これはTiger用に書かれたコードには便利なのですが、Java 1.4.xやそれ以前のバージョン用に書かれたコードに対しては実にやっかいです。全く気にもかけないものに対して、頻繁に警告を受けるようになってしまうのです。コンパイラーを静かにさせるにはどうしたら良いでしょう?
そういう時にSupressWarningsが役に立つのです。SupressWarningsはOverrideやDeprecatedとは異なり、変数を持っています。ですからDeprecatedには単一アノテーション・スタイルを使い、それぞれの値が抑制すべき特定な警告タイプを示すような、値の配列を変数として与えるのです。リスト5に示す例を見てください。このコードは、通常であればTigerで警告を引き起こします。

リスト5. タイプセーフでないTigerコード

public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List
wordList.add("foo"); // causes error on list addition
}

図3はリスト5のコードをコンパイルした結果を示します。

図3. non-typed コードによるコンパイラー警告

リスト6はSuppressWarningsアノテーションによって、このうるさい警告を除去します。

リスト6. 警告を抑える

@SuppressWarings(value={"unchecked"})
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List
wordList.add("foo"); // causes error on list addition
}

簡単ですよね? ただ単に警告のタイプ(図3で「unchecked」として表示されているものです)を探してきてSuppressWarningsに渡すだけです。
SuppressWarningsでの変数の値には配列が使えるため、同じアノテーションで複数の警告を抑えることができます。例えば、@SuppressWarnings(value={"unchecked", "fallthrough"})は2つの値を持った配列を受け付けます。この機能によって、過度に冗長にならずに、柔軟にエラーを処理できるようになります。




上に戻る


まとめ
ここでご紹介した構文は新しく見えるかも知れませんが、アノテーションは簡単に理解でき、また使うのも簡単なことが分かるでしょう。とは言ってもTigerについてくる標準アノテーションはやや素っ気なさすぎ、色々注文をつけたくなります。メタデータは大分便利になっているので、皆さんはきっと自分のアプリケーションに最適なアノテーション・タイプを思いつくでしょう。このシリーズ2回目の次回では、Tigerでサポートされている、自分独自のアノテーション・タイプが書ける機能の詳細を説明する予定です。説明の内容は、どのようにJavaクラスを作ってそれをアノテーション・タイプとして定義するか、どのようにしてコンパイラーにそのアノテーション・タイプを認識させるか、どのように使ってコードに注釈をつけるか、などです。さらに、ちょっと変に聞こえるかも知れませんが、でも便利な、アノテーションのアノテーションについても触れる予定です。皆さんもこの、Tigerでの新しい構造体をすぐにマスターできるでしょう。


参考文献
このシリーズの2回目、「 Annotations in Tiger, Part 2」 (US)も忘れずに読んでください。この記事ではカスタムのアノテーションを説明しています。

オープンソースのコード生成エンジンXDocletを使うと、Java言語で属性指向のプログラミングができるようになります。

JSR 175はJava言語にメタデータ機能を採り入れるための仕様です。現在はJava Community Processで最終ドラフト提案の段階にあります。

Sunのホーム・ベースであるall things J2SE 5.0を見てください。

Tigerをダウンロードして、自分で試してみてください。

John ZukowskiによるTigerを使いこなす  シリーズは、Java 5.0の新しい機能を実際的なヒントを中心に説明しています。

Brett McLaughlinとDavid Flanagan共著によるJava 1.5 Tiger: A Developer's Notebook(2004年O'Reilly & Associates刊)はアノテーションを含めたTigerの最新機能のほぼ全てを、コード中心で開発者に分かりやすく網羅しています。

developerWorksのJava technologyゾーンにはJava技術に関する資料が豊富に取り揃えられています。

Developer BookstoreにはJava関連の書籍をはじめ、広範囲な話題を網羅した技術書が豊富に取り揃えられています。

0 件のコメント:

マイブログ リスト


Jang ki hote

自己紹介