Difference between "*" and "Any" in Kotlin generics

It may be helpful to think of the star projection as a way to represent not just any type, but some fixed type which you don't know what is exactly.

For example, the type MutableList<*> represents the list of something (you don't know what exactly). So if you try to add something to this list, you won't succeed. It may be a list of Strings, or a list of Ints, or a list of something else. The compiler won't allow to put any object in this list at all because it cannot verify that the list accepts objects of this type. However, if you attempt to get an element out of such list, you'll surely get an object of type Any?, because all objects in Kotlin inherit from Any.

From asco comment below:

Additionally List<*> can contain objects of any type, but only that type, so it can contain Strings (but only Strings), while List<Any> can contain Strings and Integers and whatnot, all in the same list.


The key to understanding the star projection(*) is to properly understand the other two type projections in and out first. After that, the star projection becomes self-explanatory.


Understanding the problem

Let's say that you have this generic class Crate that you intend to use for storing fruits. This class is invariant in T. This means, this class can consume as well as produce T (fruits). In other words, this class has functions that take T as argument(consume) as well as return T(produce). The size() function is T-independent, it neither takes T nor does it return T:

class Crate<T> {
    private val items = mutableListOf<T>()
    fun produce(): T = items.last()
    fun consume(item: T) = items.add(item)
    fun size(): Int = items.size
}

But what if you want to use this already existing class just as a producer(out T) or just as a consumer(in T) or not want to use T but just its T-independent functions like size()? Without worrying about the accidental use of the unwanted functionality?

The solution is, we project the types by using variance modifiers out, in and * at the use-site. Use-site simply means wherever we use the Crate class.


out projection is a producer of T

By projecting the Crate as out, you are telling the compiler: "give me an error when I accidentally use the Crate class as a consumer of T because I just want to safely use that class as a producer T":

fun useAsProducer(producer: Crate<out Fruit>) {

    // T is known to be out Fruit, so produces Fruit and its subtypes.
    val fruit = producer.produce()           // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK
    
    // Consumer not allowed because you don't want to accidentally add
    // oranges, if this is a Crate<Apple>
    producer.consume(Orange())               // Error             
}

in projection is a consumer of T

By projecting the Crate as in, you are telling the compiler: "give me an error when I accidentally use the Crate class as a producer of T because I just want to safely use that class as a consumer of T":

fun useAsConsumer(consumer: Crate<in Orange>) {

    // Produces Any?, no guarantee of Orange because this could
    // be a Crate<Fruit> with apples in it.
    val anyNullable = consumer.produce()     // Not useful
    
    // Not safe to call functions of Orange on the produced items.
    anyNullable.getVitaminC()                // Error

    // T is known to be in Orange, so consumes Orange and its subtypes.
    consumer.consume(MandarinOrange())       // OK
}

Star projection is no producer, no consumer of T

By projecting the Crate as *, you are telling the compiler: "give me an error when I accidentally use the Crate class as a producer or consumer of T because I don't want to use the functions or properties that consume and produce T. I just want to safely use the T-independent functions and properties like size()":

fun useAsStar(star: Crate<*>) {

    // T is unknown, so the star produces the default supertype Any?.
    val anyNullable = star.produce()         // Not useful

    // T is unknown, cannot access its properties and functions.
    anyNullable.getColor()                   // Error

    // Cannot consume because you don't know the type of Crate.
    star.consume(Fruit())                    // Error

    // Only use the T-independent functions and properties.
    star.size()                              // OK
}

Any is not a projection

When you say Crate<Any>, you are not projecting, you are simply using the original invariant class Crate<T> as it is, which can produce as well as consume Any:

fun useAsAny(any: Crate<Any>) {

    // T is known to be Any. So, an invariant produces Any.
    val anyNonNull = any.produce()           // OK

    // T is known to be Any. So, an invariant consumes Any.
    any.consume(Fruit())                     // OK

    // Can use the T-independent functions and properties, of course.
    any.size()                               // OK
}

The same is true for Crate<Apple> or any other similar type without a variance modifier in, out, or *, it will consume and produce that type (Apple in that case). It's not a projection. This explains the difference between SomeGeneric<*> and SomeGeneric<Any>, you can compare the two code snippets above, side by side.


Star projection for declaration-site producer

So far, we saw the type projections out, in and * for the Crate class that was invariant at declaration-site: Crate<T>. From here on, let's find out how the star projection behaves with the classes that are already in and out at the declaration-site with type parameter bounds:

Declaration-site

class ProducerCrate<out T : Fruit> {
    private val fruits = listOf<T>()
    fun produce() : T = fruits.last()
}

Use-site

fun useProducer(star: ProducerCrate<*>) {

    // Even though we project * here, it is known to be at least a Fruit
    // because it's an upper bound at the declaration-site.
    val fruit = star.produce()               // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK
}

Star projection for declaration-site consumer

Declaration-site

class ConsumerCrate<in T> {
    private val items = mutableListOf<T>()
    fun consume(item: T) = items.add(item)
    fun size(): Int = items.size
}

Use-site

fun useConsumer(consumer: ConsumerCrate<*>) {

    // Cannot consume anything, because the lower bound is not supported
    // in Kotlin and T is unknown
    consumer.consume(Orange())               // Error

    // Only useful for T-independent functions and properties.
    consumer.size()                          // OK
}

Note that the lower bound is not supported in Kotlin. So, in the ConsumerCrate class above, we cannot have something like in T super Orange(lower bound) like we have out T : Orange(upper bound).


Star projection for declaration-site invariant

Declaration-site

class ProducerConsumerCrate<T : Fruit> {
    private val fruits = mutableListOf<T>()
    fun produce(): T = fruits.last()
    fun consume(fruit: T) = fruits.add(fruit)
}

Use-site

fun useProducerConsumer(producerConsumer: ProducerConsumerCrate<*>) {

    // Even though we project * here, T is known to be at least a Fruit
    // because it's the upper bound at the declaration-site.
    val fruit = producerConsumer.produce()   // OK

    // Fruit is guaranteed. Can use functions and properties of Fruit.
    fruit.getColor()                         // OK

    // Consumer not allowed because you don't want to accidentally add
    // oranges, if this crate is a Crate<Apple>.
    producerConsumer.consume(Fruit())        // Error
}

Conclusion

Type projections for the invariant Crate<T>:

Projections Produces Consumes Behaviour
Crate<Fruit> Fruit Fruit Producer and Consumer
Crate<out Fruit> Fruit Nothing Producer only
Crate<in Fruit> Any? Fruit Consumer only
Crate<*> Any? Nothing No Producer and No Consumer

That's it! Hope that helps.