Pass a Java class reference as a function parameter in Kotlin - kotlin

I want to pass a reference to a class to a function:
getCallData(ServiceCallBase::class.java)
fun getCallData(msg: Message, t: KClass<Any>): String {
return gson.fromJson((msg.obj as Bundle).getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), t)
}
The t parameter is not correct here. How do I correctly define the t parameter? The closest I can get is:
private inline fun <reified T> getCallData(msg: Message): String {
return gson.fromJson((msg.obj as Bundle).getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), T)
}
But here T is not an expression.

try this
inline fun <reified T> getCallData(msg: Message): String {
return gson.fromJson((msg.obj as Bundle)
.getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), T::class.java)
}
and use it like this :
getCallData<ServiceCallBase>(Message())

The problem is that ServiceCallBase is not a subtype of Any. You should change the signature to:
fun getCallData(t: KClass<*>) { ... }
Then, to call this function just use:
getCallData(ServiceCallBase::class)
But, to do that in more kotlin way use the second snipped you pasted:
inline fun <reified T> getCallData() { /* use T::class property here */ }
and call the function like this:
getCallData<ServiceCallBase>()

Related

How to get the class through reflection and then pass in the generic?

There is such a function
public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
repeatInterval: Duration
): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
}
requires passing in a generic. How do I set this generic through reflection? The following is the pseudocode.
val work = Class.forName("com.demo.work")
PeriodicWorkRequestBuilder<work::class>(1, TimeUnit.HOURS).build()

How does Kotlin choose the generic overloaded function to call?

I'm trying to write serialization functions to be able to serialize any vector (=ArrayList) in Kotlin, as well as primitive types and classes extending a Serialize class having a toBinary() function.
I also have a custom WriteDataStream class (code below) to serialize fields with the right format, endianness, etc.
I'm new to Kotlin but have experience in C++. In C++, I used templates and template specialization to solve that problem easily, but with Kotlin I've been struggling for a few days, without success.
I have a custom vector class MyVector which extends ArrayList and adds a maximum size. I want to serialize it with any generic type T, including inner vectors like a MyVector<MyVector<MyClass>>.
My WriteDataStream contains the following:
inline fun <reified T> write(vector: MyVector<T>) {
this.writeSize(vector.size.toULong(), vector.MAX_SIZE)
for (element in vector) {
write<T>(element)
}
}
inline fun <reified T: Serialize> write(value: T) {
writeSerialize(value as Serialize)
}
inline fun <reified T> write(value: T) {
when (T::class) {
UByte::class -> {
writeUInt8(value as UByte)
}
UShort::class -> {
writeUInt16(value as UShort)
}
UInt::class -> {
writeUInt32(value as UInt)
}
ULong::class -> {
writeUInt64(value as ULong)
}
Byte::class -> {
writeInt8(value as Byte)
}
Short::class -> {
writeInt16(value as Short)
}
Int::class -> {
writeInt32(value as Int)
}
Long::class -> {
writeInt64(value as Long)
}
Boolean::class -> {
writeBoolean(value as Boolean)
}
Float::class -> {
writeFloat(value as Float)
}
Double::class -> {
writeDouble(value as Double)
}
else -> {
error("Default serialization:" + T::class.qualifiedName)
}
}
}
All the underlying functions writeXXX() are tested and work fine. However, when tying to serialize a MyVector with a class extending Serialize, I fall in the "Default serialization" case:
#Test
fun writeVectorOfStructure() {
class TestStructure: Serialize() {
override fun toBinary(stream: WriteDataStream) {
stream.writeUInt32(17U)
stream.writeUInt8(3U)
stream.writeDouble(555.555)
}
}
val value = MyVector<TestStructure>(MAX_SIZE, arrayListOf(TestStructure(), TestStructure()))
writeStream.write(value)
val bytes: UByteArray = writeStream.byteArray()
Assert.assertEquals(bytes.size, 28) // = 2 (for size) + 2*(4+1+8) = 28 bytes
}
So my question is: Why does Kotlin not use the function
inline fun <reified T: Serialize> write(value: T)
when it serializes an element of the vector (write<T>(element)) with generic T = Serialize, but instead uses the more generic one?
inline fun <reified T> write(value: T)
In C++, the compiler always uses the most fitted function.
Is there a way to overcome this limitation in Kotlin?
I have tried with and without reified types, I have tried a non-generic function as well: inline fun write(value: Serialize), but without success. The only thing that seems to work was to add a case for classes "instance of" Serialize in the fully-generic inline fun <reified T> write(value: T), but this is not really a nice solution.
Thanks you !
JVM and its bad implementation of generics
You are a victim of Java's implementation of generics, more specifically the erasure. C++ uses what is called type expansion to implement generics, meaning if you declare MyType<A> and MyType<B>, at runtime you will have two different types, language runtime will create them for you.
On the other hand what Java does is called the erasure implementation. so in java world when you say List<String> and List<Integer>, at runtime they are both identical types, that is system doesn't have any information to make a distinction between both of these, they are List type (Note that there is no type parameter, it got removed during the compilation).
Lets decompile your code and see for yourself
I wrote following code in kotlin, it matches yours
class SomeType {
inline fun <reified T: String> write(value: T) {}
inline fun <reified T> write(value: T) {}
inline fun <reified T: Any> write(vector: List<T>) {
for (element in vector) {
write(element)
}
}
}
And when I decompile the code it gives me following. (Only relevant code included)
public final class SomeType {
public final void write(#NotNull String value) {}
public final void write(Object value) {}
public final void write(#NotNull List vector) {
boolean var6;
for(Iterator var4 = vector.iterator(); var4.hasNext(); var6 = false) {
Object element = var4.next();
}
}
}
Look at the write(vector: List<T>) method's decompilation. parameter type got changed to List which is a Raw Type and its components are objects.
And for an Object best method match is public final void write(Object value) and not the one with String or in your case Serialize.

Kotlin generic type class as function parameter

How to pass parametr to function and then use it as generic type ?
private fun createRequest(data: Data, class: Class<out UploaderTask>): OneTimeWorkRequest {
return OneTimeWorkRequestBuilder<class>().build()
}
I found answer
private inline fun <reified T : UploaderTask>createRequest(data: Data): OneTimeWorkRequest {
return OneTimeWorkRequestBuilder<T>()
.setInputData(data)
.build()
}

Type inference only works for extension function

The following code works fine and the call to the foo.get() extension function returns the correct type BarImpl.
open class Bar
class BarImpl: Bar()
class Foo<T : Bar>
inline fun <reified T : Bar> Foo<T>.get(): T {
return SomeMap(this).get(T::class)
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
But when I try to move Foo<T>.get() into the class the type inference fails
class Foo<T : Bar> {
inline fun <reified T : Bar> get(): T {
return SomeMap(this).get(T::class)
}
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
error: type inference failed: Not enough information to infer parameter T in inline fun get(): T
Please specify it explicitly.
val vm = foo.get()
^
How can I move the function into the class?
The extension function returns the result of the Foo type parameter. So the result type can be inferred from the receiver type.
And the member function result type has nothing in common with Foo type parameter except the name, which means nothing for a compiler. You can see that T in method and T in class are different types by writing and compiling the following code:
Foo<BarImpl>().get<BarImpl2>()
If you want to make get to be a member function which returns the result of Foo type parameter, you should remove type parameter from function and inject class instance via the constructor:
class Foo<T : Bar>(private val clazz: KClass<T>) {
fun get(): T {
return SomeMap(this).get(clazz)
}
companion object {
inline operator fun <reified T : Bar> invoke() = Foo(T::class)
}
}

why the translated kotlin code complains about a Array<BaseData>? to be a Array<out BaseData>

Having a java class, using androidStudio to translate to kotlin.
Got a error and not sure how to correctly translate it.
The java code:
public class BaseDataImpl extends BaseData {
private final BaseData[] translators;
public BaseDataImpl(final BaseData... translators) {
this.translators = cloneArray(translators);
}
public static <T> T[] cloneArray(final T[] array) {
if (array == null) {
return null;
}
return array.clone();
}
}
after the code translation, got error: required Array<BaseData>?, found Array<out BaseData>, but the translators in the cloneArray<BaseData>(translators) call is defined as val translators: Array<BaseData>?,
anyone could help to explain?
class BaseDataImpl(vararg translators: BaseData) : BaseData() {
private val translators: Array<BaseData>?
init {
this.translators = cloneArray<BaseData>(translators) //<=== error: required Array<BaseData>?, found Array<out BaseData>
}
companion object {
fun <T> cloneArray(array: Array<T>?): Array<T>? {
return array?.clone()
}
}
}
It is written in the Kotlin function reference regarding varargs:
Inside a function a vararg-parameter of type T is visible as an array of T, i.e. the ts variable in the example above has type Array<out T>.
where the referenced function was:
function <T> asList(vararg ts: T): List<T>
So in your case you actually pass an Array<out BaseData> but you only accept an array of type Array<T>? (in your case Array<BaseData>). Either you adapt all of the types to Array<out T> (which basically is similar as saying List<? extends BaseData> in Java) or you take care that you are only dealing with Ts instead, e.g. with:
inline fun <reified T> cloneArray(array: Array<out T>?): Array<T>? {
return array?.clone()?.map { it }?.toTypedArray()
}
But look up the documentation regarding this: Kotlin generics reference - type projections. You probably can accomplish this even easier.