Передача лямбда вместо интерфейса

Я создал интерфейс:

interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

но может использовать его только как анонимный класс, а не лямбда

dataManager.createAndSubmitSendIt(title, message,
object : ProgressListener {
    override fun transferred(bytesUploaded: Long) {
        System.out.println(bytesUploaded.toString())
    }
})

Я думаю, что это должна быть возможность заменить его лямбдой:

dataManager.createAndSubmitSendIt(title, message, {System.out.println(it.toString())})

Но я получаю ошибку: Тип несоответствие; требуется - ProgressListener, найдено -() → Единица?

Что я делаю не так?

Ответ 1

Как сказал @zsmb13, конверсии SAM поддерживаются только для интерфейсов Java.

Вы могли бы создать функцию расширения, чтобы заставить ее работать:

// Assuming the type of dataManager is DataManager.
fun DataManager.createAndSubmitSendIt(title: String, 
                                      message: String, 
                                      progressListener: (Long) -> Unit) {
    createAndSubmitSendIt(title, message,
        object : ProgressListener {
            override fun transferred(bytesUploaded: Long) {
                progressListener(bytesUploaded)
            }
        })
}

Ответ 2

Kotlin поддерживает только конверсии SAM для интерфейсов Java.

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

- Официальная документация

Если вы хотите использовать лямбда в параметре, сделайте свою функцию вместо параметра "параметр функции" вместо параметра. (На данный момент, по крайней мере. Поддержка конверсий SAM для интерфейсов Kotlin - постоянное обсуждение, это была одна из возможных будущих возможностей в прямом эфире Kotlin 1.1).

Ответ 3

Немного поздно для вечеринки: вместо создания интерфейса вы позволяете компилятору создавать его, беря функцию напрямую, а не интерфейс в вашем datamanager, например:

fun createAndSubmitSendIt(title: String, message: String, transferred: (Long) -> Unit) {
    val answer = TODO("whatever you need to do")
    transferred(answer)
}

и тогда вы просто используете его, как вы этого хотите! Если я правильно помню, что компилятор kotlin/jvm делает то же самое, что и сделать интерфейс.

Надеюсь, поможет!

Ответ 4

Другим решением было бы объявить typealias, ввести его куда-нибудь и вызвать его. Вот пример:

internal typealias WhateverListener = (String) -> Unit

и затем мы вводим эти typealias в наш класс:

class Gallery constructor(private val whateverListener: WhateverListener) {

    ...

    galleryItemClickListener.invoke("hello")

    ...
}

итак у нас наша лямбда

val gallery = Gallery { appNavigator.openVideoPlayer(it) }

Благодарности моему коллеге Джоэлю Педрасе, который показал мне хитрость, пытаясь найти решение <3.

Ответ 5

Единственного окончательного решения этой проблемы не существует, если вы стремитесь получить лучший доступ как с Kotlin, так и с Java.

Если бы разработчики Kotlin не думали, что преобразование SAM для интерфейсов Kotlin не является необходимым, метод "Kotlin Interface" был бы окончательным решением.

https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
Также обратите внимание, что эта функция работает только для взаимодействия с Java; с котлина имеет надлежащие типы функций, автоматическое преобразование функций в реализация интерфейсов Kotlin не нужна и, следовательно, неподдерживаемый.

Выберите лучшее решение для вашего варианта использования.

Тип функции Котлина

  • API Kotlin: отлично
  • Kotlin Access: отлично
  • Java Access:
    • Автоматически сгенерированный тип параметра, такой как Function1 (не большая проблема для лямбды Java 8)
    • Подробный return Unit.INSTANCE; вместо пустого возврата.
class KotlinApi {
    fun demo(listener: (response: String) -> Unit) {
       listener("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo { success ->
        println(success)
    }
}


public void javaConsumer() {
    new KotlinApi().demo(s -> {
        System.out.println(s);
        return Unit.INSTANCE;
    });
}

Интерфейс Kotlin

  • API Kotlin: дополнительное определение интерфейса.
  • Kotlin Access: слишком многословно
  • Java Access: отлично
class KotlinApi {
    interface Listener {
        fun onResponse(response: String)
    }

    fun demo(listener: Listener) {
       listener.onResponse("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo(object : KotlinApi.Listener {
        override fun onResponse(response: String) {
            println(response)
        }
    })
}

//If Kotlin had supported SAM conversion for Kotlin interfaces. :(
//fun kotlinConsumer() {
//    KotlinApi().demo {
//        println(it)
//    }
//}

public void javaConsumer() {
    new KotlinApi().demo(s -> {
        System.out.println(s);
    });
}

Java-интерфейс

  • API Kotlin: смешанный Java-код.
  • Kotlin Access: немного многословно
  • Java Access: отлично
class KotlinApi {
    fun demo(listener: Listener) {
        listener.onResponse("response")
    }
}

public interface Listener {
    void onResponse(String response);
}

//Semi SAM conversion
fun kotlinConsumer() {
    KotlinApi().demo(Listener {
        println(it)
    })
}

public void javaConsumer() {
    new KotlinApi().demo(s -> {
        System.out.println(s);
    });
}

Несколько методов

  • API Kotlin: множественные реализации методов
  • Kotlin Access: Отлично, если используется правильный метод. Автозаполнение также предлагает подробный метод.
  • Доступ к Java: отлично. Автозаполнение не предлагает метод типа функции из-за аннотации JvmSynthetic
class KotlinApi {
    interface Listener {
        fun onResponse(response: String)
    }

    fun demo(listener: Listener) {
        demo {
            listener.onResponse(it)
        }
    }

    @JvmSynthetic //Prevents JVM to use this method
    fun demo(listener: (String) -> Unit) {
        listener("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo {
        println(it)
    }
}

public void javaConsumer() {
    new KotlinApi().demo(s -> {
        System.out.println(s);
    });
}

Java API

  • API Kotlin: API Kotlin отсутствует, весь код API - это Java
  • Доступ к котлину: идеальный
  • Доступ к Java: идеальный
public class JavaApi {
    public void demo(Listener listener) {
        listener.onResponse("response");
    }

    public interface Listener {
        void onResponse(String response);
    }

}

//Full SAM conversion
fun kotlinConsumer() {
    JavaApi().demo {
        println(it)
    }
}

public void javaConsumer() {
    new JavaApi().demo(s -> {
        System.out.println(s);
    });
}