ジェネリクスの型宣言付きの型の可変長引数って何がダメなんだっけ?

ジェネリクスの宣言付きの可変長引数を受け取るメソッドを定義したときにlintに怒られて「あれ?これってダメなんだっけ?」ってなったのでメモ。

やりたいこととしては、ORMを作っていてupdate文を作るときにvoid update(UpdateValuePair<E, ?>... pairs)というメソッドを作りたい。
呼び出し側はUsers.all().update(Users.NAME.set("hoge"), Users.ACTIVE.set(true))のように書けるのが理想。

危険な操作をしてみる

これを書いた時「Possible heap pollution from parameterized vararg type」という警告が出てくる。どのように危険な操作ができるのかサンプルを書きました。

public class Main {
    public static void main(String[] args) {
        hoge(Arrays.asList("aaa"), Arrays.asList("bbb"));
    }

    public static void hoge(List<String>... args) {
        Object[] objArray = args;
        // List<Integer>を入れる
        objArray[0] = Arrays.asList(1);

        // List<String>として取り出せてしまう
        List<String> stringList = args[0];
        // 実はIntegerが入っているのでClassCastExceptionが出る
        String str = stringList.get(0);
    }
}

Object[]を経由してargsの最初の要素にListを入れた後args[0]を取り出すとListとして見えてしまうので、getしたときにClassCastExceptionが出てしまう。詳しい解説は非具象化可能仮パラメータを可変長引数メソッドに使用する場合のコンパイラの警告とエラーの改善というドキュメントを見ると良さそう。

対策

取れる対策は2つで

  1. 可変長引数を使わず専用の型を用意する
  2. SafeVarargsを使って警告を抑制する

理想としては可変長引数をやめるのが一番安全。ただ、今回はUpdate文を作るときに可変長引数で更新する値を受け取りたいので、更新するたびに新しいインスタンスを作って組み立てて…とすると使いづらいのでSafeVarargsを使うことにした。

SafeVarargsで警告を抑制する

方針としては呼び出し側は問題なくて実装側が気をつければ良さそう(気を付けるだけはもにょるけど…)なので警告を抑制する。
警告を抑制するにはfinal or staticメソッドにして@SafeVarargsアノテーションをつければ良い。

最終的にはMocoのコードは以下のようになりました。

    @SafeVarargs
    public final void update(UpdateValuePair<E, ?>... pairs) {
        createUpdate(pairs).executeQuery(ConnectionManager.getConnection(), ConnectionManager.createSqlVisitor());
    }

github.com