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
. И что от этого толку?