Вернуть универсальный функциональный интерфейс в Java 8

#java #generics #functional-programming #java-8

#java #общие #функциональное программирование #java-8

Вопрос:

Я хочу написать что-то вроде фабрики функций. Это должна быть функция, которая вызывается один раз, с различными стратегиями в качестве параметров. Он должен возвращать функцию, которая выбирает одну из этих стратегий в зависимости от параметра, который должен выполняться предикатом. Ну, лучше посмотрите condition3 для лучшего понимания. Проблема в том, что он не компилируется. Я думаю, поскольку компилятор не может понять, что функциональный интерфейс H может быть реализован с помощью реализации. Без дженериков он работает нормально.

 @FunctionalInterface
public interface Finder<T, S> {

    Stream<S> findBest (T t);

    // Valid code
    static <P, Q> Finder<P, Q> condition1 () {
        return p -> null;
    }

    // Valid code, but selects just one of the H's when the method is invoked
    static <P, Q, H extends Finder<P, Q>> H condition2 (Pair<Predicate<P>, H>... hs) {
        return hs[0].getRight ();
    }

    // Should return a method, which selects the appropiate H 
    // whenever it is invoked with an P
    // Compiler complain: 
    // The target type of this expression must be a functional interface
    static <P, Q, H extends Finder<P, Q>> H condition3 (Pair<Predicate<P>, H>... hs) {
        return p -> stream (hs).filter (pair -> pair.getLeft ().test (p))
                               .findFirst ()
                               .map (Pair::getRight)
                               .map (h -> h.findBest (p))
                               .orElseGet (Stream::empty);
    }
}
  

Так в чем же здесь проблема? Могу ли я решить эту проблему, и если это возможно с помощью Java: как?

Комментарии:

1. Пожалуйста, предоставьте полное сообщение об ошибке компиляции.

2. «Целевой тип этого выражения должен быть функциональным интерфейсом». Это в комментарии над методом.

Ответ №1:

Посмотрите на сигнатуру вашего метода и попытайтесь определить, каков точный возвращаемый тип:

 static <P, Q, H extends Finder<P, Q>> H condition3(…
  

Лямбды могут реализовывать только interface известные во время компиляции. Но фактический аргумент типа for H не известен компилятору.

Ваш первый метод работает, потому что он возвращает тип Finder<P, Q> , который может реализовать лямбда-выражение, ваш второй работает, потому что он не использует лямбда-выражение для реализации возвращаемого типа H extends Finder<P, Q> .

Только ваш третий метод пытается указать лямбда-выражение для аргумента типа H extends Finder<P, Q> .


Решение состоит в том, чтобы не давать вызывающей стороне свободу указывать определенный подтип Finder в качестве возвращаемого типа метода:

 static <P, Q, H extends Finder<P, Q>>
    Finder<P, Q> condition3(Pair<Predicate<P>, H>... hs) {
  

Чтобы проиллюстрировать, какие последствия имеет ваша исходная сигнатура метода, посмотрите на следующий пример:

 final class HImpl implements Finder<String,String> {
    public Stream<String> findBest(String t) {
        return null; // just for illustration, we never really use the class
    }
}
  

 HImpl x=Finder.<String,String,HImpl>condition3();
  

Учитывая вашу исходную сигнатуру метода, это компилируется без каких-либо ошибок. Но как метод должен condition3 предоставлять экземпляр HImpl here, используя ваше лямбда-выражение?

Комментарии:

1. Разве общие элементы не разрешаются во время компиляции? Я думаю, что когда я использую condition3() с аргументом, определяется тип H, и он заменяет общий возвращаемый тип H во время компиляции конкретным типом?

2. Они разрешаются во время компиляции, но только вызывающий метод condition3 знает фактические аргументы типа, поскольку каждый сайт вызова может использовать другой тип для H . Сравните с Collections.emptyList() : он возвращает a List<T> , который может List<Integer> или List<String> в зависимости от того, как вы его используете. Это работает только потому, что возвращаемый список пуст, поскольку реализация Collections.emptyList() никогда не знает тип T и не может предоставить для него реальные значения.

3. Но в Collections.emptyList() нем нет предоставленных аргументов типа. Я думаю, что с Pair<Predicate<P>, H>... hs типом должно быть определено. И когда я предоставляю аргумент типа Pair<Predicate<PImpl>, HImpl> , я ожидаю, что компилятор знает, что возвращаемый тип равен HImpl . И поскольку HImpl из-за ограничения H extends Finder<P, Q> является функциональным интерфейсом, компилятор должен иметь возможность определять тип лямбда-выражения как HImpl .

4. Похоже, вы не понимаете, что делают дженерики. H extends Finder<P, Q> не требуется, чтобы это H был функциональный интерфейс. В нем просто говорится, что H он должен выполнять Finder интерфейс. Это может быть расширение или реализация произвольного типа Finder . Это может быть интерфейс, расширяющий Finder и добавляющий дополнительные методы, поэтому не являющийся функциональным интерфейсом. Фактическим аргументом типа для H может быть даже final класс. Смотрите мой обновленный ответ.