JSR 308の概要

本連載第15回で、配列やメソッドレシーバ、ジェネリックの型引数などに対してアノテーションを適用できるようにするための「JSR 308: Annotations on Java Types」を紹介した。そのJSR 308だが、11月9日よりJCPのサイトにおいてEary Draftの公開が開始されている。これによって新しいアノテーション利用方法の詳細が明らかになってきた。

まず、前回も書いたようにJSR 308の主題は"型"に対するアノテーションの適用を可能にすることであり、同時にローカル変数や配列、文に対する適用も検討されている。一方で式やブロック、ループ、部分構造などに対する適用は考えられていない。

Eary DraftではJSR 308によるアノテーションの適用例として、リスト1に示すものが挙げられている。大半は前回紹介したものと同じだが、新たにGenaricであるメソッドレシーバの型パラメータや静的メンバアクセスに対する適用が追加されている。

リスト1 JSR 308によるアノテーションの適用例

// ジェネリックにおける型引数
Map> files;

// ジェネリックにおけるメソッドやコンストラクタ呼び出しの引数
o.m("...");

// 型引数の制約やワイルドカード
class Folder { ... }
Collection super @Existing File>

// クラスの継承
class UnmodifiableList implements @Readonly List { ... }

// 例外指定
void monitorTemperature() throws @Critical TemperatureException { ... }

// 型キャスト
myString = (@NonNull String) myObject;

// instanceofによる型チェック
boolean isNonNull = myString instanceof @NonNull String;

// オブジェクト生成
new @NonEmpty @Readonly List(myNonEmptyStringSet)
new  @Interned MyObject()

// メソッドレシーバ
public int size() @Readonly { ... }
public void write() @Writable throws IOException { ... }
// メソッドレシーバがGenaricである場合、そのパラメータにも指定できる [New]
public int size() @Readonly { ... }
public void requiresNonNullKeys()  { ... }

// クラスリテラル
Class c = @NonNull String.class;

// 静的メンバへのアクセス [New]
@NonNull Type.field

// 配列
Document[@Readonly][] docs4 = new Document[@Readonly 2][12];
Document[][@Readonly] docs5 = new Document[2][@Readonly 12];
※ ここで使われている@NonNullや@NonEmptyなどのアノテーションは架空のもので、実際にこのようなアノテーションがJava SEに用意されるということではない。

Eary Draftで上記適用例のいくつかに関する詳細な説明が掲載されているので、以下で簡単に紹介したい。

型に対するアノテーションの適用例1 - メソッドレシーバに対するアノテーション

メソッドレシーバは通常暗黙のパラーメータだが、そのレシーバにアノテーションを指定することで明示的な制限などを加えることができるようになる。たとえばリスト2のように宣言されていた場合、size()メソッドの呼び出しがレシーバに変更を加えないことを示す。

リスト2

int size() @Readonly { ... }

メソッドレシーバに対するアノテーションは、メソッドそのものやメソッドの戻り値に対するアノテーションとは異なるので注意が必要だ。たとえばメソッドの戻り値を変更しないように示唆したい場合は、戻り値に対して@Readonlyを指定するような形になる。

型に対するアノテーションの適用例2 - 型キャストに対するアノテーション

Eary Draftには、キャストする型に対してアノテーションを許可する理由が2つ挙げられている。ひとつは、キャスト対象のオブジェクトの型にアノテーションが指定されていた場合、キャスト後の型も同じアノテーションを含む必要があるという理由である。キャスト時にアノテーションが外れてしまったのでは意味がないからだ。

たとえばリスト3のように@NonNullを指定された型のオブジェクトは、@NonNullの型にしかキャストすることはできない。

リスト3

@NonNull Object myObject;
@NonNull String myString = (@NonNull String) myObject;
String myString2 = (String) myObject;   // これはアノテーションの処理時にエラーになる

もうひとつの理由は、コンパイラに型に関する付加的な情報を渡したい場合に利用できるというもの。たとえばリスト4の場合、キャストすることによってオブジェクトxにnullを許容しないという制限を加えている。

リスト4

final Object x = new Object();
@NonNull myObject = (@NonNull Object) x;

型に対するアノテーションの適用例3 - 型チェックに対するアノテーション

instanceof演算子でチェックする型に対してもアノテーションを指定できるようになる。このとき、アノテーションと型が両方一致しなければtrueにはならない。たとえば、リスト5のように"@NonNull Object"な型のオブジェクトは、型が一致したとしても@NonNullでなければtrueにはならない。

リスト5

@NonNull Object myObject = new String("hoge");
... myObject instanceof @NonNull String  // true
... myObject instanceof String            // false

逆も同様で、アノテーションを指定されていない型のオブジェクトは、何らかのアノテーションが指定された型とは一致しない。

リスト6

Object myObject = new String("hoge");
... myObject instanceof @NonNull String  // false
... myObject instanceof String           // true

型に対するアノテーションの適用例4 - オブジェクト生成に対するアノテーション

オブジェクトの生成に対してアノテーションを指定すると、生成されるオブジェクトに対して制約を加えることが可能になる。たとえばリスト7のようにした場合、生成されるListオブジェクトは読み込み専用で、かつ空であってはならないものとなる。

リスト7

List myList = new @NonEmpty @Readonly List(myNonEmptyStringSet);

型に対するアノテーションの適用例5 - 型引数の制約やワイルドカードに対するアノテーション

ジェネリクスを利用する場合、型引数には制約やワイルドカードを指定できる。JSR 208では、この制約やワイルドカードのための型に対してアノテーションを付けることができるようになる。Draftには詳しくは書かれていないが、たとえばリスト8のようなクラス宣言の場合、型Fの値はは存在するファイルへの参照を持つFile(の派生クラスの)インスタンスでなければならないということのようだ。

リスト8

class Folder { 
      ...... 
}

型に対するアノテーションの適用例6 - スーパークラスに対するアノテーション

クラスの継承やインタフェースの実装の際に、スーパークラスに対してアノテーションを指定することで、サブクラス自身にもそのアノテーションが適用される。たとえばリスト9ではスーパークラスが読み込み専用のListになっているため、UnmodifiableListも読み込み専用となる。

リスト9

class UnmodifiableList implements @Readonly List {
      ......
}

型に対するアノテーションの適用例7 - 配列に対するアノテーション

JSR 308では配列に対してアノテーションを指定することも可能になる予定だが、そのための構文についてはまだ詳細が定まっていない。Eary Draftではいくつかの候補が提案されている。

基本的な議論としては、まず括弧([])に対するアノテーションが配列自身を対象とするのか(ARRAY)、それとも配列の要素を対象とするのか(ELTS)という点がある。それから、アノテーションがどこに表れるのかといった問題もある。選択肢としては括弧の中(IN)なのか前(PRE)なのか、あるいは後ろ(POST)なのかだ。

たとえばリスト10のような宣言の場合、ARRAY-INという組み合わせならば@Readonlyは配列自身にかかるので、「要素にnullを許容しない読み込み専用の配列」ということになる。ELTS-INならば逆に@Readonlyは要素にかかるので、「読み込み専用の要素を持つnullでない配列」ということになる。

リスト10

@NonNull Document[@Readonly]

いずれにせよ矛盾がなく、かつ理解しやすい構文にしなければならないが、もともとの配列の構文があまり厳密と言えるものではないため、詳細が決定するまでにはまだ時間が必要なようだ。

本連載の第15回ではJSR 308対応コンパイラの開発版を使ってみた。現在は使用できるアノテーションが増えている他、ビルド方法も若干変更されているので、次回はその辺りを紹介したいと思う。