Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Have a Non-Generic class take a generic argument in constructor

I would like to have a non-generic class in kotlin that uses generics in its constructor in order to specify that a parameter. However, I can't figure out how to do this, and the Java-to-Kotlin converter for Intellij breaks.

My java class looks like this

public class Test {    
    interface I1 { }    
    interface I2 { }

    private final I1 mI1;
    private final I2 mI2;

    public <T extends I1 & I2> Test(T host) {
        mI1 = host;
        mI2 = host;
    }
}

The converter's output looks like this.

class Test(host: T) where T: I1, T: I2 {
    internal interface I1
    internal interface I2

    private val mI1: I1
    private val mI2: I2

    init {
        mI1 = host
        mI2 = host
    }
}

I would like to do this because in Android development it is useful to be able to specify a constructor parameter that looks like <Host extends Context & CustomCallbackInterface>

like image 472
The LaPoubelle Avatar asked Feb 04 '16 19:02

The LaPoubelle


2 Answers

Looking at Kotlin's grammar, it seems that this is not possible at the moment. For primary constructors, the type parameters denote class type parameters:

class (used by memberDeclaration, declaration, toplevelObject)
  : modifiers ("class" | "interface") SimpleName
      typeParameters?
      primaryConstructor?
      (":" annotations delegationSpecifier{","})?
      typeConstraints
      (classBody? | enumClassBody)
  ;

For secondary constructors, there are no type parameters possible:

secondaryConstructor (used by memberDeclaration)
  : modifiers "constructor" valueParameters (":" constructorDelegationCall)? block
  ;

However, a constructor is just a special function. If we instead don't use the constructor, but a function of our own, we can come up with the following:

class Test {

    interface I1

    interface I2

    private val mI1: I1
    private val mI2: I2

    internal constructor(host: I1, host2: I2) {
        mI1 = host
        mI2 = host2
    }

    companion object {

        fun <T> create(host: T): Test where T : Test.I1, T : Test.I2 {
            return Test(host, host)
        }

    }
}

fun <T> test(host: T): Test where T : Test.I1, T : Test.I2 {
    return Test(host, host)
}

We can now call Test.create(host) or test(host) to create an instance.

like image 171
nhaarman Avatar answered Nov 06 '22 02:11

nhaarman


Expanding on nhaarman's answer you can use operator overloading to make Test's companion object implement the invoke operator:

class Test {

    interface I1

    interface I2

    private val mI1: I1
    private val mI2: I2

    private constructor(i1: I1, i2: I2) {
        mI1 = i1
        mI2 = i2
    }

    companion object {
        operator fun <T> invoke(host: T): Test where T : I1, T : I2 {
            return Test(host, host)
        }
    }
}

You can then create a Test object using the call syntax you want:

Test(object : Test.I1, Test.I2 {})
like image 38
mfulton26 Avatar answered Nov 06 '22 01:11

mfulton26