Недопустимая ссылка на метод для перегруженного метода с разными значениями

При попытке скомпилировать выражение Comparator.comparing(String::toLowerCase) компилятор Java возвращает ошибку. См. следующий вопрос для получения дополнительной информации:

Почему Comparator.comparing не работает с методом String::toLowerCase ссылка?


Я постарался максимально уменьшить проблему. В частности, я убрал почти все зависимости от других классов. Метод main содержит два вызова метода. Первый оператор компилируется без ошибок, тогда как второй оператор выдает ошибку.

interface Fun<T, R> { R apply(T t); }

public final class Foo {
    public static void main(String... args) {
        invoke(Foo::bar); // OK
        invoke(Foo::baz); // ERROR
    }
    private static <T, U> void invoke(Fun<T, U> f) { }
    private String bar() { return null; }
    private String baz() { return null; }
    private String baz(Integer i, Integer j) { return null; }
}

Это странно, потому что второй метод baz не должен применяться в данном контексте из-за несовпадения количества параметров. Я взглянул на JLS8 (15.13). Однако это не помогло, потому что правила для ссылок на методы довольно сложны.

Вопрос. Почему возникает ошибка компиляции во втором случае? Должна ли действительно быть ошибка компиляции в соответствии с JLS? Согласно некоторым комментариям по другому вопросу, в Netbeans нет ошибки компиляции.


Для справки, я использую JDK8 версии 1.8.0-b132. Если скомпилировать программу в командной строке, компилятор показывает следующее сообщение об ошибке:

$ /opt/jdk8/bin/javac Foo.java
Foo.java:6: error: incompatible types: cannot infer type-variable(s) T,U
        invoke(Foo::baz); // ERROR
              ^
    (argument mismatch; invalid method reference
      no suitable method found for baz(Object)
          method Foo.baz() is not applicable
            (actual and formal argument lists differ in length)
          method Foo.baz(Integer,Integer) is not applicable
            (actual and formal argument lists differ in length))
  where T,U are type-variables:
    T extends Object declared in method <T,U>invoke(Fun<T,U>)
    U extends Object declared in method <T,U>invoke(Fun<T,U>)
Foo.java:6: error: invalid method reference
        invoke(Foo::baz); // ERROR
               ^
  non-static method baz() cannot be referenced from a static context
2 errors

person nosid    schedule 08.04.2014    source источник
comment
Я не уверен, какой ответ вы ожидаете, кроме Java, недостаточно умный, чтобы понять, что здесь применима только одна из перегрузок.   -  person Louis Wasserman    schedule 08.04.2014
comment
На самом деле, я планировал взглянуть на JLS, чтобы понять, смогу ли я понять правила и то, как они применяются в этой ситуации. Я должен получить ответ к... ноябрю, если мне повезет.   -  person ajb    schedule 09.04.2014
comment
Я думаю, что JLS указывает, что ссылка на метод должна быть уникальной.   -  person aepurniet    schedule 09.04.2014


Ответы (1)


JLS8 (15.13) — это сбивает с толку, но он показывает примеры, похожие на ваш, заявляя, что это двусмысленность в поиске, которую он не может разрешить.

Для вашего примера Intellij говорит, что invoke(Foo::baz); - это циклический вывод, который, я думаю, больше связан с комбинацией invoke, необходимой для вывода типа, а также Foo::baz.

Это можно решить, присвоив функции invoke тип, аналогичный JSL (примеры 15.13.1).

Поиск достаточно умен, чтобы игнорировать двусмысленности, в которых все применимые методы (из обоих поисков) являются методами экземпляра:

Foo.<Foo,String>invoke(Foo::baz); -- эквивалентно Я хочу использовать метод void для Foo, который возвращает String, также известный как String baz()

interface Fun<T, R> { R apply(T t); }
interface Fun2<T,U,R> { R apply(T t, U u); }

public final class Foo {
    public static void main(String... args) {
        invoke(Foo::bar); // OK
        Foo.<Foo,String>invoke(Foo::baz); // NO ERROR
        Fun2<Foo, Integer, String> f2 = Foo::baz; // Overloaded method baz
    }
    private static <T, U> void invoke(Fun<T, U> f) { }
    private String bar() { return null; }
    private String baz() { return null; }
    private String baz(Integer i) { return null; } 
}

Я согласен с вами, что baz(Integer i) не является допустимым аргументом для invoke, если он не сделан статическим или из экземпляра Foo. Я предполагаю, что алгоритм поиска просто завершает работу, если метод перегружен и пытается определить тип. Потому что он работает только с одной сигнатурой метода.

person Scott    schedule 08.04.2014