I am reading and trying to understand Kotlin type projections, sometimes I come up with confusing things like this:
For contravariant type parameters such as
Consumer<in T>
, a star projection is equivalent to<in Nothing>
. In effect, you can’t call any methods that haveT
in the signature on such a star projection. If the type parameter is contravariant, it acts only as a consumer, and, as we discussed earlier, you don’t know exactly what it can consume. Therefore, you can’t give it anything to consume.You can use the star projection syntax when the information about type arguments isn’t important: you don’t use any methods that refer to the type parameter in the signature, or you only read the data and you don’t care about its specific type. For instance, you can implement the
printFirst
function takingList<*>
as a parameter.
What does it mean to contravariant type to have a star projection and how does it come up to
This is also explained in the Kotlin documentation:
For
Foo<in T>
, whereT
is a contravariant type parameter,Foo<*>
is equivalent toFoo<in Nothing>
. It means there is nothing you can write toFoo<*>
in a safe way whenT
is unknown.
We have a class Foo<T>
with contravariant T
(declaration-site), i.e. Foo
only works as a consumer of T
:
class Foo<in T> {
fun accept(t: T) {
println(t)
}
}
We can use this type in simple generic functions as follows:
fun <F> usingFoo(con: Foo<F>, t: F) {
con.accept(t)
}
(used F
in order to distinguish from class type parameter T
)
This works fine since we use Foo
as intended: As a consumer of T
s.
Now, your quote simply says that having a parameter of type con: Foo<*>
instead of con: Foo<T>
would have the following effect: "you can't call any methods that have T
in the signature".
Thus, the following fails:
fun <F> usingFoo(con: Foo<*>, t: F) {
con.accept(t) //not allowed!!
}
It's not safe to call accept
with an instance of F
because we don't know the type of Foo
(denoted by star projection).
Star Projection: Sometimes you want to say that you know nothing about the type argument, but still want to use it in a safe way. The safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type would be a subtype of that projection.