Вывод типа 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> когда-нибудь совпадать с коллекцией?

2 ответа

Решение

Проблема в том, что вывод типа был улучшен. У вас есть метод, как

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 метод и объявить, что он действительно возвращает, Derived,


Кстати, меня раздражает то, что вы утверждаете, что этот код может быть скомпилирован под Java 7. Его ограниченный вывод типов с помощью вложенных вызовов методов приводит к лечению get метод во вложенном контексте вызова, как возвращение Base который не может быть передан методу, ожидающему Derived, Как следствие, попытка скомпилировать этот код с использованием соответствующего компилятора Java 7 также не удастся, но по другим причинам.

Ну, это не так, как если бы 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 есть, тогда как может компилятор?

Один из способов сказать компилятору - жестко запрограммировать и сказать это, в частности: 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) он генерирует исключение во время выполнения:

Исключение в потоке "main" 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,

Теперь оба ваших метода соответствуют первому аргументу. По второму аргументу 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: Спасибо Хольгеру за то, что он указал на неполноту моего ответа.

Другие вопросы по тегам