Adam Arold Adam Arold - 9 months ago 40
Java Question

How can I store reified type data in instance fields in Kotlin?

I'm currently writing a DSL for a library and I'd like to supply type metadata using reified type parameters like this:

val config = Config.create()
.consumerFor<MyType>{
// consume
}


My problem is that i can only use the
reified
keyword in
inline
functions and in an
inline
function I can't use instance fields like this:

inline fun <reified T> consumerFor(consumer: (T) -> Unit) {
consumers.put(T::class.java, consumer)
return this
}


because I get an error:


Public-API inline function cannot access non-public-API 'private final val consumers...


It seems so far that I can't use reified type parameters where they would be most useful. Is there a workaround for this?

Answer Source

Public inline functions cannot use private declarations directly, because, when inlined at the call sites outside the class, the usages will have incorrect access level (on JVM, a private member of a class cannot be accessed from outside).

What you can do is use the internal visibility in Kotlin: on JVM, the members with this visibility modifier will be compiled into public members with their names mangled (therefore still visible but not easy-to-call from Java), and the Kotlin compiler will at least control the usages from the Kotlin code.

There are a few ways to access an internal member from within a public inline fun, see this question: (link)

In your particular case, I would prefer doing it with @PublishedApi:

private val consumers = mutableMapOf<Class<*>, Any>()

@PublishedApi
internal fun <T> putConsumer(clazz: Class<out T>, consumer: (T) -> Unit) {
    consumers.put(clazz, consumer)
}

inline fun <reified T> consumerFor(noinline consumer: (T) -> Unit): C {
    putConsumer(T::class.java, consumer)
    return this
}

Or, if you don't mind exposing consumers as a public property with a mangled name (e.g. getConsumers$production_sources_for_module_myModule_main()), then you can do it as follows:

@PublishedApi
internal val consumers = mutableMapOf<Class<*>, Any>()

inline fun <reified T> consumerFor(noinline consumer: (T) -> Unit): C {
    consumers.put(T::class.java, consumer)
    return this
}