Content Index
- When to use lists and when?
- 1. When to use Lists (List, MutableList)
- 2. When to use Arrays (Array, IntArray, etc.)
- Arrays in Kotlin: Low-level structure (The "Legacy" way)
- Variations in the arrayOf, arrayOfNulls, and emptyArray methods
- emptyArray
- arrayOfNulls
- Comparison of Array types
- The concept of "Boxing"
-
Why is arrayOf
inefficient? - Lists in Kotlin
- Immutable Lists
- Mutable Lists
- Deleting values from the list by value and index.
- Mutable and immutable lists
- setOf Lists
- Examples of List operations
- 1. Plus (+) and Minus (-) Operators
- 2. Data Classes in Lists
- 3. Predicates (Filter, Map, Any, Find)
- Putting it all together
- Filtering and Transformation Functions
- Conclusion
Arrays and lists are fundamental elements in any programming language that allow handling collections of values or data of the same type, whether they are Kotlin primitives or our own objects; in Kotlin.
As a fundamental principle in Kotlin, which is the handling of mutable and immutable types, this principle also extends to the arrays and lists that we will see below.
Previously we saw companion Objects to handle Static and Factories in Kotlin.
When to use lists and when?
In summary, lists represent the modern way of working in Kotlin. Both structures allow us to store a set of elements of the same type (integers, strings, or objects from a database).
For example, if you are developing an academy application (like the one you see in the background), you will have a Book class and you will want to return a list to display it on the screen. For that, we use these structures.
- Lists: They are the preferred option 95% of the time. They allow varying their size (if they are mutable), manipulating data with functional programming (filters, sorting), and they feature the null safety characteristic of Kotlin.
- Arrays: They are a lower-level structure. They are mainly used for interoperability with Java or when slightly superior efficiency is sought. They have a fixed size known from their creation.
1. When to use Lists (List, MutableList)
It is the preferred option in 95% of cases in Kotlin.
- Dynamic size: When you don't know how many elements you are going to have (for example, data coming from a database or an API).
- Data manipulation: If you need to easily add, remove, or filter elements.
- Functional Programming: Lists have much more powerful methods to transform data such as map, filter, reduce, and groupBy.
- Type safety: Lists in Kotlin are "covariant" (you can treat a List<String> as a List<Any>), which makes them more flexible in software design.
2. When to use Arrays (Array, IntArray, etc.)
Arrays are considered a "low-level" structure. You should use them in specific situations:
- Critical Performance: Arrays are slightly faster and consume less memory because they map directly to native JVM structures. They are used in video games or massive data processing.
- Primitive Types: If you need to optimize to the maximum, specialized arrays like IntArray or DoubleArray avoid "boxing" (converting numbers into objects), saving a lot of memory.
- Interoperability with Java: Many old Java libraries request String[] or int[]. In Kotlin, to pass that data, you must use Array.
- Known fixed size: When the collection will never change size (for example, the months of the year or the coordinates of a 3D point).
Summary:
| Method | Size | Initial Content | Main Use |
|---|---|---|---|
| emptyArray() | Always 0 | Nothing | Avoiding null values in functions. |
| arrayOfNulls(size) | Defined by you | null | Reserving fixed space before having the data. |
Arrays in Kotlin: Low-level structure (The "Legacy" way)
If we want to define an array in Kotlin, we can do it using the arrayOf, arrayOfNulls, and emptyArray methods; each of these forms shares common properties and functions as we will see a bit later; finally, to create an array with the arrayOf method we have:
var array = arrayOf(1,2,3,4,5)And with this we create an array of integers; if we want to print the array as a String or text chain, where we separate each of the elements that make up the array with commas, we can use the following method:
array.joinToString()And we get:
1, 2, 3, 4, 5Variations in the arrayOf, arrayOfNulls, and emptyArray methods
If we want to declare the previous array with integers, we use the intArrayOf method:
var array = intArrayOf(1,2,3,4,5)If on the contrary we want them to be double:
var array = doubleArrayOf(1.1,2.2,3.3,4.4,5.5)Of course, in all cases we can use the same primitives and methods like the one we mentioned earlier which is the joinToString() method:
array.joinToString()
// prints 1.1, 2.2, 3.3, 4.4, 5.5To get the size of the array, we use the size property:
array.size // prints 5 in any of the previous casesWe can also use some functions to get or set values:
var array = doubleArrayOf(1.1,2.2,3.3,4.4,5.5)
println(array.get(2)) // prints 3.3 array.set(2, 2.5)
// we set the value 2.5 at index 2
println(array.get(2)) // prints 2.5 We can also get values as if it were an array:
println(array[2]) // prints 2.5emptyArray
This method creates an array with a size of 0.
What is it used for?
- Default initialization: When you need to initialize a variable that must contain an array, but you don't have elements yet.
- Function return: If a function must return an array and finds no results, it is better to return an emptyArray than a null value. This way you prevent the app from crashing due to a "null pointer" error.
// We create an empty array of Strings
var names = emptyArray<String>()
println(names.size) // Prints 0arrayOfNulls
This method creates an array of a specific size but fills all its positions with the null value.
What is it used for?
Memory reservation: If you know exactly how many elements your collection will have (for example, 100), but you are going to calculate or receive them one by one later. It is more efficient than creating a list and making it grow.
Data structures: It is widely used when implementing algorithms where you need empty "holes" that will be filled according to certain logic.
// We reserve space for 3 integers, initially they are null
val myGrades = arrayOfNulls<Int>(3)
myGrades[0] = 10 myGrades[1] = 8
// myGrades[2] is still null
println(myGrades.joinToString()) In Summary:
- emptyArray<T>(): Creates an array of size zero. It is useful when you need to declare the variable but don't have the elements or the final size yet.
- arrayOfNulls<T>(size): Creates a fixed-size array filled with null values. It serves to reserve memory space that you will fill later.
- Primitive Arrays: For greater efficiency, Kotlin offers versions like intArrayOf() or doubleArrayOf(). These avoid object "boxing," working directly with primitive types.
Comparison of Array types
If you use arrayOf<Int>(1, 2), Kotlin creates an object for each number ("boxed" Integer), which is inefficient. When using intArrayOf(), Kotlin uses direct primitive types from the processor, which is much faster and saves memory.
The concept of "Boxing"
Imagine a number is a small piece of metal.
- Primitive Type (int): It is the loose piece of metal. It is light, small, and the processor can handle it directly super fast.
- Object (Integer): It is that same piece of metal, but tucked inside a velvet-lined wooden box. The box has labels, methods, and takes up much more space in the warehouse (memory).
Why is arrayOf<Int> inefficient?
When you create an Array<Int>, what you have in memory is:
A structure for the Array.
- References (arrows) pointing to different "Integer" objects scattered throughout memory.
- Each "Integer" object spends extra memory (object metadata) in addition to the numeric value.
Lists in Kotlin
As we mentioned at the beginning and as a fundamental principle in Kotlin, we have the handling of mutable and immutable types which also leads to the use of lists, so by default, lists in Kotlin are immutable, which in other words means, they cannot mutate or what is the same, they cannot change.
Therefore Kotlin provides methods for queries; on the other hand, for mutable lists Kotlin provides methods to mutate lists as we want, i.e. methods like editing or changing values or removing them; first we will see the default lists or those of the immutable type.
In summary, in Kotlin, by default, lists are immutable (they cannot be modified once created).
- listOf(): Creates a read-only list. It does not have methods to add or change elements. This protects data integrity.
- mutableListOf(): If you need to add, delete, or modify elements, you must use this variant.
Immutable Lists
To create a list we have the List class predefined in Kotlin that allows creating lists of generic objects; that is, of any type; to create a list of immutable elements we have:
val list = listOf(1,2,3,4,5)Here the same organization presented with the Arrays we saw earlier is NOT extended, in that we cannot specify the data type of the list by indicating the data type in front of the list name as we did with Arrays; that is, the following would be invalid code:
val list = intListOf(1,2,3,4,5) // error: unresolved reference: intListOfMutable Lists
If on the contrary we need to customize the values of the list, that is, use methods to add, remove, or change values, we can use mutable lists and in this way we have an extra range of options to be able to customize or change the list whenever we want; for this we must add "mutable" in front of "ListOf", for example, to create a mutable list we have:
val mutableList = mutableListOf(1,2,3,4,5)To change a value, we can do it in two ways, just as happens with Arrays:
mutableList[1] = 85
println(mutableList[1]) // prints 85Or through:
mutableList.set(1,90)
println(mutableList.get(1)) // prints 90In the two previous code blocks, in addition to changing or setting another value to an index of the list, we saw how to get the value from it; for this we use the conventional method using brackets [] and between them the index of the position we want to query, or the get method that receives said index as a parameter; we can use whichever method we prefer both for setting and getting values.
Deleting values from the list by value and index.
We also have methods to delete values; for this there is the removeAt method which allows deleting a value from the list by indicating the index of the position we want to delete and remove which allows deleting a value by the value of the position:
val mutableList = mutableListOf(10,20,30,400,50)
mutableList.removeAt(1)
// the list becomes mutableList [10, 30, 400, 50]
mutableList.remove(400)
// the list becomes mutableList [10, 30, 50]Mutable and immutable lists
As we can see, by default Kotlin offers listOf and by adding the word "mutable" we can already handle mutable types; it's a kind of "rule" or tip we can keep in mind when working with lists; with mutable types, which if we take it to its base meaning are those that can mutate or change, we can use methods to get values via get, delete via, and set values via set which we cannot do with immutable lists.
Lists also have common methods and properties like the size property:
val mutableList = mutableListOf(1,2,3,4,5 )
mutableList.size // prints 5 setOf Lists
In addition to the lists we saw earlier, Kotlin also has another structure similar to lists called setOf, which are collections of data that do not allow repeated values, that is, they only allow unique values, meaning they do not repeat:
val set = setOf(1,1,2,3,4,5,5,1)It will print:
[1, 2, 3, 4, 5]This type of data collection does not allow modifying, deleting, or adding new values, that is, they are of the immutable type; to modify it we have to use the mutable types.
val setMutable = mutableSetOf(1,2,3,4,5)Examples of List operations
1. Plus (+) and Minus (-) Operators
Unlike .add() or .remove(), these operators do not modify the original list, but return a new list with the change applied. This is ideal for maintaining immutability:
val numbers = listOf(10, 20, 30, 40)
// Plus: Creates a list with an extra element
val listPlusOne = numbers + 50 // Result: [10, 20, 30, 40, 50]
// Minus: Creates a list removing the first occurrence of the element
val listMinusOne = numbers - 30
// Result: [10, 20, 40]
println("Original: $numbers") // The original remains intact2. Data Classes in Lists
In the real world, you rarely handle lists of simple numbers. You handle objects. Data classes are perfect because Kotlin already knows how to compare their values automatically.
data class Product(val name: String, val price: Double)
val cart = listOf( Product("Laptop", 1200.0),
Product("Mouse", 25.0), Product("Keyboard", 80.0) )
// Now the list contains complex objects println(cart[0].name)
// Prints: Laptop3. Predicates (Filter, Map, Any, Find)
Predicates are conditions (lambda functions) that you pass to the list to process the data elegantly.
val prices = listOf(10, 50, 100, 200, 500)
// FILTER: Keeps only what meets the condition
val expensive = prices.filter { it > 150 }
// Result: [200, 500]
// MAP: Transforms each element (e.g., applying 10% discount)
val discounts = prices.map { it * 0.9 }
// Result: [9.0, 45.0, 90.0, 180.0, 450.0]
// ANY: Returns true if AT LEAST ONE element meets the condition
val anySuperExpensive = prices.any { it > 1000 }
// Result: false
// FIND: Searches for the first element that meets the condition or returns null val firstLarge = prices.find { it > 150 }
// Result: 200Putting it all together
Imagine you want the names of products that cost more than 50:
val expensiveProdNames = cart
.filter { it.price > 50.0 } // We filter
.map { it.name }
// We extract only the name
println(expensiveProdNames)
// [Laptop, Keyboard]Filtering and Transformation Functions
These functions are extremely powerful and common in modern development:
- filter: Returns a list with the elements that meet a condition.
- map: Transforms each element of the list into another data type.
- any: Returns true if at least one element meets the condition.
- find: Searches for the first match that meets the rule.
Conclusion
As you can see, lists are usually handled using Data Classes to represent entities (products, books, users). Handling lists and arrays is truly learned by practicing, something we will do thoroughly when we start creating our application in Android.