javakotlingenericstypestype-projection

Kotlin star projection on contravariant types


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 have T 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 taking List<*> as a parameter.

What does it mean to contravariant type to have a star projection and how does it come up to


Solution

  • This is also explained in the Kotlin documentation:

    For Foo<in T>, where T is a contravariant type parameter, Foo<*> is equivalent to Foo<in Nothing>. It means there is nothing you can write to Foo<*> in a safe way when T is unknown.

    Example

    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 Ts.

    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.