Andrej Petrović
Andrej Petrović

Reputation: 907

How do you write secondary constructors for a class with type parameters?

Suppose I have the following class, with a type parameter T (in this example it's bounded to help illustrate a later example, but the error persists when it's unbounded):

class GenericsTest<T : CharSequence>(private var cs: T)

Now suppose I want to add a secondary constructor to this class. How can I do this? My first (naive) attempt resulted in a compiler error:

class GenericsTest<T : CharSequence>(private var cs: T)
{
    // dummy exists to ensure the method signatures are different
    constructor(cs: String, dummy: Int) : this("a") 
}

IntelliJ underlines "a" with the message:

Type mismatch.
Required: T
Found: String

To me, String seems to be a perfectly valid T. I thought explicitly specifying the type parameter would help, but that doesn't seem to be allowed. Both of these attempts are incorrect syntax:

constructor(cs: String, dummy: Int) : this<String>("a")
constructor<U : String>(cs: U, dummy: Int) : this("a")

Since I suspect that there's a common approach to all of these scenarios, my main question is:

How do you write secondary constructors for a generic class in Kotlin? Or similarly, how do you delegate to the primary constructor when the constructor has type parameters?

Is this even possible? If not, one workaround might be to use a helper function to create the object using the primary constructor, but this wouldn't work for e.g. abstract classes.

The official documentation on generics doesn't discuss constructors.

Upvotes: 3

Views: 590

Answers (2)

ephemient
ephemient

Reputation: 204758

Another way to group the helper function: the companion object can be made invokable.

class GenericsTest<T : CharSequence>(private var cs: T) {
    companion object {
        operator fun invoke(cs: String, dummy: Int) = GenericsTest(cs)
    }
}

GenericsTest("a", 1)

It's not really a constructor but it looks like one. One benefit over a standalone function is that this works even if the called constructor is private.

Upvotes: 5

miensol
miensol

Reputation: 41638

As per documentation:

If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s).

The GenericsTest signature primary constructor states that it accepts any type which implements CharSequence. Since a secondary constructor class must call the primary constructor it also has to support any type which implements CharSequence. Whichever constructor we pick to instantiate GenericsTest we should be able to do so for any T: CharSequence not only String, hence the compiler complain about Type mismatch.

You already mentioned a work around involving a helper function e.g.:

fun GenericsTest(cs: String) = GenericsTest<String>(cs)
fun GenericsTest(cs: CharBuffer, other:Int) = GenericsTest(cs)

As you mentioned helper functions will not work with abstract classes. However, abstract class allows for type specialisation in derivatives e.g.:

class StringsTest<T : String>(s: T) : GenericsTest<T>(s)

It is hardly ever possible or desirable (from design perspective, open closed principle) for an abstract base class to declare something that would be directly available only from derived class that specialises generic type.

Upvotes: 0

Related Questions