Как написать краткое замыкание для универсального метода?
Я хочу написать реализацию функционального неуниверсального интерфейса, который имеет универсальный метод. Реализация должна быть встроенной и краткой.
В качестве упрощенного примера
@FunctionalInterface interface Fn {
<R> R fn(R arg);
}
public class Scratch {
Fn id = arg -> arg;
//Fn nul = arg -> null;
//Fn requiresNonNull = ...
}
который дает
/Scratch.java:5: error: incompatible types: invalid functional descriptor for lambda expression
Fn id = arg -> arg;
^
method <R>(R)R in interface Fn is generic
where R is a type-variable:
R extends Object declared in method <R>fn(R)
1 error
(На самом деле параметр был бы универсальным интерфейсом с методами, которые имели возвращаемый тип R
.)
Есть ли обходной путь, не возвращаясь к многословию анонимного внутреннего класса?
Существует, по-видимому, похожий вопрос: " Не удается преобразовать функциональный интерфейс с помощью универсального метода в лямбда-выражение", но это связано с использованием параметра типа, называемого Integer
вместо чего-то обычного, как T
и в принятом ответе Джона Скита говорится, что он не знает решения моей проблемы.
Существует также долгая дискуссия " Путаница с функциональным интерфейсом", которая не может ответить на этот вопрос. Это не может быть "подробные анонимные внутренние классы лучше здесь", не так ли?
2 ответа
Общие лямбды не являются законными, но ссылки на общие методы являются. Вы можете немного сократить многословие анонимных классов, создав вместо этого вспомогательные методы:
public class Scratch {
Fn id = Scratch::id;
Fn nul = Scratch::nul;
Fn requiresNotNull = Objects::requireNonNull;
private static <R> R id(R arg) {
return arg;
}
private static <R> R nul(R arg) {
return null;
}
}
После долгих экспериментов и косвенного обращения у меня есть решение. Я был менее успешен с именами.
Вот идея
- Ни функциональный интерфейс, ни единственный абстрактный метод не имеют параметра типа.
- Функциональный интерфейс получает получателя с параметром типа, но в параметре метода подстановочный знак.
- Потребитель - это просто кусок внутреннего gubbins, но у него есть этот параметр типа. Он используется для сохранения результата, в то время как выполнение возвращается через включающую функцию.
- Сам потребитель получает функциональный интерфейс, содержащий фактический экземпляр бизнес-функции, типизированный типом.
- Есть метод по умолчанию, который связывает вещи вместе, включая создание потребителя.
Очистить? [риторический]
Так что вместо возможности писать
Fn id = arg -> arg;
мы можем хотя бы написать
Fn id = q -> q.q(arg -> arg);
которая является лямбда-лямбда-фабрикой.
Кажется, у нас кончился синтаксис и мы не можем написать что-то вроде
Fn id = Fn.Consumer::q(arg -> arg); // not valid syntax!
В целом (с основным, чтобы показать, что я не обманываю)
import java.util.concurrent.atomic.*;
@FunctionalInterface interface Fn {
interface Instance<R> {
R fn(R arg);
}
interface Consumer<R> {
void q(Instance<R> gn);
}
void consume(Consumer<?> consumer);
default <R> R fn(R arg) {
AtomicReference<R> result = new AtomicReference<>();
this.consume((Instance<R> instance) -> { result.set(instance.fn(arg)); });
return result.get();
}
}
public interface Scratch {
Fn id = q -> q.q(arg -> arg);
Fn nul = q -> q.q(arg -> null);
public static void main(String[] args) {
String idStr = id.fn("cruel");
String nulStr = nul.fn("cruel");
System.err.println(idStr);
System.err.println(nulStr);
}
}
Я не думаю, что использовал какую-то нехватку в системе типов.
(Я, вероятно, должен добавить более сложный пример к вопросу, чтобы показать, почему вы можете сделать это.)