В default методе интерфейса возвращаемое значение может не соответствовать сигнатуре метода?
Тема - лямбда выражения, в контексте паттерна цепочка ответственности. Встретился в сети такой код:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@FunctionalInterface
interface RequestHandler {
public abstract Request handle(Request r);
default RequestHandler setSuccessor(RequestHandler other) {
return (req) -> { //req - it's request
System.out.println("req instance of: " +req.getClass().getName());
return other.handle(this.handle(req));
};
}
}
public class Request {
String data;
public String getData() {
return data;
}
public Request(String data) {
this.data = data;
}
}
Детальнее - есть класс Request - в нём только строка, с конструктором и геттером. Есть интерфейс RequestHandler - он обработчик запросов. В нём есть абстрактный метод handle - для лямбда выражения. И есть метод setSuccessor - для более глубоких манипуляций. Вот метод setSuccessor вызывает у меня вопросы. Почему он вообще компилится? По сигнатуре, он должен возвращать RequestHandler, но в первом return (req) - я выяснил, что req - это просто Request. (Строчка кода System.out.println("req instance of: " +req.getClass().getName()); сказала что req instance of Request) Во втором return other.handle(this.handle(req)); - возвращаемое значение, как ни крути, тоже Request, a не RequestHandler! К примеру выражение RequestHandler r = other.handle(this.handle(req)); - говорит о несовместимости типов.
Суммарный вопрос: где и в какой строке, метод setSuccessor умудрился вернуть объект интерфейса RequestHandler?
Ответы (2 шт):
Разберитесь, что такое функциональный интерфейс. Чтобы быть RequestHandler нужно предоставлять метод с аргументом типа Request и возвращаемым значением типа Request. Это следует из сигнатуры handle. Лямбда-выражение (req) -> {...}, грубо говоря, определяет анонимный объект с таким методом. Как вы правильно заметили, параметер req имеет тип Request, и возвращаемое значение внутри фигурных скобок тоже имеет тип Request. Получается этот объект удовлетворяет требованиям RequestHandler.
Да, действительно java может возвращать "объект интерфейса", даже если в методе возврата нигде явно не создан такой "объект". "Объект интерфейса" - пишу в кавычках, потому что мы всё равно подразумеваем объект конкретного (часто анонимного) класса, которым управляет интерфейсная ссылка.
До java 8 метод мог вернуть "объект" интерфейса как-то так:
public interface Marker { //Интерфейс
public int doSomething(int x);
}
public Marker m(){ //Метод возвращающий "объект" интерфейса
return new Marker(){ //создание объекта и реализация его на базе анонимного класса
public int doSomething(int x){ //создание конкретной реализации абстрактного метода
return 25;
};
};
}
После выхода java 8, чтобы создать "объект интерфейса" нам по прежнему нужно объявить какую-то реализацию метода. Но если в интерфейсе только один абстрактный это можно сделать лямбда выражением:
public Marker m(){ //Тот же метод, c возможностями Java 8
return x-> 25; //Этой строчкой мы задали конкретную реализацию метода doSomething
}
Что за магия произошла x-> 25? В терминах лямбда выражения, мы задали f(x) = 25; f(x) - это конкретная реализация абстрактного метода doSomething(int x); Язык программирования понял это как "из метода m() верни мне конкретную реализацию Marker, такую что, его единственный абстрактный метод doSomething(int x) станет возвращать константу {return 25;};
То есть, по сути чтобы создать "объект функционального интерфейса" достаточно просто реализовать его абстрактный метод лямбда выражением:
Marker m = re->re*2; //Это полностью валидный код. Сказал что реализация метода doSomething(int x){return x*2}
Возвращаясь к методу:
default RequestHandler setSuccessor(RequestHandler other) {
return (req) -> { //req - это Request компилятор вывел из контекста
System.out.println("req instance of: " +req.getClass().getName());
return other.handle(this.handle(req));
};
}
Здесь было сказано "реализация абстрактного метод handle(зависящего от параметра Request) такая, что":
handle(Request r){
System.out.println("r instance of: " +r.getClass().getName());
return other.handle(this.handle(r));
}
И далее, этот метод благополучно вернул "объект интерфейса" RequestHandler с конкретной реализацией данного метода.