Generics: * results in "Nothing"

I’m annoyed that Generic methods don’t even accept “Any” when declared as Star. I don’t want a type parameter, I don’t care what type parameter there is, but even with Star generics, it will still always be subclassing Any, so why won’t you let me use it as Any?

And why can’t I hand something that requires a star something with a star, and instead it is suddenly a nothing which is not compatible with Star? It makes less and less sense the more I write…

image

It gets even weirder. Why does this not work?

image

Because you can’t insert for example a Condition<Any?> in a ObservableList<Condition<Any>>.

okay, but let’s review the case before with a usable example

class Condition<T>

abstract class Type<T>  {
	abstract val conditions: ObservableList<Condition<T>>
}

class Test {
	lateinit var myList: ObservableList<Condition<*>>
	fun doSomething(type: Type<*>) {
		myList = type.conditions
	}
}

image

WHY? In Java I would’ve used raw types and it would’ve worked easily, but apparently Kotlin does not let me do that…

Which version of Kotlin are you using? I can compile and run the following code on https://try.kotlinlang.org/ with v1.2.10:

class Condition<T>

abstract class Type<T>  {
	abstract val conditions: List<Condition<T>>
}

class Test {
	lateinit var myList: List<Condition<*>>
	fun doSomething(type: Type<*>) {
		myList = type.conditions
	}
}

fun main(args: Array<String>) {
    val test = Test()
    test.doSomething(object : Type<String>() {
        override val conditions = emptyList<Condition<String>>()
    })
    println(test.myList)
}

oh apparently the issue here seems to only appear when not using standard Collections. I use 1.2.10 too and your example works, but it doesn’t compile when using ObservableList or even Condition itself like this:

class Condition<T>

abstract class Type<T>  {
	abstract val conditions: Condition<Condition<T>>
}

class Test {
	lateinit var test: Condition<Condition<*>>
	fun doSomething(type: Type<*>) {
		test = type.conditions
	}
}

Condition is a generic class which is invariant on its type parameter. It means that even if A is a supertype of B then Condition<A> is neither supertype nor subtype of Condition<B>. I suppose ObservableList is some generic class/interface written in Java, so it’s invariant on its type parameter too (because all generic classes are invariant in Java).

In your example, Condition<*> is equivalent to Condition<out Any?>, so Condition<T> is definitely its subtype. But since ObservableList is invariant, ObservableList<Condition<T>> is not a subtype of ObservableList<Condition<*>> and thus cannot be assigned to myList.

I suppose you cannot alter ObservableList declaration to add declaration site variance, so you may want to specify use site variance instead on the myList declaration:

var myList: ObservableList<out Condition<*>>

I recommend reading the section about generic variance to better grasp these concepts.

I’ve read this section, but grasping it takes longer than jst that read. I do understand what you mean, but in my real code there is one important difference: I can’t control the variable I assign to, I’m interacting with Java code, so i can’t declare any variance.

That’s where raw types come into play. They are basically a way of telling the compiler: Hey, let me do the type erasure for you, since my code is sure to work whatever happens. But I can’t do that in Kotlin. I know that raw types should definitely not be commonly used, but there are situations where they would be extremely handy and make the whole situation much simpler.

If you control Java code where the expected type is declared, you can specify use site variance there:
ObservableList<? extends Condition<?>> myList

Otherwise you can use an unchecked cast to coerce the value to the required type:
SomeJavaClass.myList = type.conditions as ObservableList<Condition<*>>

Kotlin can do type casts on generic types that are “invalid”. It will give you a big fat warning, but it will obey. If it breaks you get to keep the pieces :wink: .

1 Like