Home > Back-end >  Reflection and Generics in Kotlin
Reflection and Generics in Kotlin

Time:11-12

I've written myself into a corner where I want an instance of Class<Foo<Bar>>. While there's no apparent reason that this shouldn't be valid, there seems to be no way to create one. Foo<Bar>::class.java is a syntax error, and Kotlin does not provide a public constructor for Class.

The code I'm writing is an abstraction layer over gson. Below is an overly-simplified example:

class Boxed<T : Any> (val value: T)

class BaseParser<U : Any> (
    private val clazz: Class<U>
) {
    //This works for 98% of cases
    open fun parse(s: String): U {
        return gson.fromJson(s, clazz)
    }
    
    //Presume that clazz is required for other omitted functions
}

//Typical subclass:
class FooParser : BaseParser<Foo>(Foo::class.java)

// Edge Case 
class BarParser : BaseParser<Boxed<Bar>>(Boxed<Bar>::class.java) {
    override fun parse(s: String): Boxed<Bar> {
        return Boxed(gson.fromJson(s, Bar::class.java))
    }
}
// not valid: "Only classes are allowed on the left hand side of a class literal"

In my production code, there are already dozens of subclasses that inherit from the base class, and many that override the "parse" function Ideally I'd like a solution that doesn't require refactoring the existing subclasses.

CodePudding user response:

Actually, there is a reason this is impossible. Class (or Kotlin's KClass) can't hold parameterized types. They can hold e.g. List, but they can't List<String>. To store Foo<Bar> you need Type (or Kotlin's KType) and specifically ParameterizedType. These classes are somewhat more complicated to use and harder to acquire than simple Class.

The easiest way to acquire Type in Kotlin is by using its typeOf() utility:

typeOf<Foo<Bar>>().javaType

Gson supports both Class and Type, so you should be able to use it instead.

CodePudding user response:

The closest you'll get is Boxed::class.java. This is not a language restriction but a JVM restriction. JVM has type erasure, so no generic types exist after compilation (thats also one of the reasons generics cant be primitives, as they need to be reference types to behave). Does it work with the raw Boxed type class?

CodePudding user response:

For this case, it looks like

BaseParser<Boxed<Bar>>(Boxed::class.java as Class<Boxed<Bar>>)

could work (that is, it will both type-check and succeed at runtime). But it depends on what exactly happens in the "Presume that clazz is required for other omitted functions" part. And obviously it doesn't allow actually distinguishing Boxed<Foo> and Boxed<Bar> classes.

I'd also consider broot's approach if possible, maybe by making BaseParser and new

class TypeBaseParser<U : Any>(private val tpe: Type)

extend a common abstract class/interface.

  • Related