Ссылка на метод неоднозначна для Thread.sleep
Я столкнулся с какой-то странной проблемой, где ссылка метода на Thread::sleep
неоднозначна, но метод с той же сигнатурой не является.
package test;
public class Test
{
public static void main(String[] args)
{
foo(Test::sleep, 1000L); //fine
foo((FooVoid<Long>)Thread::sleep, 1000L); //fine
foo(Thread::sleep, 1000L); //error
}
public static void sleep(long millis) throws InterruptedException
{
Thread.sleep(millis);
}
public static <P, R> void foo(Foo<P, R> function, P param) {}
public static <P> void foo(FooVoid<P> function, P param) {}
@FunctionalInterface
public interface Foo<P, R> {
R call(P param1) throws Exception;
}
@FunctionalInterface
public interface FooVoid<P> {
void call(P param1) throws Exception;
}
}
Я получаю эти 2 ошибки:
Error:(9, 17) java: reference to foo is ambiguous
both method <P,R>foo(test.Test.Foo<P,R>,P) in test.Test and method <P>foo(test.Test.FooVoid<P>,P) in test.Test match
Error:(9, 20) java: incompatible types: cannot infer type-variable(s) P,R
(argument mismatch; bad return type in method reference
void cannot be converted to R)
Единственное отличие, которое я вижу, это то, что Thread::sleep
есть native
. Что-то меняет? Я не думаю, что здесь происходит перегрузка Thread::sleep(long, int)
. Почему это происходит?
EDIT: Использование javac версии 1.8.0_111
Ответы
Ответ 1
Вы можете воссоздать проблему в своем классе, добавив метод sleep
с двумя аргументами в класс Test, как показано ниже:
public static void sleep(long millis) {
}
public static void sleep(long millis, int nanos) {
}
Таким образом, проблема действительно вызвана тем, что метод sleep перегружен.
JLS указывает, что код выбора исходного метода рассматривает только количество аргументов типа для функционального интерфейса - только во второй фазе он смотрит на подпись метода внутри функционального интерфейса.
Невозможно указать конкретную подпись, которая должна быть сопоставлена, например, Arrays:: sort (int []). Вместо этого функциональный интерфейс предоставляет типы аргументов, которые используются в качестве входных данных для перегрузки (§15.12.2).
(второй абзац этого раздела)
Таким образом, в случае Thread::sleep
, void sleep(long)
потенциально соответствует функциональному интерфейсу FooVoid<P>
, а перегрузка void sleep(long, int)
потенциально соответствует функциональному интерфейсу Foo<P, R>
. Вот почему вы получаете ошибку "ссылка на foo is twoiguous".
Когда он пытается пойти дальше и посмотреть, как сопоставить Foo<P, R>
с функциональным методом R call(P param1)
с методом void sleep(long, int)
, он обнаруживает, что это на самом деле невозможно, и вы получаете еще одну ошибку компиляции:
test/Test.java:7: error: incompatible types: cannot infer type-variable(s) P,R
foo(Thread::sleep, 1000L); // error
^
(argument mismatch; bad return type in method reference
void cannot be converted to R)
Ответ 2
Проблема в том, что оба, Thread.sleep
и foo
, перегружены. Таким образом, существует круговая зависимость.
- Чтобы узнать, какой метод
sleep
использовать, нам нужно знать целевой тип, т.е. какой метод foo
вызывать
- Чтобы узнать, какой метод
foo
нужно вызвать, нам нужно знать функциональную сигнатуру аргумента, т.е. какой метод sleep
мы выбрали
Пока понятно, что для этого сценария только одна из комбинаций 2 × 2 действительна, компилятор должен следовать формальным правилам, которые работают для произвольных комбинаций, поэтому разработчикам языка пришлось сделать разрез.
Для полезности ссылок на методы существует специальное обращение к однозначным ссылкам, например, ваш Test::sleep
:
JLS §15.13.1
Для некоторых ссылочных выражений метода существует только одно возможное объявление времени компиляции с одним возможным типом вызова (§15.12.2.6), независимо от типа целевой функции. Такие ссылочные выражения метода называются точными. Неверное точное ссылочное выражение метода называется неточным.
Обратите внимание, что это различие похоже на различие между неявно типизированными лямбда-выражениями (arg -> expression
) и явно типизированными лямбда-выражениями ((Type arg) -> expression
).
Когда вы смотрите JLS, §15.12.2.5., выбирая наиболее специфический метод, вы увидите, что подпись ссылки метода используется только для точных ссылок на методы, так как при выборе правильного foo
решение для правильного метода sleep
еще не сделано.
Если e
является точным ссылочным выражением метода (§15.13.1), то i) для всех я (1 ≤ я ≤ k), U
i совпадает с V
i, и ii) верно одно из следующих утверждений:
-
R₂
- void
. -
R₁ <: R₂
. -
R₁
является примитивным типом, R₂
является ссылочным типом, и объявление времени компиляции для ссылки на метод имеет тип возврата, который является примитивным типом. -
R₁
является ссылочным типом, R₂
является примитивным типом, а декларация времени компиляции для ссылки метода имеет тип возврата, который является ссылочным типом.
Вышеприведенное правило было указано в п. 15.12.2.5. для неосновных методов, перенаправление на §18.5.4 для общих методов (которые здесь применяются, поскольку ваши методы foo
являются общими), содержащие точно такое же правило с немного другой формулировкой.
Так как подпись ссылок метода не учитывается при выборе наиболее конкретного метода, не существует особого метода, и вызов foo
неоднозначен. Вторая ошибка компилятора является результатом стратегии продолжения обработки исходного кода и потенциального сообщения об ошибках, а не остановки компиляции сразу при первой ошибке. Одно из двух вызовов foo
вызвало ошибку "несовместимых типов", если этот вызов был выполнен, но на самом деле это было исключено из-за ошибки "неоднозначного вызова".
Ответ 3
Лично я рассматриваю это как некоторую рекурсию, так или иначе: нам нужно решить этот метод, чтобы найти целевой тип, но нам нужно знать тип цели, чтобы разрешить этот метод. Это имеет какое-то отношение к специальному правилу совместимости void, но я признаю, что не полностью его понимаю.
Вещи даже funner, когда у вас есть что-то вроде этого:
public static void cool(Predicate<String> predicate) {
}
public static void cool(Function<String, Integer> function) {
}
И попробуйте позвонить через:
cool(i -> "Test"); // this will fail compilation
И btw, если вы сделаете свою ямбду явной, это будет работать:
foo((Long t) -> Thread.sleep(t), 1000L);