29

I have a generically typed class Builder<T> that takes a constructor argument Class<T> so I can keep the type around. This is a class that I use a lot in java code so I don't want to change the signature. When I try to use the constructor like this:

Builder<List<Number>>(List<Number>::class)

I get an error: "Only classes are allowed on the left hand side of a class literal"

Any way to resolve this? I can't change the constructor for Builder, too many java classes rely upon it.

I understand the whole type erasure issue, I really just want to make the compiler happy.

Ilya
  • 21,871
  • 8
  • 73
  • 92
Jonathan Leitschuh
  • 822
  • 1
  • 8
  • 29

2 Answers2

26

Due to generic type erasure List class has a single implementation for all its generic instantiations. You can only get a class corresponding to List<*> type, and thus create only Builder<List<*>>.

That builder instance is suitable for building a list of something. And again due to type erasure what that something is you can decide by yourself with the help of unchecked casts:

Builder(List::class.java) as Builder<List<Number>>
Builder(List::class.java as Class<List<Number>>)

Another approach is to create inline reified helper function:

inline fun <reified T : Any> Builder() = Builder(T::class.java)

and use it the following way:

Builder<List<Number>>()
Ilya
  • 21,871
  • 8
  • 73
  • 92
  • reified is the way – saarrrr May 05 '16 at 02:35
  • 2
    This won't retain actual type parameters: `T::class.java` will represent `List<*>`, not `List`, but there's a solution that will do it for reified generics -- super class tokens. – hotkey May 10 '16 at 08:58
  • @hotkey I assume `Builder` class doesn't require actual type parameters to be retained, because it takes a constructor argument `Class` and not `Type` as it's stated in the question. – Ilya May 10 '16 at 14:48
  • @Ilya what is the Builder class? Can you provide the package name? – nutella_eater Oct 30 '17 at 11:19
  • @alexeypolusov it's the class from the question. – Ilya Oct 30 '17 at 12:39
9

The solution is to use reified generics in couple with super class tokens.

Please refer to this question for the method explained. Constructors in Kotlin don't support reified generics, but you can use TypeReference described there to write a builder factory function which will retain actual generic parameters at runtime:

inline fun <reified T: Any> builder(): Builder<T> {
    val type = object : TypeReference<T>() {}.type
    return Builder(type)
}

Then inside Builder you can check if type is ParameterizedType, and if it is, type.actualTypeArguments will contain the actual generic parameters.

For example, builder<List<Number>>() will retain the information about Number at runtime.

The limitation of this approach is that you cannot use non-reified generic as a reified type parameter because the type must be known at compile-time.

raoulsson
  • 14,978
  • 11
  • 44
  • 68
hotkey
  • 140,743
  • 39
  • 371
  • 326