これはJava Advent Calendarの2日目の記事です。
昨日(2014/12/1)は久保田さんのCMS GC おさらいでした。 明日(2014/12/3)は太一さんのラムダ式を駆使してSSR対応のフレームワークを作ってみたです。
Javaでラムダが使えるようになると、人間欲が出てくるもので、つい、Scalaのmatchみたいなものを書きたくなってしまいます。
そうするとAPI的に同じ名前のメソッドでFunction<? super T, ? extends R>
とConsumer<? super T>
を受け取れるようなメソッドを書いてしまいます。
(なお、ここでは、すこし単純にOptional
みたいなクラスを考えます。)
public class Hoge<T> { public Hoge<R> foo(Function<? super T, ? extends R> function) { Objects.requireNonNull(function); return new Hoge<R>(function.apply(t)); } public void foo(Consumer<? super T> consumer) { Objects.requireNonNull(consumer); consumer.accept(t); } private final T t; public Hoge(T t) { this.t = Objects.requireNonNull(t); } public static void main(String... args) { Hoge<String> hoge = new Hoge("hoge"); System.out.println(hoge.foo(s -> s.length())); } }
ところが上記のようにHoge
クラスを作るとコンパイルに失敗します。
実際にコンパイルしてみましょう。
Hoge.java:20: エラー: fooの参照はあいまいです System.out.println(hoge.foo(s -> s.length())); ^ Hogeのメソッド <R>foo(Function<? super T,? extends R>)とHogeのメソッド foo(Consumer<? super T>)の両方が一致します
はい、ダメダメですね。
結論
Scalaのような感じを目指すなら…
- もう少し考えてからクラスの設計をする
- Scala使う
のがよいですかね…(´・ω・`)
補足
なお、さっきのHoge
クラスからConsumer<? super T>
を取るメソッドを取り払うと、こんどはもっと面倒な型推論上の問題にぶちあたります。
修正後
public class Hoge<T> { public Hoge<R> foo(Function<? super T, ? extends R> function) { Objects.requireNonNull(function); return new Hoge<R>(function.apply(t)); } private final T t; public Hoge(T t) { this.t = Objects.requireNonNull(t); } public static void main(String... args) { Hoge<String> hoge = new Hoge("hoge"); hoge.foo(System.out::println); } }
上記の変更に対して、副作用で終わるFunction<? super T, void>
を渡すと…
Hoge.java:15: エラー: 不適合な型: 型変数Rを推論できません System.out.println(hoge.foo(System.out::println)); ^ (引数の不一致: メソッド参照の戻り型が不正です voidをRに変換できません:)
@mike_neck それは基本的にはどうにもなりませんね!Javaのジェネリクスの限界です!
— なぎせ ゆうき (@nagise) 2014, 10月 21
ということなので、なんだか、もう…(´・ω・`)