web-dev-qa-db-ja.com

なぜこれはJava 8ストリームの例をコンパイルしないのですか?

このコードがJDKでコンパイルされない理由を理解しようとしています_1.8.0_45_:

_public class Example<E extends Example<E>> {
    public List<? extends Example<?>> toExamples(Collection<String> collection) {
        return collection.stream()
                .map(v -> lookup(v))
                .collect(Collectors.toList());
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}
_

一見不要なキャストを追加すると、修正されます。

_public class Example<E extends Example<E>> {
    public List<? extends Example<?>> toExamples(Collection<String> collection) {
        return collection.stream()
                .map(v -> (Example<?>) lookup(v))
                .collect(Collectors.toList());
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}
_

コンパイラからのエラーは次のとおりです。

_Example.Java:9: error: incompatible types: inference variable R has incompatible bounds
              .collect(Collectors.toList());
                      ^
  equality constraints: List<Object>
  upper bounds: List<? extends Example<?>>,Object
where R,A,T are type-variables:
  R extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
  A extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
  T extends Object declared in interface Stream
_

何らかの理由で、lookup()の戻り値の型がExampleを拡張するものに正しく推測されていません。

10
Stijn Van Bael

Peter Lawreyが指摘 のように、_? extends Example<?>_は_E extends Example<E>_と互換性がありません。それでも、署名を修正しても、ここでは型推論は機能しません。

その理由は、連鎖メソッド呼び出しを介して逆伝播しないため、型推論の既知の制限です。言い換えると、戻り値の型では、collect(…)呼び出しの型を推測できますが、先行するmap(…)呼び出しの型は推測できません。 (参照 この回答

ただし、nestedメソッドの呼び出しでは機能するため、次の書き直されたメソッドをコンパイルできます。

_public class Example<E extends Example<E>> {
    public <E extends Example<E>> List<E> toExamples(Collection<String> collection) {
        return collection.stream()
            .collect(Collectors.mapping(v -> lookup(v), Collectors.toList()));
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}
_

それでも、コードのセマンティクスを再考する必要があります。戻り値の型にのみ表示されるメソッドの型パラメーターは、「呼び出し元がこの型パラメーターの代わりに使用するものは何でも、メソッドは正しいものを返すことを意味するため、正しくありません」。メソッドの実装は呼び出し元が何を想定しているのかわからないため、これは不可能です。 nullまたは空のリストを返すだけで正しく機能しますが、これはほとんど役に立ちません。

10
Holger

?がある場合、それは別の?と等しくありません。つまり、コンパイラは認識しません。

? extends Example<?>

の試合として

E extends Example<E>

2つの?が同じであると想定できないためです。かもしれない

A extends Example<B>

キャストを実行するときは、制約が一致するように制約を覆い隠します。

8
Peter Lawrey

私の推測では、静的メソッドで定義されたジェネリック型は、クラスで定義されたジェネリック型と同じではありません。 lookupメソッドを非静的にして、クラスレベルのジェネリック宣言で定義されているのと同じ型に一致させることができるはずです。

    public E lookup(String value) {
        return null;
    }
1
John Vint