Контекст назначения функционального интерфейса Java 8

Вопрос касается контекста назначения функционального интерфейса-

Predicate<String> p = String::isEmpty;

Отлично работает, когда объявление метода isEmpty в классе String - public boolean isEmpty(){}.

Если я попытаюсь объявить то же самое в пользовательском классе, например -

class Test{
   public boolean isEmpty(){
       ...
   }
}

И сделать такое же задание -

Predicate<String> p = Test::isEmpty;

Это будет ошибка компиляции -

Тип Test не определяет isEmpty(String), который применим здесь.

И Predicate<T> представляет собой предикат (логическую функцию) одного аргумента, а функциональный метод - boolean test(T t){}.

Любое объяснение? И я что-то пропустил?


person Subhrajyoti Majumder    schedule 08.08.2014    source источник
comment
Довольно просто: класс не является интерфейсом, поэтому он не может быть функциональным интерфейсом.   -  person Brian Goetz    schedule 09.08.2014


Ответы (4)


Вы должны иметь:

Predicate<Test> p = Test::isEmpty;

и нет

Predicate<String> p = Test::isEmpty;

Нет строки в

class Test {
    public boolean isEmpty(){
           ...
    }
}

так почему должно быть Predicate<String> ?

См. руководство по ссылкам на методы. Здесь у вас есть третий случай «Ссылка на метод экземпляра произвольного объекта определенного типа».

Имея

Predicate<String> p = String::isEmpty();
String s = "";

Вызов p.test(s); аналогичен вызову s.isEmpty();, поэтому вы не можете указать в качестве аргумента String для вызова метода из Test.

Было бы возможно иметь общий Predicate, если бы и String, и Test реализовали интерфейс Empty с методом boolean isEmpty(), а затем иметь Predicate<Empty>. Тогда и p.test(string), и p.test(test) будут работать; в противном случае этого не произойдет, Java имеет строгую типизацию и не поддерживает утиную типизацию.

person Random42    schedule 08.08.2014

Ссылку на метод можно использовать несколькими способами. Он может захватить экземпляр для вызова. Он может ссылаться на метод, который следует вызывать для параметра, предоставленного функциональным интерфейсом (ваш первый пример). Его также можно использовать в качестве цели вызова, когда параметр в функциональном интерфейсе передается в качестве аргумента для вызова указанного метода (ваш второй пример).

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

person Sotirios Delimanolis    schedule 08.08.2014
comment
можете ли вы рассказать мне логику компиляции, стоящую за назначением функционального интерфейса. - person Subhrajyoti Majumder; 08.08.2014
comment
@sub Это в JLS здесь. Смотрите второй пункт. - person Sotirios Delimanolis; 08.08.2014

Я хочу добавить к ответу @ m3th0dman.

У вас есть способ определить Predicate<String> с помощью ссылки на метод вашего класса Test. Это можно сделать, определив метод isEmpty как статический и с аргументом String.

public class Test
{

  public static boolean isEmpty (String s)
  {
    return s.length() == 0;
  }

  public static void main (String[] args)
  {
      Predicate<String> p = Test::isEmpty;
      System.out.println(p.test("ff"));
      System.out.println(p.test(""));
  }

}
person Eran    schedule 08.08.2014

Когда вы использовали Predicate<String>, то приемлема только ссылка на метод String.
Чтобы дать ссылку на метод класса Test, вам понадобится Predicate<Test>
Поэтому код должен быть:

Predicate<Test> p = Test::isEmpty;

вместо

Predicate<String> p = Test::isEmpty;

Чтобы сделать мою точку зрения более ясной, рассмотрим следующий код: -

public static void main(String args[]){
    Predicate<String> p;
    p = String::isEmpty;
    Predicate<Test> q;
    q = Test::isEmpty;
}

interface Predicate<T>{
    public boolean test(T t);
}

class Test{
    public boolean isEmpty(){
        return true;
    }
}
person afzalex    schedule 08.08.2014