java.util.concurrent.FutureTaskのコンストラクタの定義が
public FutureTask(Callable
http://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/FutureTask.html#FutureTask-java.util.concurrent.Callable-callable)
とCallable<V>となっていてCallable<? extends V>じゃないのが不便だという話題。
これにより Thread Safe な汎用オブジェクトCache - がくぞーのメモ のキャッシュ機構のコンストラクタをCallable<? extends V>に出来ないのだとがくぞさん(@gakuzzzz)が嘆いていたので、小手先のテクニックで対応したのが以下のコード。
import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicReference; public class Cache<T> { private Callable<? extends T> originalFactory; private AtomicReference<Future<? extends T>> cache = new AtomicReference<Future<? extends T>>(); public Cache(Callable<? extends T> originalFactory) { this.originalFactory = originalFactory; } public T get() { try { return getFuture().get(); } catch (ExecutionException e) { // ここの処理は現場のポリシーによって柔軟に throw new RuntimeException(e.getCause()); } catch (InterruptedException e) { throw new CancellationException(e.getMessage()); } } private Future<? extends T> getFuture() { Future<? extends T> result = cache.get(); if (result != null) return result; FutureTask<? extends T> task = getFutureTask(originalFactory); if (cache.compareAndSet(null, task)) { task.run(); } return cache.get(); } private <X> FutureTask<X> getFutureTask(Callable<X> callable) { return new FutureTask<X>(callable); } }
問題の解説
問題の中心になるのは
public FutureTask(Callable
http://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/FutureTask.html#FutureTask-java.util.concurrent.Callable-callable)
というコンストラクタに対して、
Callable<HogeEx> call = ()->new HogeEx(); new FutureTask<Hoge>(call);
という呼び出しが出来ないという点。
Callable<HogeEx>をCallable<Hoge>にキャストすることは出来ない。
このキャストについてはJavaジェネリクス再入門 - プログラマーの脳みそあたりを参考にされたし。ジェネリクスの代入互換性は通常の型の代入互換性とは異なる。
そして
Callable<? extends Hoge> call = ()->new Hoge(); new FutureTask<? extends Hoge>(call);
というようにコンストラクタで? extends Hogeをバインドすることもできない。
もし、Javaの標準APIのFutureTaskのコンストラクタが
public FutureTask(Callable<? extends V> callable)
という形状であれば
Callable<HogeEx> call = ()->new HogeEx(); new FutureTask<Hoge>(call);
とすることが可能になる。が、Javaの標準APIのシグネチャ(ようするにメソッドの型)なんて互換性の関連もあってそうそう変わるものでもない。
対応策
new FutureTask<? extends Hoge>を実現できれば良いのに、ということで? extends Hogeをロンダリングしてメソッドスコープの型変数にすることで回避。
private <X> FutureTask<X> getFutureTask(Callable<X> callable) { return new FutureTask<X>(callable); }
このgetFutureTaskを呼び出す側だが、メソッドスコープの型変数へのバインドは型が推論できるので
FutureTask<? extends T> task = getFutureTask(originalFactory);
と書くことで呼び出すことが出来る。
逆に推論をさせずに明示的にcaptuer ? extends Tをバインドしようとするとコンパイルエラーになる。
FutureTask<? extends T> task = this.<? extends T>getFutureTask(originalFactory); // NG
ここまで書いてもうひとつ型推論が行われるダイヤモンド演算子を使えばいいことに気づく。
FutureTask<? extends T> task = new FutureTask<>(originalFactory); // OK