Chapters

Hide chapters

Kotlin Apprentice

Second Edition · Android 10 · Kotlin 1.3 · IDEA

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Building Your Own Types

Section 3: 8 chapters
Show chapters Hide chapters

Section IV: Intermediate Topics

Section 4: 9 chapters
Show chapters Hide chapters

8. Arrays & Lists
Written by Eli Ganim & Joe Howard

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

As discussed in the introduction to this section, collections are flexible “containers” that let you store any number of values together. Two of the most common collections types in Kotlin are arrays and lists.

Arrays

Arrays in Kotlin correspond to the basic array type available in Java. Arrays are typed, just like regular variables and constants, and store multiple values in a contiguous portion of memory.

Before you create your first array, take some time to consider in detail what an array is and why you might want to use one.

What is an array?

An array is an ordered collection of values of the same type. The elements in the array are zero-indexed, which means the index of the first element is 0, the index of the second element is 1, and so on. Knowing this, you can work out that the last element’s index is the number of values in the array less 1.

When are arrays useful?

Arrays are useful when you want to store your items in a particular order. You may want the elements sorted, or you may need to fetch elements by index without iterating through the entire array.

Creating arrays

The easiest way to create an array is by using a function from the Kotlin standard library, arrayOf(). This is a concise way to provide array values.

val evenNumbers = arrayOf(2, 4, 6, 8)
val fiveFives = Array(5, { 5 }) // 5, 5, 5, 5, 5
val vowels = arrayOf("a", "e", "i", "o", "u")

Arrays of primitive types

When using arrayOf() and creating arrays with types such as Array<Int>, the resulting array is a list of object types. In particular, if you’re running on the JVM, the integer type will be the boxed Integer class and not the primitive int type. Using primitive types over their boxed counterparts will consume less memory and result in better performance. Unfortunately you can’t use primitives with lists (covered in the next section), so it will be up to you to determine on a case by case basis if the trade off is worth it!

val oddNumbers = intArrayOf(1, 3, 5, 7)
val zeros = DoubleArray(4) // 0.0, 0.0, 0.0, 0.0
val otherOddNumbers = arrayOf(1, 3, 5, 7).toIntArray()

Arguments to main()

The main function is the entry point to Kotlin programs. From Kotlin 1.3 onwards, the main function has an optional parameter named args that is an Array<String>:

fun main(args: Array<String>) {
}

Iterating over an array

To see the arguments passed to main, you can use the for loop you read about in Chapter 5, “Advanced Control Flow”.

for (arg in args) {
  println(arg)
}
// do
// re
// mi
// fa
// sol
// la
// ti
// do
args.forEach { arg ->
    println(arg)
}

Lists

A type that is very similar conceptually to an array is a list. Like in Java, the List type in Kotlin is an interface that has concrete realizations in types such as ArrayList, LinkedList and others. Arrays are typically more efficient than lists in terms of raw performance, but lists have the additional feature of being dynamically-sized. That is, arrays are of fixed-size, but lists can be setup to grow and shrink as needed, as you’ll see later when learning about mutable lists.

Creating lists

Like with arrays, the standard library has a function to create a list.

val innerPlanets = listOf("Mercury", "Venus", "Earth", "Mars")
val innerPlanetsArrayList = arrayListOf("Mercury", "Venus", "Earth", "Mars")
val subscribers: List<String> = listOf()
val subscribers = listOf<String>()

Mutable lists

Once again, the standard library has a function to use here.

val outerPlanets = mutableListOf("Jupiter", "Saturn", "Uranus", "Neptune")
val exoPlanets = mutableListOf<String>()

Accessing elements

Being able to create arrays and lists is useless unless you know how to fetch values from them. In this section, you’ll learn several different ways to access the elements. The syntax is similar for both arrays and lists.

Using properties and methods

Imagine you’re creating a game of cards, and you want to store the players’ names in a list. The list will need to change as players join or leave the game, so you need to declare a mutable list:

val players = mutableListOf("Alice", "Bob", "Cindy", "Dan")
print(players.isEmpty())
// > false
if (players.size < 2) {
  println("We need at least two players!")
} else {
  println("Let's start!")
}
  // > Let's start!
var currentPlayer = players.first()
println(currentPlayer) // > Alice
println(players.last()) // > Dan
val minPlayer = players.min()
minPlayer.let {
  println("$minPlayer will start") // > Alice will start
}
println(arrayOf(2, 3, 1).first())
// > 2
println(arrayOf(2, 3, 1).min())
// > 1
val maxPlayer = players.max()
if (maxPlayer != null) {
  println("$maxPlayer is the MAX") // > Dan is the MAX
}

Using indexing

The most convenient way to access elements in an array or list is by using the indexing syntax. This syntax lets you access any value directly by using its index inside square brackets:

val firstPlayer = players[0]
println("First player is $firstPlayer")
// > First player is Alice
val secondPlayer = players.get(1)
val player = players[4] // > IndexOutOfBoundsException

Using ranges to slice

You can use the slice() method with ranges to fetch more than a single value from an array or list.

val upcomingPlayersSlice = players.slice(1..2)
println(upcomingPlayersSlice.joinToString()) // > Bob, Cindy

Checking for an element

You can check if there’s at least one occurrence of a specific element by using the in operator, which returns true if it finds the element, and false otherwise.

fun isEliminated(player: String): Boolean {
  return player !in players
}
println(isEliminated("Bob")) // > false
players.slice(1..3).contains("Alice") // false

Modifying lists

You can make all kinds of changes to mutable lists, such as adding and removing elements, updating existing values, and moving elements around into a different order. In this section, you’ll see how to work with the list to match up with what’s going on in your game.

Appending elements

If new players want to join the game, they need to sign up and add their names to the list. Eli is the first player to join the existing four players.

players.add("Eli")
players += "Gina"
println(players.joinToString())
// > "Alice", "Bob", "Cindy", "Dan", "Eli", "Gina"
var array = arrayOf(1, 2, 3)
array += 4
println(array.joinToString()) // > 1, 2, 3, 4

Inserting elements

An unwritten rule of this card game is that the players’ names have to be in alphabetical order. This list is missing a player that starts with the letter F. Luckily, Frank has just arrived. You want to add him to the list between Eli and Gina. To do that, you can use a variant of the add() method that accepts an index as the first argument:

players.add(5, "Frank")

Removing elements

During the game, the other players caught Cindy and Gina cheating. They should be removed from the game! You can remove them by name using the remove() method:

val wasPlayerRemoved = players.remove("Gina")
println("It is $wasPlayerRemoved that Gina was removed")
// > It is true that Gina was removed
val removedPlayer = players.removeAt(2)
println("$removedPlayer was removed") // > Cindy was removed

Mini-exercise

Use indexOf() to determine the position of the element "Dan" in players.

Updating elements

Frank has decided everyone should call him Franklin from now on. You could remove the value "Frank" from the list and then add "Franklin", but that’s too much work for a simple task. Instead, you should use the indexing syntax to update the name.

println(players.joinToString())
// > "Alice", "Bob", "Dan", "Eli", "Frank"
players[4] = "Franklin"
println(players.joinToString())
// > "Alice", "Bob", "Dan", "Eli", "Franklin"
players[3] = "Anna"
players.sort()
println(players.joinToString()) // > "Alice", "Anna", Bob", "Dan", "Franklin"
players.set(3, "Anna")
val arrayOfInts = arrayOf(1, 2, 3)
arrayOfInts[0] = 4
println(arrayOfInts.joinToString()) // > 4, 2, 3

Iterating through a list

It’s getting late, so the players decide to stop for the night and continue tomorrow. In the meantime, you’ll keep their scores in a separate list. You’ll investigate a better approach for this when you learn about maps, but for now you can continue to use lists:

val scores = listOf(2, 2, 8, 6, 1)
for (player in players) {
  println(player)
}
// > Alice
// > Anna
// > Bob
// > Dan
// > Franklin
for ((index, player) in players.withIndex()) {
  println("${index + 1}. $player")
}
// > 1. Alice
// > 2. Anna
// > 3. Bob
// > 4. Dan
// > 5. Franklin
fun sumOfElements(list: List<Int>): Int {
  var sum = 0
  for (number in list) {
    sum += number
  }
  return sum
}
println(sumOfElements(scores))  // > 19

Mini-exercise

Write a for loop that prints the players’ names and scores.

Nullability and collection types

When working with arrays, lists, and other collection types, special consideration should be given to nullability. Are the elements of a collection nullable, for example, or is the collection itself nullable?

var nullableList: List<Int>? = listOf(1, 2, 3, 4)
nullableList = null
var listOfNullables: List<Int?> = listOf(1, 2, null, 4)
listOfNullables = null // Error: Null can not be a value of a non-null type
var nullableListOfNullables: List<Int?>? = listOf(1, 2, null, 4)
nullableListOfNullables = null

Challenges

Check out the following challenges to test your knowledge of Kotlin arrays and lists.

 1. val array1 = Array<Int>()
 2. val array2 = arrayOf()
 3. val array3: Array<String> = arrayOf()
val array4 = arrayOf(1, 2, 3)
4. println(array4[0])
5. println(array4[5])
6. array4[0] = 4
val array5 = arrayOf(1, 2, 3)
7. array5[0] = array5[1]
8. array5[0] = "Six"
9. array5 += 6
10. for item in array5 { println(item) }
fun removeOne(item: Int, list: List<Int>): List<Int>
fun remove(item: Int, list: List<Int>): List<Int>
fun reverse(array: Array<Int>): Array<Int>
import java.util.Random  
val random = Random()
fun rand(from: Int, to: Int) : Int {
 return random.nextInt(to - from) + from
}
fun randomized(array: Array<Int>): Array<Int>
fun minMax(numbers: Array<Int>): Pair<Int, Int>?

Key points

  • Arrays are ordered collections of values of the same type.
  • There are special classes such as IntArray created as arrays of Java primitive types.
  • Lists are similar to arrays but have the additional feature of being dynamically-sized.
  • You can add, remove, update, and insert elements into mutable lists.
  • Use indexing or one of many methods to access and update elements.
  • Be wary of accessing an index that’s out of bounds.
  • You can iterate over the elements of an array or list using a for loop or using forEach.
  • You can check for elements in an array or list using in.
  • Special consideration should be given when working with nullable lists and lists with nullable elements.

Where to go from here?

Now that you’ve learned about the array and list collection types in Kotlin, you can now move on to learning about two other common collection types: maps and sets.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now