Issue
I created a RollingWindow class in order to have a fixed number of most recent data points stored in an array.
class RollingWindow<T> (private val length: Int) {
private val window = Array<Any?>(length) {null}
private var count = 0
fun push(t: T) {
if (length == 0)
return
window[currentIndex()] = t
count++
}
@Suppress("UNCHECKED_CAST")
fun toArray(): Array<T> {
if (length == 0)
return arrayOf<Any>() as Array<T>
val firstHalf = window
.copyOfRange(currentIndex(), window.size)
.filterNotNull()
.toTypedArray()
val secondHalf = window
.copyOfRange(0, currentIndex())
.filterNotNull()
.toTypedArray()
val arr = arrayOf(*firstHalf, *secondHalf) as Array<T>
print(arr.contentToString())
//this works fine but for some reason the class cast exception is thrown from the unit test
return arr
}
override fun toString() = toArray().contentToString()
private fun currentIndex() = count % length
}
I wrote some unit tests and am getting a ClassCastException
@Test
fun testRollingWindowNotFull() {
val doubleWindow = RollingWindow<Double>(5)
doubleWindow.push(2.5)
doubleWindow.push(6.8)
assertArrayEquals(arrayOf(2.5, 6.8), doubleWindow.toArray()) //this passes
val variableInWindow = RollingWindow<Double>(5)
variableInWindow.push(25.6)
variableInWindow.push(24.32)
val windowArray = variableInWindow.toArray() // ClassCastException only gets thrown here or whenever it's stored in a variable. If I use variableInWindow.toArray() inline it's fine, as shown in previous assertion
assertArrayEquals(arrayOf(25.6, 24.32), windowArray) // This never gets reached
}
While running the test I tried casting the Array<Any> into an Array<T>. Casting within the RollingWindow class works fine, no errors, but I specifically get an error in the Unit Test. Here's the StackTrace:
Sep 13, 2021 1:55:49 PM org.junit.platform.launcher.core.EngineDiscoveryOrchestrator lambda$logTestDescriptorExclusionReasons$7
INFO: 0 containers and 4 tests were Method or class mismatch
[Ljava.lang.Object;@7a8051ce[Ljava.lang.Object;@3ba12a08[Ljava.lang.Object;@725e196
class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Double; ([Ljava.lang.Object; and [Ljava.lang.Double; are in module java.base of loader 'bootstrap')
java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Double; ([Ljava.lang.Object; and [Ljava.lang.Double; are in module java.base of loader 'bootstrap')
at collections.RollingWindowTest.testRollingWindowNotFull(RollingWindowTest.kt:24)
Solution
Unlike all other generics in classes in Kotlin, the Array class and the Array class only has a reified type, and it is an invariant type. You cannot ever successfully cast an Array of one type to an Array of another type unless you are casting it to have a covariant or contravariant type.
I think the only reason the first part of your test passes is that there must be a compiler optimization that omits the reified cast inside the toArray()
function if the returned object is used as an Any
or Array<out Any>
. The Java-defined assertArrayEquals
function takes two Object[]
parameters which get mapped to either Array<Any>
or Array<out Any>
, and perhaps in this case it's doing the latter cast because it sees that nothing is put into the arrays in this function.
So the compiler as an optimization may be replacing your reified cast to Array<Double>
with a non-reified cast to Array<out Double>
, which is a safe cast.
You might want to consider using List or MutableList instead of Array to avoid having to deal with these problems.
Answered By - Tenfour04
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.