可変長引数を持つメソッドをコンパイルした際の警告を抑制する、@SafeVarargsアノテーションというものがあるということを、前のJDK 7の言語仕様変更のエントリを書いている時に知りました。
警告されたものは、場合によっては@SuppressWarnings付けるくらいだと思っていたのですが、そんなのできたんですね。
@SafeVarargsアノテーションの宣言は、以下の通り。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface SafeVarargs {}
コンストラクタ、またはメソッドに付与することができるアノテーションみたいですね。
ここで、こんなソースを用意して
import java.util.Arrays; import java.util.List; public class SafeVarargsTest { public static void main(String[] args) { for (String s : toListStatic("foo", "bar")) { System.out.println(s); } for (String s : new SafeVarargsTest().toList("foo", "bar")) { System.out.println(s); } } public static <T> List<T> toListStatic(T... elms) { return Arrays.asList(elms); } public <T> List<T> toList(T... elms) { return Arrays.asList(elms); } }
「-Xlint:unchecked」付きでコンパイルすると
:15: 警告:[unchecked] パラメータ化された可変引数型Tからのヒープ汚染の可能性があります public static <T> List<T> toListStatic(T... elms) { ^ Tが型変数の場合: メソッド <T>toListStatic(T...)で宣言されているT extends Object 19: 警告:[unchecked] パラメータ化された可変引数型Tからのヒープ汚染の可能性があります public <T> List<T> toList(T... elms) { ^ Tが型変数の場合: メソッド <T>toList(T...)で宣言されているT extends Object 警告2個
「ヒープ汚染の可能性があります」なんて、おっそろしい警告を言われます。
ここで言う「ヒープ汚染」というのは、ジェネリクスを使って型安全を保証しているように見せかけてClassCastExceptionが発生するようなコードを入れてしまうことです。
@SafeVarargsアノテーションのAPIドキュメントに、その例が載っています。
@SafeVarargs // Not actually safe! static void m(List<String>... stringLists) { Object[] array = stringLists; List<Integer> tmpList = Arrays.asList(42); array[0] = tmpList; // Semantically invalid, but compiles without warnings String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime! }
配列は共変なので、それを利用して型安全性を破壊している例ですね。
んで、可変長引数を使っても問題ない…というか、この警告を抑制する手段のひとつとしてJDK 7で追加されたのが@SafeVarargsアノテーションだそうな。
使い方。
@SafeVarargs public static <T> List<T> toListStatic(T... elms) { return Arrays.asList(elms); }
普通に付けるだけです。これで警告がなくなります。
ただ、上記はstaticメソッドの例ですが、インスタンスメソッドに普通に付与してしまうと
@SafeVarargs public <T> List<T> toList(T... elms) { return Arrays.asList(elms); }
コンパイルエラーになります。
21: エラー: SafeVarargs注釈が無効です。インスタンス・メソッド<T>toList(T...)はfinalではありません。 public <T> List<T> toList(T... elms) { ^ Tが型変数の場合: メソッド <T>toList(T...)で宣言されているT extends Object エラー1個
なんか、インスタンスメソッドはfinalで宣言されていなくてはならないようですね…。
というわけで、finalを付けてあげるとちゃんとコンパイルを通るようになります。
@SafeVarargs public final <T> List<T> toList(T... elms) { return Arrays.asList(elms); }
ちなみに、finalにしなくても、またJDK 6では
@SuppressWarnings("unchecked") public static <T> List<T> toListStatic(T... elms) { return Arrays.asList(elms); } @SuppressWarnings("unchecked") public <T> List<T> toList(T... elms) { return Arrays.asList(elms); }
実は@SuppressWarnings("unchecked")で事足りたりします。
Oracleのドキュメント
http://docs.oracle.com/javase/7/docs/technotes/guides/language/non-reifiable-varargs.html#suppressing
では、この警告を抑制するには
- @SafeVarargsアノテーションを使用する
- @SuppressWarnings({"unchecked", "varargs"})を使用する
- コンパイルオプション「-Xlint:varargs」を使用する
だそうです。@SuppressWarningsアノテーションでは、メソッド呼び出し側の警告は抑制できないんだそうな。
また、@SafeVarargsアノテーションを使用すると、コンパイル時のチェックも入り
@SuppressWarnings({"unchecked", "varargs"}) public static <T> List<T> toListStaticNoVarargs1(T elm) { return Arrays.asList(elm); } @SafeVarargs public static <T> List<T> toListStaticNoVarargs2(T elm) { return Arrays.asList(elm); }
と、実際には可変長引数でないメソッドにアノテーションを付与すると
31: エラー: SafeVarargs注釈が無効です。メソッド<T>toListStaticNoVarargs2(T)は可変引数メソッドではありません。 public static <T> List<T> toListStaticNoVarargs2(T elm) { ^ Tが型変数の場合: メソッド <T>toListStaticNoVarargs2(T)で宣言されているT extends Object エラー1個
後者はコンパイルエラーになります。