CLOVER🍀

That was when it all began.

可変長引数を持つメソッドに付与する、@SafeVarargsアノテーション

可変長引数を持つメソッドをコンパイルした際の警告を抑制する、@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
では、この警告を抑制するには

だそうです。@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個

後者はコンパイルエラーになります。

その他、@SafeVarargsアノテーションを使うとJavadocにもそれが反映されるみたいですよ。