mike-neckのブログ

Java or Groovy or Swift or Golang

FunctionalInterfaceの型推論で僕がはまった1つくらいのこと

これは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に変換できません:)

ちなみに、これはジェネリクス職人のなぎせさんによると…

ということなので、なんだか、もう…(´・ω・`)