4

I'm trying to implement checked Stack<E> based on java array in Kotlin. But I have a problem with using KClass with my generic parameter type <E> which allows null values.

Java Generic types are not available in runtime, but array types are available. I want to use this feature so that there is built-in type checking in runtime.

More details about the checked/unchecked can be found here https://stackoverflow.com/a/530289/10713249

interface Stack<E> {
    fun push(elem: E)
    fun pop(): E
}
class CheckedStack<E>(elementType: Class<E>, size: Int) : Stack<E> {

    companion object {
        inline fun <reified E> create(size: Int): CheckedStack<E> {
            //**compile error here**
            return CheckedStack(E::class.javaObjectType, size)
        }
    }

    @Suppress("UNCHECKED_CAST")
    private val array: Array<E?> = java.lang.reflect.Array.newInstance(elementType, size) as Array<E?>
    private var index: Int = -1

    override fun push(elem: E) {
        check(index < array.size - 1)
        array[++index] = elem
    }

    override fun pop(): E {
        check(index >= 0);
        @Suppress("UNCHECKED_CAST")
        return array[index--] as E
    }
}

I expect that this code will work like this:

fun main() {
    val intStack = CheckedStack.create<Int>(12) // Stack must store only Integer.class values
    intStack.push(1); //[1]
    intStack.push(2); //[1, 2]

    val stackOfAny: Stack<Any?> = intStack as Stack<Any?>;
    stackOfAny.push("str") // There should be a runtime error
}

But I have the compile error

Error:(39, 42) Kotlin: Type parameter bound for T in val <T : Any> KClass<T>.javaObjectType: Class<T>
 is not satisfied: inferred type E is not a subtype of Any

In order to fix it, I need to bound the type parameter <E : Any> but I need the stack to be able to work with nullable values <T : Any?>. How to fix it?

Why is KClass declared as KClass<T : Any> not KClass<T : Any?>?

UPD: It works if use E::class.java instead E::class.javaObjectType Because the property val <T> KClass<T>.java: Class<T> has type param <T> with annotation @Suppress("UPPER_BOUND_VIOLATED").

But the property val <T : Any> KClass<T>.javaObjectType: Class<T> has type <T : Any>.

In my case, Kotlin compiles Int to Integer.class rather than int (in my case). But I'm not sure that it will always work as well.

Community
  • 1
  • 1

1 Answers1

5

Nullable types are not classes on their own, so they don't have class objects. That's why the KClass's type parameter has an Any upper bound.

You can call ::class.java on a nullable reified type, but it will be evaluated to the same class object as the same call on the corresponding non-null type. So if you replace E::class.javaObjectType with E::class.java, type of elements will be checked at runtime, but no null checks will be done.

If you need null checks, you can add them by yourself. I also suggest moving the array creation to a factory method. Here is how you can do it:

class CheckedStack<E>(private val array: Array<E?>, private val isNullable: Boolean) : Stack<E> {

    companion object {
        // This method invocation looks like constructor invocation
        inline operator fun <reified E> invoke(size: Int): CheckedStack<E> {
            return CheckedStack(arrayOfNulls(size), null is E)
        }
    }

    private var index: Int = -1

    override fun push(elem: E) {
        if (!isNullable) elem!!
        check(index < array.size - 1)
        array[++index] = elem
    }

    override fun pop(): E {
        check(index >= 0)
        @Suppress("UNCHECKED_CAST")
        return array[index--] as E
    }
}
Bananon
  • 2,034
  • 1
  • 7
  • 20
  • Thanks. Indeed, it does not make sense to have a KClass which is parameterized by a nullable type argument. `Type=Class(with type arguments) + nullability`. But I actually think this is a bug, that the line `E::class.javaObjectType` produces the error. Operator `::` should return KClass. A type can be nullable or not nullable, but it always has a class – Andrey Loginov Oct 06 '19 at 21:29
  • Interesting idea to check for nullable at runtime. Full safety) – Andrey Loginov Oct 06 '19 at 21:40
  • Cool! By using the Kotlin's inline function, the call of `arrayOfNulls` is substituted by array creation with the desired type. Verry pretty) Thanks! – Andrey Loginov Oct 06 '19 at 22:03