Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overload constructors in kotlin differs in lambda return type

I have two constructors which are different only in their lambda return type. Is there any option how to overload them? I was trying to use JvmOverloads annotation, but it didn't work.

constructor(db : Database, handler: ( transaction: Transaction) -> Unit) : this(db, Handler<Transaction>( {handler.invoke(it)}))

@JvmOverloads
constructor(db : Database, handler: ( transaction: Transaction) -> Any) : this(db, Handler<Transaction>( {handler.invoke(it)}))
like image 656
rusna Avatar asked Jan 05 '23 08:01

rusna


2 Answers

You cannot define constructors with signatures that differ only in generic parameters (in your case, it's generic parameters for Function1<in P1, out R>) because the signatures would clash after generics erasure.

However, in your case Unit is subtype of Any, and since Function<in P1, out R> is covariant on R, you can pass a function that returns Unit to the second constructor, so just remove the first one.

Simplified example:

class C(val action: (Int) -> Any)

fun main(args: Array<String>) {
    val f: (Int) -> Unit = { println(it) }
    C(f)
}

For more complicated cases, consider changing to factory functions: unlike constructors, you can annotate them with @JvmName to avoid the signatures clash:

@JvmName("createCUnit")
fun createC(f: (Int) -> Unit) = C(f)

fun createC(f: (Int) -> Any) = C(f)
like image 144
hotkey Avatar answered Jan 13 '23 14:01

hotkey


When targeting JVM backend, all Kotlin classes are compiled to JVM bytecode. The problem with java's bytecode is type erasure. This means that all info about generics is removed (it's Java's issue not Kotlin's).

Declaring functional type (transaction: Transaction) -> Unit is eqivalent to using this type: Function1<Transaction, Unit>. However, for JVM bytecode both Function1<Transaction, Unit> and Function1<Transaction, Any> are the same.

This means that both your constructors have the same signature in JVM world.

You can "simulate" constructors using companion object

class MyClass {
    constructor(db: Database, h: Handler<Transaction>)

    companion object {
        operator fun invoke(db: Database, handler: (transaction: Transaction) -> Unit) = MyClass(db, Handler<Transaction>({ handler.invoke(it) }))

        @JvmName("alternative_constructor")
        operator fun invoke(db: Database, handler: (transaction:     Transaction) -> Any) = MyClass(db, Handler<Transaction>({ handler.invoke(it) }))
    }
}

And usage looks like this:

fun main(args: Array<String>) {
    val db = Database()

    MyClass(db, Handler {  }) //real constructor
    MyClass(db){ } //version from companion object
}
like image 26
rafal Avatar answered Jan 13 '23 14:01

rafal