When we define a collection with "*", it should contain the object of only that type. There should not be any mix and match between the data types inside a collection. If we use "Any", we can mix and match the data types, which means we can have multiple data types in a collection.
Any is the root of the Kotlin class hierarchy. What this means is that every* value in Kotlin is implicitly an Any . In the same way that every List<T> is also a Collection<T> and every Integer and Double is also a Number , every* single value in Kotlin is also an Any .
The * operator is known as the Spread Operator in Kotlin. From the Kotlin Reference...
"Out" keyword is extensively used in Kotlin generics. Its signature looks like this − List<out T> When a type parameter T of a class C is declared out, then C can safely be a super type of C<Derived>. That means, a Number type List can contain double, integer type list.
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 String
s, or a list of Int
s, 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), whileList<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.
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
}
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 projectionWhen 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.
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
}
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).
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
}
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.
In the context I think you imply, SomeGeneric<*>
is equivalent to SomeGeneric<out Any?>
. The Java equivalent is SomeGeneric<? extends Object>
.
The syntax called "star-projections". Here are the official docs: https://kotlinlang.org/docs/reference/generics.html#star-projections
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With