Lesson 2 - Lists with arrays in Kotlin
In the previous lesson, Introduction to collections and genericity in Kotlin, we made an introduction to collections and showed what genericity is. In today's Kotlin tutorial, we'll learn more about Lists, which represent one type of collection that we've already encountered before.
Array
First and foremost, let's take a quick peek back to arrays, which were the first collection we learned about throughout the courses. One of the main characteristics of an array is that it has a fixed number of elements and we can never change its length. If we want to add something to the array, we have to create a new one. For this reason, it's not even considered a collection by some sources. Let's have an example:
val sampleArray = arrayOf(1, 2, 3) val sampleArray2 = sampleArray + 4 // sampleArray2 is a new instance and have no relationship to the sampleArray2 variable println(sampleArray.size) // the size of the original array is still 3, because we can't change the size of the current instance
As you can see, Kotlin smartly gets around this limitation caused by the fixed array size, using an immutable class wrapping the original JVM array. The elements in an array are indexed numerically, starting from zero.
Since the data is of the same type (either exactly the same or of a common ancestor) they take up the same space in memory. Individual array items are stored in memory as an uninterrupted sequence, like in a row. We can imagine an array of integers as something like this:
If we wanted to access the 5th item, we would simply access the beginning of
the array and then jump forward by 4 times the type size (in this case, the size
of an Int
). Reading and writing at array indexes are done
within a constant time complexity. If you are confused by this term,
you may see it as a way of writing into array indexes immediately and so that we
can read them.
The main disadvantage to using arrays is that we can't add or delete items during runtime. Unfortunately, we often need to do this. Imagine creating new array instances (its copies) of more than 1000 elements over and over again; the program would be slowed down radically.
Lists
Lists are collections that allow us to add and delete items at runtime. They can be indexed numerically as arrays, but they don't have to. Generally, there are two types of lists: Array lists and Linked lists. Kotlin doesn't support linked lists; if we ever needed one, we'd have to use standard Java collections.
Array lists
Lists often take advantage of the fact that we are able to create new arrays during runtime (keep in mind that the size of an array cannot be changed during runtime).
In this case, the list is a class that contains methods for adding and
removing elements (and many other useful methods, which aren't relevant to us at
the moment). The class essentially wraps the array and contains an extra
variable where the number of elements is stored. When the instance is being
created, an array of a given amount of elements is created inside, and the
variable carrying the number of elements is set to 0. When we add the first
element, it's stored at the 1st index in the array and the number of elements is
incremented. We would be able to add elements like this until we fill the array.
Once the array is full, we simply create a new array, let's say, twice as big.
We copy the elements from the old array into the new array and then throw away
the old one. Once this new array is filled as well, the process will be
repeated. This is the way the Kotlin ArrayList
collection, which we
haven't met yet, internally works. We can imagine an ArrayList
like
this:
The ArrayList
in the picture contains eight elements. The
elements are stored in an internal array of 12 elements. The last four elements
are unused and from the outside look as if they aren't even there.
The advantage here is the fast random access to the elements using numeric indexes thanks to the inner array. The downside to it is the time required to create the new array and copy the elements into it, although this process doesn't occur too often. Another less painful drawback is that the collection occupies more memory than necessary. Anyway, this list type is still the most used collection in Java and is fairly well optimized.
Array list is, in Kotlin, represented by the ArrayList
class.
Let's have a look at how to declare an ArrayList
and then describe
some important methods of the ArrayList
class:
We can initialize the collection like this:
val array = arrayListOf(1, 2, 3)
Or we can use:
val array = mutableListOf(1, 2, 3)
The ArrayList
collection corresponds to the one in Java. Kotlin
is using it. [hint]
ArrayList methods and other members
Java's ArrayList
implements the List
interface.
Kotlin does use Java's ArrayList
, but also adds its own methods
wrapped around this Java's ArayList
to make it look more like a
Kotlin collection. The basic methods are:
clear()
- Removes all the elements.contains()
- Returnstrue
if the list contains a given elementsize
- Returns the element count of the listget()
or[]
- Returns the element at a given positionadd()
or+=
- Adds a new element at the end of the list or at a given index (and shifts the other elements).remove()
or-=
- Removes a given element. This feature is very useful when we store instances of a class in the list (e.g. users). We don't have to keep their numerical indexes, we just call e.g.list.remove(carl)
.removeAt()
- Removes the element at a specific index.
Although it might be confusing, -=
and
+=
do not return a new ArrayList
instance.
List
We can also use listOf()
which is immutable and
used quite often in Kotlin. Every addition or deletion returns a new instance.
Because it doesn't really make sense to have methods such as add()
returning Unit
(nothing) on an immutable collection, we won't find
them here. However, we do have overloaded operators (+=
,
-=
) which always return a new instance.
When should I use List and when ArrayList?
Both ArayList
and List
are lists which we can use
for the same purpose. Where is the difference then? As I've already mentioned, a
variable of the List
type is immutable (not changing) and every
operation results in a new List
instance. On the other side,
ArrayList
can be changed. Consider having some kind of a 2D game
with the game board represented by a 2D array (e.g. chess or tic-tac-toe). In
this case, ArrayList
would be a better choice because it's
unnecessary to create new instances over and over again, especially when we
change only one object in it. If we moved a stone one place forward, we'd
created a completely new "array". Isn't that unnecessary?
ArrayList
has its disadvantages as well. For example, we can't
simply delete elements when iterating through it. It's because we would be
deleting them from the very same instance that is currently being iterated
through.
Usually, List
is a solid choice. If you want to change the
elements often, using ArrayList
is definitely a way to go. However,
when working with threads, absence of immutability may cause further
problems.
Example
Despite the fact that we've already used the Array
like 1000
times and ArrayList
isn't much different, for completeness' sake,
here's an example of its use:
val array = arrayListOf<Int>(1) array += 2 array -= 1 // We can delete! println(array)
The output:
[2]
The code above creates an ArrayList
of the Int
type
with a 1
element, then adds a 2
element and then
deletes the 1
element. The resulting list is then printed to the
console. We work with indexes as if we worked with an array, however, we can add
or delete elements on the same instance at runtime.
The ArrayList
adds a few extra methods, let's describe them
too:
addAll()
or+=
- Adds elements from a given array to the List. Similarly, we call theremoveRange()
method or-=
. It's a good idea to use this method since since it saves us from having to use an additional loop.toTypedArray()
- Returns a read-onlyArray
instance. Useful to encapsulate the elements in the collection.size
- A property storing the number of elements in the list.lastIndexOf
() - TheindexOf()
method alternative, returns the index of the last occurrence of a given element in the list.removeAll()
- Removes all the elements passed in an array as argument.reverse()
- It's important that the elements implement theComparable
interface, otherwise, this method will throw an exception. All of the basic classes and structures in Kotlin implementComparable
, but we have to implement it manually in our own classes.
We can add range using the +=
operator as well:
array += (1..5)
That's enough for now. As soon as we get into lambda functions,
we can have a look at more methods, such as filter()
or
map()
.
Make sure to try out other methods like sort()
, searching, and
so on. We'll get to more detailed work with collections further.
In the next lesson, Multidimensional arrays in Kotlin, we'll discuss multidimensional arrays, if you have skipped them when learning, and also dictionaries and sets in Kotlin.