Использование ссылок на методы для экземпляра, определяемого во время выполнения в Java

Я тестировал правила использования ссылок на методы, но написанный мной код не компилировался. Компилятор постоянно сообщает мне, что я не могу ссылаться на нестатический метод из статического контекста. Однако в документах Java прямо написано, что можно использовать «::» для «ссылки на метод экземпляра произвольного объекта определенного типа». Может ли кто-нибудь указать, что не так с моим кодом? Благодарю вас!

package Test;
import java.util.function.BiPredicate;

class Evaluation {
    public boolean evaluate(int a, int b) {
        if (a-b ==5){
            return true ;
        }
        return false; 
    }

    public void methodTest() {
        BiPredicate<Integer, Integer> biPredicate = Evaluation::evaluate;
        System.out.println(biPredicate.test(6,1));
    }
}

Изменить: после прочтения ответов мне стало интересно, так ли это, что ссылка на метод экземпляра по имени класса работает только в некоторых функциональных интерфейсах, но не в других? Например,

BiPredicate <String, Integer> biPredicate =  String::startsWith;

не компилируется, пока:

Predicate <String> predicate = String::isEmpty;

компилирует. Если это так, есть ли страница/руководство/что угодно, на что кто-нибудь может мне направить, объясняющее, какие интерфейсы функций совместимы, а какие нет?


person Eddie Lin    schedule 17.01.2017    source источник
comment
См. также здесь   -  person Holger    schedule 18.01.2017
comment
String::startsWith будет принимать 3 аргумента; 1. экземпляр String для вызова, 2. параметр String prefix и 3. параметр int toffset. Но Bipredicate<String, Integer> может составлять только 2 из них. String::isEmpty принимает 1 параметр, экземпляр для вызова, поэтому Predicate<String> будет работать.   -  person Jorn Vernee    schedule 18.01.2017
comment
@JornVernee Вы совершенно правы. Однако почему Predicate <String> predicate = String::isEmpty; работает? Например, почему мне не нужно передавать новый экземпляр String() (как может указывать ваш ответ ниже на мой первоначальный вопрос)?   -  person Eddie Lin    schedule 18.01.2017
comment
@EddieLin Вам нужно будет сделать это при его вызове, метод test принимает 1 параметр. Это не обязательно должно быть новым, любой экземпляр будет работать (в моем ответе я просто использовал new Evaluation() в качестве примера)   -  person Jorn Vernee    schedule 18.01.2017
comment
@JornVernee Когда я назову это, это будет что-то вроде System.out.println("predicate.test(""));верно? Мне не нужно создавать новый функциональный интерфейс с методом, который принимает что-то вроде (Evaluation instance, String a). Зачем мне это нужно для моего кода с BiPredicate? Является ли BiPredicate особым случаем?   -  person Eddie Lin    schedule 18.01.2017
comment
@Eddie Lin: Как уже объяснил Джорн Верни на примере String::startsWith, метод экземпляра, принимающий два аргумента, приводит к функции, принимающей три аргумента. Вашему evaluate нужен экземпляр Evaluation и два аргумента int, следовательно, вам нужен тип функции с тремя параметрами. Поскольку функция Bi имеет только два параметра, она неуместна. А так как предустановленного TriFunction интерфейса нет, то приходится создавать свой собственный интерфейс.   -  person Holger    schedule 18.01.2017
comment
Просто учтите, что ссылка на метод эквивалентна (instance, i, j) -> instance.evaluate(i, j). Три параметра.   -  person Holger    schedule 18.01.2017
comment
@Holger Теперь это имеет смысл. Если я изменю Predicate <String> predicate = String::isEmpty; на что-то вроде Predicate <String> predicate = stringInstance::isEmpty;, он не скомпилируется. Спасибо за все ваши ответы!   -  person Eddie Lin    schedule 18.01.2017


Ответы (4)


При статической ссылке на метод экземпляра возвращаемый функтор принимает дополнительный аргумент, представляющий экземпляр.

interface Func {
    boolean evaluate(Evaluation instance, int a, int b);
}
...
Func biPredicate = Evaluation::evaluate;
System.out.println(biPredicate.evaluate(new Evaluation(), 6, 1));

Но вам нужно будет передать экземпляр Evaluation при его вызове.

Поскольку в вашем методе evaluate не используются какие-либо поля экземпляра, вы можете также сделать его static, тогда вам не нужно будет передавать экземпляр и можно использовать только BiPredicate<Integer, Integer>, как вы пытались.

person Jorn Vernee    schedule 17.01.2017
comment
Func biPredicate = Evaluation::evaluate синтаксическая ошибка: Невозможно сделать статическую ссылку на нестатический метод Assessment(int, int) из Evaluation типа. Введение нового функционального интерфейса ничего не меняет. - person Andreas; 18.01.2017
comment
@Andreas Попробуйте еще раз, у меня там опечатка. Он отлично работает для меня :) - person Jorn Vernee; 18.01.2017
comment
Не проще ли просто написать biPredicate = this::evaluate, чем создавать новый функциональный интерфейс? Или biPredicate = new Evaluation()::evaluate, если вы настаиваете на использовании нового экземпляра объекта (хотя почему?)? - person Andreas; 18.01.2017
comment
@Andreas Андреас Это другое решение, но я хочу объяснить, почему версия OP не работает и как заставить ее работать. Мое предложение состояло в том, чтобы сделать метод статическим. - person Jorn Vernee; 18.01.2017
comment
Ваш код представляет собой очень многословный способ реализации new Evaluation()::evaluate. - person Andreas; 18.01.2017
comment
@Andreas Мой код показывает, как работает статическая ссылка на метод экземпляра на самом деле, он многословен, потому что в java.util.function нет стандартного интерфейса, который вы могли бы использовать. - person Jorn Vernee; 18.01.2017
comment
Я собирался опубликовать это. Это показывает, что вы можете ссылаться на метод экземпляра с именем класса, при условии, что вы можете предоставить правильный функциональный интерфейс. - person xiaofeng.li; 18.01.2017
comment
@Andreas: этот ответ касается фактического вопроса ОП. Аргумент new Evaluation() — это просто пример, так как OP не предоставил аргумент. Но вы можете передавать разные экземпляры при каждом вызове… - person Holger; 18.01.2017

Если ваш метод является методом экземпляра, вам нужно вызвать его в каком-то экземпляре, например:

public void methodTest(){
    BiPredicate<Integer, Integer> biPredicate = this::evaluate;
    System.out.println(biPredicate.test(6,1));
}

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

person Michał Szewczyk    schedule 17.01.2017
comment
Писал тот же ответ ;) - person JFPicard; 17.01.2017
comment
Поскольку метод не использует переменные/методы экземпляра, я бы сказал, что ваш ответ неверен, и этот OP должен вместо этого просто сделать метод static. В качестве альтернативы, поскольку код находится в нестатическом методе, зачем создавать новый экземпляр? Используйте this::evaluate. - person Andreas; 17.01.2017

Я все еще пытаюсь выяснить применимое правило, но проблема исчезает, если вы используете

BiPredicate<Integer, Integer> biPredicate = this::evaluate;

Я ломаю голову над https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13, но, насколько я могу судить, потому что Evaluation::evaluate заставляет компилятор создать произвольный объект тип Evaluation, и вы вызываете его из объекта этого типа, что правило другое. Вам нужно вызвать его из конкретного объекта, внутри которого появляется метод methodTest.

Хотя у меня нет объяснения, решение состоит в том, чтобы использовать this::evaluate. Это однозначно связывает ссылку на метод с вызывающим его объектом.

Примечание: вам не нужно оценивать boolean как условное выражение, чтобы получить boolean из boolean. Вы могли бы просто return a - b == 5;.

person Lew Bloch    schedule 17.01.2017

Я, вероятно, слишком поздно, чтобы ответить на этот вопрос, но, поскольку вопрос все еще остается без ответа, я хотел бы попытаться ответить.

Я думаю, что есть промах в том, чего пытается достичь OP.

Я понимаю, что ОП пытается понять, почему что-то вроде этого сработает:

    String str = "abc";
    Predicate<String> methodRef = str::startsWith; 
    methodRef.test("s");

а потом,

Predicate <String> predicate = String::isEmpty 

Работает и аналогичным образом, почему бы и нет

Predicate <String> predicate =  String::startsWith;

Компиляция, которая принимает имя класса String.

Это просто потому, что Predicate в основном принимает любой аргумент и возвращает логическое значение. Это неправильная настройка для этой проблемы.

Вместо этого вы можете попробовать,

BiFunction<String, String, Boolean> methodRef2 = String::startsWith;
methodRef2.apply("sdsdfsd", "sdfsdf");

Это будет работать, так как для запуска требуется исходная строка, строка для проверки и возвращаемое значение. В основном есть 4 способа вызова ссылок на методы в Java 8.

  1. Вызовы статических методов.
  2. Вызов метода экземпляра.
  3. Вызовы методов класса
  4. Конструкторы
person GingerBeer    schedule 09.05.2020