Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Kotlin spread operator requires toTypedArray() when passing a primitive vararg argument?

I have an Android app written 100% in Kotlin.

In my unit test class I have 2 test observers, one observing integers, another - objects:

val conversationCountObserver: TestObserver<Int>
val conversationObserver: TestObserver<Conversation>

For each one I want to do some basic assertions, like check that there were no errors and check the values observed. I decide to write a generic method that can do a number of assertions on a list of elements in an RX TestObserver class, and 2 proxy methods to pass corresponding observer and vararg of values via Kotlin * spread operator:

private fun thenConversationsNotified(vararg conversations: Conversation) {
    thenTestObserverNotified(conversationObserver, *conversations)
}

private fun thenConversationCountNotified(vararg counts: Int) {
    thenTestObserverNotified(conversationCountObserver, *counts)
}

private fun <T>thenTestObserverNotified(observer: TestObserver<T>, vararg elements: T) {
    observer.assertNoErrors()
    elements.forEachIndexed { index, element ->
        observer.assertValueAt(index, element)
    }
}

thenConversationsNotified() method works just fine, however the thenConversationCountNotified() shows an error:

enter image description here

enter image description here

After reading Kotlin docs (http://kotlinlang.org/docs/reference/java-interop.html#java-arrays) I realized that vararg of primitive types actually creates a IntArray object rather than an Array, and this was done as a solution for Java interop, that treats varargs of primitives differently than objects. So I have to rewrite thenConversationCountNotified with .toTypedArray():

private fun thenConversationCountNotified(vararg counts: Int) {
    thenTestObserverNotified(conversationCountObserver, *counts.toTypedArray())
}

Why doesn't the spread operator work on a primitive array class in the same way as on an array of objects? Is there any way to remove the call to .toTypedArray() here?

P.S. Note that in the link above, they declare intArrayOf(1,2,3) that returns IntArray, and when passing it to the other method the spread operator works fine. So spread operator does work with IntArray class. The only difference between an example in Kotlin docs and my method is that I have 2 parameters in a method, one concrete and one vararg.

like image 621
Anton Cherkashyn Avatar asked Jul 30 '18 18:07

Anton Cherkashyn


1 Answers

When the Int type is used as a type argument (replaces T in your case), JVM primitives cannot be used, which is a JVM restriction (it has a single method signature with java.lang.Object in place of the type parameter, and so it cannot accept primitives).

The vararg complicates the case, but, under the hood, a function accepting vararg elements: T is the same as if it accepted Array<T>.

So you need to pass Array<Int> (array of boxed integers) rather than IntArray (array of primitives), and the .toTypedArray() function is the way to get the former.

like image 187
hotkey Avatar answered Oct 24 '22 20:10

hotkey