web-dev-qa-db-ja.com

Java型推論:参照はJava 8ではあいまいですが、Java 7

2つのクラスがあるとしましょう。空のクラスBase、およびこのクラスのサブクラスDerived

public class Base {}

public class Derived extends Base {}

次に、別のクラスにいくつかのメソッドがあります。

import Java.util.Collection

public class Consumer {

    public void test() {
        set(new Derived(), new Consumer().get());
    }

    public <T extends Base> T get() {
        return (T) new Derived();
    }

    public void set(Base i, Derived b) {
        System.out.println("base");
    }

    public void set(Derived d, Collection<? extends Consumer> o) {
        System.out.println("object");
    }

}

これは、Java 7で正常にコンパイルおよび実行されますが、Java 8ではコンパイルされません。エラー:

Error:(8, 9) Java: reference to set is ambiguous
  both method set(Base,Derived) in Consumer and 
  method set(Derived,Java.util.Collection) in Consumer match

なぜJava 7で動作するのに、Java 8では動作しないのですか?どうすれば<T extends Base>evermatch Collection?

26
Rohit Kumar

問題は、型推論が改善されたになっていることです。あなたは次のような方法を持っています

_public <T extends Base> T get() {
    return (T) new Derived();
}
_

これは基本的に、「呼び出し元はBaseのどのサブクラスを返すかを決定できます」と言っていますが、これは明らかにナンセンスです。すべてのコンパイラは、ここで型キャスト_(T)_に関するチェックされていない警告を表示する必要があります。

これで、メソッド呼び出しができました。

_set(new Derived(), new Consumer().get());
_

メソッドConsumer.get()が「呼び出し元は私が返すものを決定できる」と言っていることを思い出してください。したがって、Baseを拡張し、同時にCollectionを実装する型が存在する可能性があると想定するのは完全に正しいです。したがって、コンパイラは「set(Base i, Derived b)set(Derived d, Collection<? extends Consumer> o)のどちらを呼び出すかわからない」と言います。

set(new Derived(), new Consumer().<Derived>get());を呼び出すことで「修正」できますが、メソッドの狂気を説明するために、次のように変更することもできます。

_public <X extends Base&Collection<Consumer>> void test() {
    set(new Derived(), new Consumer().<X>get());
}
_

これで、コンパイラの警告なしにset(Derived d, Collection<? extends Consumer> o)が呼び出されます。実際の安全でない操作は、getメソッド内で発生しました。

したがって、正しい修正は、getメソッドからtypeパラメーターを削除し、それが何を返すかを宣言することです本当にDerived


ちなみに、私を苛立たせているのは、このコードがJava 7でコンパイルできるというあなたの主張です。ネストされたメソッド呼び出しによる型推論が制限されているため、ネストされた呼び出しコンテキストでgetメソッドをBaseDerivedを期待するメソッドに渡すことはできません。結果として、準拠するJava 7コンパイラを使用してこのコードをコンパイルしようとすると失敗しますが、理由は異なります。

23
Holger

そうですね、Java7が喜んで実行できるわけではありません。エラーが発生する前に、いくつかの警告が表示されます。

_jatin@jatin-~$ javac -Xlint:unchecked -source 1.7 com/company/Main.Java 
warning: [options] bootstrap class path not set in conjunction with -source 1.7
com/company/Main.Java:19: error: no suitable method found for set(Derived,Base)
        set(new Derived(), new Consumer().get());
        ^
    method Consumer.set(Base,Derived) is not applicable
      (argument mismatch; Base cannot be converted to Derived)
    method Consumer.set(Derived,Collection<? extends Consumer>) is not applicable
      (argument mismatch; Base cannot be converted to Collection<? extends Consumer>)

com/company/Main.Java:28: warning: [unchecked] unchecked cast
        return (T) new Derived();
                   ^
  required: T
  found:    Derived
  where T is a type-variable:
    T extends Base declared in method <T>get()
_

問題はこれです:

_set(new Derived(), new Consumer().get());
_

new Consumer().get()を実行するとき、getの署名を見ると。タイプTを返します。 TがどういうわけかBaseを拡張することはわかっています。しかし、Tが具体的に何であるかはわかりません。何でもかまいません。では、Tが何であるかを具体的に決定できない場合、コンパイラーはどのようにできるでしょうか。

コンパイラーに伝える1つの方法は、ハードコーディングして、set(new Derived(), new Consumer().<Derived>get());で具体的に伝えることです。

上記の理由は非常に(意図的に繰り返される)危険です。これを実行しようとすると、次のようになります。

_class NewDerived extends Base {
     public String getName(){return "name";};
}
NewDerived d = new Consumer().<NewDerived>get();
System.out.println(d.getName());
_

Java7(または任意のJavaバージョン)では、実行時に例外がスローされます。

スレッド「メイン」の例外Java.lang.ClassCastException:com.company.Derivedをcom.company.NewDerivedにキャストできません

getはタイプDerivedのオブジェクトを返しますが、コンパイラーにそれがNewDerivedであると述べたためです。そして、DerivedをNewDerivedに正しく変換することはできません。これが警告を表示する理由です。


エラーのとおり、new Consumer().get()の何が問題になっているのかがわかりました。タイプは_something that extends base_です。 set(new Derived(), new Consumer().get());を実行すると、引数をDerived (or any super class of it), something that extends Baseとして受け取るメソッドが検索されます。

これで、両方のメソッドが最初の引数の対象になります。 2番目の引数_something that extends base_のように、何かを派生または拡張することができるので、両方とも適格です。これが、Java8がエラーをスローする理由です。

Java7によると、その型推論は少し弱いです。だからそれは似たようなことをしようとします、

_Base base = new Consumer().get();
set(new Derived(), base);
_

また、_Derived, Base_を引数として取る正しいメソッドを見つけることができません。したがって、エラーがスローされますが、理由は異なります。

_    set(new Derived(), new Consumer().get());
    ^
method Consumer.set(Base,Derived) is not applicable
  (argument mismatch; Base cannot be converted to Derived)
method Consumer.set(Derived,Collection<? extends Consumer>) is not applicabl e
  (argument mismatch; Base cannot be converted to Collection<? extends Consu mer>)
_

PS:私の答えの不完全さを指摘してくれたHolgerに感謝します。

5
Jatin