Why there is such emptyList constructor in Kotlin? It was a immutable List, so there is no way to add or remove its elements and it was empty! So, what is the function of this emptyList?
4 Answers
The emptyList
is not a constructor but a function that returns and immutable empty list implementation.
The main reason such function exists is to save allocations. Since emptyList
returns the same singleton instance every time it is called so one can use it in allocation free manner. Note that the very same object is returned regardless of the element type i.e. emptyList<String>() === emptyList<Int>()
is true
.
IMHO emptyList
also reads a bit better than listOf
when used as e.g. a default parameter value:
data class Person(val friends:List<Person> = emptyList())
To address
Actually i want to know why people using emptyList? What for?
more directly: you need it e.g. to pass to methods taking lists, or when you have a list which is empty in one branch of your code and not another:
val commandLineOptions: List<String> = when {
x > 0 -> listOf(...)
else -> emptyList()
}
runCommand(command, commandLineOptions)
@miensol provides a good answer addressing the lower-level details of using a singleton empty list object, minimizing allocations. In my opinion, the allocation of an empty list in JVM-based language is only marginally significant. You will be losing a few bytes of memory or wasting only a few clock cycles when instantiating via listOf()
. Of course, if you create empty lists all of the time, this may add up, but it's difficult for me to see where this is applicable.
While I do not know the design intent of the API, I definitely see a semantic reason for its inclusion.
As @miensol already stated, emptyList()
appears like a good candidate for a default parameter. The reason this is true is because it adheres to the well-known Null-Object Pattern.
Briefly, the pattern suggests a design where we use semantically meaningful types to represent our empty, default, or null values. In languages susceptible to null-pointer exceptions, this is useful because a proper object allows the client to call any of the available class members.
Consider the example below, where we use a default of null
.
class AcademicProfile(val courseGradesAsPercent : List<Double>? = null) {
fun displayAverage() {
if (courseGradesAsPercent == null)
println("No average to calculate, please sign-up for a course!")
else {
val sum = courseGradesAsPercent.reduce { accumulator, value -> accumulator + value }
val average = sum / courseGradesAsPercent.count()
println("Your unweighted average is $average")
}
}
}
The intent expressed by this class is that the list of course grades can be in any of the following states:
- null
- not-null, but empty
- filled with elements
Now, suppose we used emptyList()
instead as the default:
class AcademicProfile(val courseGradesAsPercent : List<Double> = emptyList()) {
fun displayAverage() {
if (courseGradesAsPercent.isEmpty())
println("No average to calculate, please sign-up for a course!")
else {
val sum = courseGradesAsPercent.reduce { accumulator, value -> accumulator + value }
val average = sum / courseGradesAsPercent.count()
println("Your unweighted average is $average")
}
}
}
In this scenario, there is no confusion between null
and empty. If there are no grades, then the list is empty. These semantics are carried into the function by allowing us to call a member on the null-object's type (i.e. List<Double>.isEmpty()
). Working with null
expresses less about the domain and instead relies on the language's mechanics.
The performance benefit of using the singleton empty list is just icing on the cake!
Collections.emptyList()
– ThiloCollections.singletonList()
. – ice1000