ジェネリクスの型宣言付きの型の可変長引数って何がダメなんだっけ?
ジェネリクスの宣言付きの可変長引数を受け取るメソッドを定義したときにlintに怒られて「あれ?これってダメなんだっけ?」ってなったのでメモ。
やりたいこととしては、ORMを作っていてupdate文を作るときにvoid update(UpdateValuePair<E, ?>... pairs)
というメソッドを作りたい。
呼び出し側はUsers.all().update(Users.NAME.set("hoge"), Users.ACTIVE.set(true))
のように書けるのが理想。
呼び出し側はこういうのやりたいお気持ち pic.twitter.com/AjncV2VT1Z
— 俺九番 (@orekyuu) 2018年5月19日
危険な操作をしてみる
これを書いた時「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
対策
取れる対策は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()); }