Introduction: Why Confusion with the Asterisk and Any is Normal
\n\nIf you've been writing Kotlin for at least a couple of weeks, you've certainly encountered generics. And if you've looked into the standard library source code or written something like List<*>, you might have wondered: "Why not just write List<Any>? Doesn't the asterisk mean 'any type'?"
This question is a real hit on Stack Overflow (thousands of views). And it's no wonder: at first glance, * and Any seem interchangeable. But in reality, there's a chasm between them, and if you don't notice it, you can write code that won't compile or, even worse, silently breaks at runtime.
In this article, we'll once and for all figure out the difference between * (star projection) and Any in Kotlin generics. You'll learn when to use each one, how variance works, and why the asterisk is not "Any", but "I don't know what's there, but I don't care." Let's go!
Briefly About Generics: Why They Are Needed
\n\nGenerics allow us to write classes and functions that work with different types without losing type safety. Instead of writing a separate class for Int, a separate one for String, and a separate one for User, we write one class Box<T>.
But what do we do if we want to write a function that accepts any Box, regardless of what's inside? That's where our heroes appear: * and Any.
Any is a Concrete Type (Almost Like Object)
\n\nIn Kotlin, Any is the root of the type hierarchy for all non-null types. If you write List<Any>, it means: "a list that contains elements of type Any." That is, you can put Int, String, User — anything at all, because they all inherit from Any.
Important point: List<Any> is a concrete parameterized type. The compiler knows that the element type is Any.
val listOfAny: MutableList<Any> = mutableListOf(1, "hello", 3.14)\nlistOfAny.add(42) // OK\nlistOfAny.add("world") // OK\n\n\nEverything is predictable here: we explicitly said that the list works with Any. But there's a nuance: MutableList<Any> is not MutableList<String>. You cannot pass a list of strings where a list of Any is expected (unless you use covariance, but more on that later).
The Asterisk (*) is "I Don't Know, But I Don't Care"
\n\nThe asterisk, or star projection, is a completely different story. When you write List<*>, you are telling the compiler: "This is a list of some type, but I don't know which one, and I don't care." This is a projection of an unknown type.
The key difference: * is not a type, but a wildcard. It means the type is unknown, and the compiler imposes restrictions on what you can do with such an object.
fun printLength(list: List<*>) {\n // list.add(1) // Compilation error! Cannot add an element\n println(list.size) // OK: can get the size\n for (item in list) {\n println(item) // OK: item has type Any?\n }\n}\n\n\nNote: when you read elements from List<*>, they have type Any? (because the list might contain null). But you cannot add elements, because the compiler doesn't know what type is actually stored there. This is protection against breaking type safety.
The Main Difference in a Nutshell
\n\n- \n
List<Any>— is a list that can contain