Неожиданное преобразование функционального интерфейса 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)
       }
    });