#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()
: он возвращает aList<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
класс. Смотрите мой обновленный ответ.