Потребитель и Consumer <T>кажется не полностью эквивалентным - то же самое с любым функциональным интерфейсом Consuming - PullRequest
6 голосов
/ 19 июня 2020

У меня ошибка компилятора java, которую я не понимаю. Похоже, Consumer <? > и Consumer (с T extends Object) не эквивалентны в аргументах сигнатуры метода. Пожалуйста, ознакомьтесь с приведенным ниже кодом:

import java.util.function.Consumer;

public class MyClass {

    public void joker(Consumer<?> fct) {}
    public <T> void generic(Consumer<T> fct) {}
    public void myConsumer(String s) {}

    public void doesNotCompile()
    {
        joker(this::myConsumer); // COMPILE ERROR : (In Eclipse : ) The type MyClass does not define myConsumer(Object) that is applicable here
        generic(this::myConsumer); // Works fine : how come are "T" and "?" not equivalent here ?

        // The following also works fine as usual :
        Consumer<String> cs = this::myConsumer;
        joker(cs);

        joker((String s) -> myConsumer(s));
    }

}

Ошибка немного отличается, когда я компилирую код через командную строку, а не в Eclipse:

D:\>javac -Xdiags:verbose MyClass.java
MyClass.java:11: error: method joker in class MyClass cannot be applied to given types;
                joker(this::myConsumer);
                ^
  required: Consumer<?>
  found:    this::myConsumer
  reason: argument mismatch; invalid method reference
      method myConsumer in class MyClass cannot be applied to given types
        required: String
        found:    Object
        reason: argument mismatch; Object cannot be converted to String
1 error

Это немного яснее, но Я все еще не понимаю. Обратите внимание, что вы получаете аналоговую ошибку с java .util.function.Function, но не с java .util.function.Supplier. Поэтому я считаю, что ошибка возникает для любого функционального интерфейса, принимающего параметр.

Это ошибка компилятора или мне что-то здесь не хватает? В случае последнего (скорее всего), может ли кто-нибудь сказать причину такого дизайна поведения?

Я чувствую, что что-то не так с выводом типа. Я также почти не знаю, что JVM по-разному обрабатывает лямбды и ссылки на методы (AFAIK через MethodHandler). Но если честно, я просто запуталась.

Помогите? ¯ \ (ツ) / ¯

Ответы [ 2 ]

2 голосов
/ 19 июня 2020

joker(this::myConsumer); использует ссылку на метод this::myConsumer в контексте, который заставляет целевой тип быть Consumer<?>, где компилятор вынужден использовать Object для ?. Учитывая это, должна быть подпись myConsumer, которая принимает Object, а в MyClass ее нет.

, почему же «T» и «?» здесь нет эквивалента?

Проблема не в этом. См. Следующий код, который компилируется:

Consumer<String> jokerConsumer = this::myConsumer;
joker(jokerConsumer);

Единственное отличие состоит в том, что ссылка на метод (с целевым типом) имеет совместимый метод в MyClass.

Кроме того, если вы добавьте public void myConsumer(Object s) {} в свой класс, исходный joker(this::myConsumer); компилируется нормально.

Метод generi c работает просто потому, что T считается таким же, как тип параметра myConsumer, что положит конец проблеме.

Итак, это показывает два пути решения текущей проблемы. Короче говоря, это вопрос совместимости целевого типа ссылки на метод (в контексте вызова метода) с сигнатурой ссылочного метода.

2 голосов
/ 19 июня 2020

TL; DR; Компилятор фактически запрещает передавать любое значение, кроме null, в Consumer<?>. Таким образом, использование Consumer<?> буквально не используется.

Попробуйте это в своем методе joker:

fct.accept(new Object());

Вы увидите, что он не компилируется, потому что компилятор не может гарантировать, что переданный Consumer действительно может принять Object s. Но использование null работает, поскольку это единственное допустимое значение для всех Object s:

fct.accept(null);

Вы можете преодолеть это ограничение, используя неконтролируемые приведения, но они действительно обескуражен, так как вам понравится столкнуться с ClassCastException.

Имейте в виду, что Consumer<Object> не равно не равно и Consumer<?>. Предыдущий допускает передачу любых Object, тогда как последний ничего не принимает! (кроме null, конечно)

Ваш другой пример, который работает "нормально":

joker((String s) -> myConsumer(s));

Но под капотом вы также используете непроверенные приведения. Вы неявно преобразуете s из Object в String. Это почти эквивалентно написанию:

joker(s -> myConsumer((String) s));

Или даже этого (потому что s известен только как Object):

joker((Object s) -> myConsumer((String) s));

На ваш другой вопрос, почему T и ? не одно и то же. T - это общий тип c, и компилятор пытается сузить его до наиболее специфичного типа c. В случае generic(this::myConsumer), T будет String, если компилятор не может определить тип, он просто будет использовать Object, поэтому он похож на Consumer<Object>, но не Consumer<?>

В общем, использование Consumer<?>, скорее всего, никогда не будет иметь реального варианта использования, потому что без каких-либо неприятных хаков вы можете передать в него только null. И что от этого толку?

...