Интерфейс как функции в Kotlin

Я работаю над библиотекой Android, которая содержит некоторые представления. Естественно, эти взгляды могут излучать события.

У меня есть интерфейс, который называется (только для целей этого вопроса) Listener. Если бы я написал библиотеку на Java, все было бы так:

public interface Listener {
    void onEvent();
}
public class SomeView extends FrameLayout {
    // Some more functions and implementation details

    public void setListener(Listener l) { ... }
}

При использовании этого представления в деятельности Kotlin я могу использовать setListener следующим образом:

someViewInstance.setListener {
    // implementation
}

Я хочу написать свою библиотеку на Kotlin, но она может быть использована и в коде Java, поэтому я хочу предоставить и интерфейс для слушателя, как в обычном представлении (как выше), но иметь возможность для кода Kotlin использовать функцию реализация:

interface Listener {
    fun onEvent()
}

когда я пытаюсь использовать setListener как описано выше, в моей тестовой деятельности Kotlin, я получаю ошибку компиляции, говорящую, что функция ожидает тип Listener но got () → Unit.

Есть ли способ включить такую реализацию в Kotlin, не создавая для этого новую функцию?

Я думал о том, чтобы иметь только одну функцию, которая получает () → Unit но потом это выглядело странно в коде Java (Function1 и т.д.).

Спасибо!

Ответ 1

Вы можете определить свой интерфейс как предложено, а также добавить расширение, которое позволяет использовать лямбду, которая более идиоматична для кода Kotlin.

class SomeView {
    fun setListener(l: Listener) {}
}

fun SomeView.setListener(l: () -> Unit) = setListener(object : Listener {
    override fun onEvent() = l()
})

В Java вы все равно сможете пройти реализацию Listener.

Ответ 2

Это называется SAM-конверсиями,

Как и Java 8, Kotlin поддерживает преобразования SAM. Это означает, что литералы функций Kotlin могут автоматически преобразовываться в реализации интерфейсов Java с помощью одного метода, не используемого по умолчанию, при условии, что типы параметров метода интерфейса соответствуют типам параметров функции Kotlin.

Но

Обратите внимание, что преобразования SAM работают только для интерфейсов, но не для абстрактных классов, даже если они также имеют только один абстрактный метод.
Также обратите внимание, что эта функция работает только для взаимодействия с Java; Поскольку у Kotlin есть надлежащие типы функций, автоматическое преобразование функций в реализации интерфейсов Kotlin не требуется и, следовательно, не поддерживается.

Таким образом, вы не можете написать простой код Kotlin для имитации этого вызова.


В Java, если вы пишете

public interface Listener {
    void onEvent(); // SAM: Single Abstract Method. Only 1 is allowed
}

И у вас есть

public class SomeView extends FrameLayout {
    // skip the constructors

    public void setListener(Listener listener) {
        // do something
    }
}

Тогда вы можете сделать такой необычный звонок в Kotlin, благодаря SAM-конвертации:

SomeView(this).setListener {} // asking a parameter with type () -> Unit for setListener
                              // Then parenthesis of function call can be omitted
// setListener function can also accept a parameter with type Listener
// by object : Listener {}

Но если вы конвертируете этот Java файл в Kotlin, код сообщит об ошибке по причине, указанной выше. Вы должны самостоятельно реализовать функцию SomeView.setListener(() → Unit), например

fun SomeView.setListener(l: () -> Unit) {
    listener = object : Listener{
        override fun onEvent() {
            l()
        }
    }
}