3

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.

1
  • This is quite similar to a question i had recently: stackoverflow.com/questions/47809729/… Yes, the answer is a helper function. Remember that a constructor is not a factory function, it is void-returning code and just one part of the initialization procedure. Thus it doesn't make sense for it to specify constraints on the type that's already instantiated by the time it runs. Commented Jan 12, 2018 at 7:52

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Use factory method is the answer.
It's a shame that Kotlin does not support adding constructors trough extensions and with type constraints. This is the most elegant solution so far and it works like a charm.
0

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.

2 Comments

I see what you're saying. I could call the constructor using GenericsTest<CharBuffer> but it wouldn't work since the String it's using can't be assigned to a CharBuffer, right?
@andrej88 Yes, you could not call GenericTes<CharBuffer>("input", 1) since String is not subtype of CharBuffer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.