Неожиданное преобразование функционального интерфейса Java
У меня есть следующий фрагмент кода, который использует функциональные интерфейсы Java, который компилируется, но не ясно, почему он компилируется:
public class App {
public static void main(String[] args) throws Exception {
final RecordIterator it = new RecordIterator<MyRecord>();
final UpdateManager updateManager = new UpdateManager();
updateManager.doUpdateForEach(it, DatabaseOperator::updateInfo);
}
}
class UpdateManager {
public void doUpdateForEach(final RecordIterator recordIterator,
final FunctionalStuff<MyRecord> updateAction) throws Exception {
updateAction.execute(new DatabaseOperator(), new MyRecord());
}
}
class RecordIterator<E> {
}
@FunctionalInterface
interface FunctionalStuff<T> {
void execute(final DatabaseOperator database, final T iterator) throws Exception;
}
class DatabaseOperator {
public void updateInfo(final MyRecord r) {
}
}
class MyRecord {
}
Итак, моя путаница внутри main
метода:
- последняя строка основного метода -
updateManager.doUpdateForEach(it, DatabaseOperator::updateInfo);
- метод
UpdateManager#doUpdateForEach
ожидает RecordIterator
(хорошо, имеет смысл) и FunctionalStuff
-
FunctionalStuff
имеет единственный метод (очевидно), который получает 2 параметра - Второй аргумент
doUpdateForEach
- это ссылка на метод (DatabaseOperator::updateInfo
) - Метод
DatabaseOperator::updateInfo
получает один аргумент
как это компилируется? Как преобразовать ссылку на метод DatabaseOperator::updateInfo
в функциональный интерфейс? Я что-то упускаю из виду? Или это какой-то угловой случай функциональных интерфейсов?
Ответы
Ответ 1
Как преобразовать ссылку на метод DatabaseOperator::updateInfo
в функциональный интерфейс?
Эффективное лямбда-представление вашей ссылки на метод:
updateManager.doUpdateForEach(it, (databaseOperator, r) -> databaseOperator.updateInfo(r));
который является дальнейшим представлением анонимного класса:
new FunctionalStuff<MyRecord>() {
@Override
public void execute(DatabaseOperator databaseOperator, MyRecord r) throws Exception {
databaseOperator.updateInfo(r);
}
});
Ответ 2
Прежде всего, FunctionalStuff<T>
определяется так:
@FunctionalInterface
interface FunctionalStuff<T> {
void execute(final DatabaseOperator database, final T iterator) throws Exception;
}
Ссылка на метод DatabaseOperator::updateInfo
преобразуется в экземпляр FunctionalStuff<MyRecord>
следующим образом (я оставил фактические типы для пояснения, но их можно опустить):
FunctionalStuff<MyRecord> func = (DatabaseOperator database, MyRecord r) -> database.updateInfo(r);
Или, если вы хотите использовать его как анонимный класс:
FunctionalStuff<MyRecord> func = new FunctionalStuff<MyRecord>() {
void execute(final DatabaseOperator database, final MyRecord r) {
database.updateInfo(r);
}
}
Смотрите учебник со следующим примером:
Ссылка на метод экземпляра произвольного объекта определенного типа
Ниже приведен пример ссылки на метод экземпляра произвольного объекта определенного типа:
String[] stringArray = { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
Эквивалентное лямбда-выражение для ссылки на метод String::compareToIgnoreCase
будет иметь список формальных параметров (String a, String b)
, где a
и b
- произвольные имена, используемые для лучшего описания этого примера. Ссылка на метод будет вызывать метод a.compareToIgnoreCase(b)
.
Ответ 3
Существует 4 различных типа ссылки на метод, и вы используете ссылку на метод Instance для объекта определенного типа.
Теперь, на первый взгляд, это похоже на static method reference
на static method reference
который является Class::staticType
но имеет следующее отличие:
- the method should be present in the class same as type of first argument in functional interface
- The method used should have one less argument as opposed to number of arguments in the method declared in functional interface as the **this** reference is taken as first argument.
Таким образом, в вашем случае метод DatabaseOperator#updateInfo
присутствует в классе DatabaseOperator
который совпадает с типом DatabaseOperator
первого аргумента метода execute
внутри функционального интерфейса, и число аргументов в методе на единицу меньше, поскольку используется ссылка this
в качестве первого аргумента.
Если вы измените DatabaseOperator # updateInfo на два аргумента, то компилятор выдаст ошибку, в которой говорится, что Cannot make a static reference to the non-static method updateInfo from the type DatabaseOperator
. Таким образом, вы можете либо создать метод, который будет использоваться как ссылочный, статический, либо использовать new DatabaseOperator()#updateInfo
который является двумя другими типами ссылок на метод в java.
Ответ 4
Это не то, как на самом деле работают лямбды, но по сути вы могли видеть
updateManager.doUpdateForEach(it, DatabaseOperator::updateInfo);
как
DatabaseOperator referencedMethodOwner = instanceGivenAsMethodExpression;
updateManager.doUpdateForEach(it,
new FunctionalStuff{
void execute(final T iterator) throws Exception{
referencedMethodOwner.updateInfo(iterator)
}
});