Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and promises to make coding for Android even more fun.
In the previous article in this series, you learned about nullability, loops, and conditions in Kotlin. In this tutorial, we'll continue to learn the language by looking at the ranges and collections API in Kotlin.
A Range in Kotlin is a unique type that defines a start value and an end value. In other words, it is an interval between a start and an end value. Ranges in Kotlin are closed, meaning that the start value and end value are included in the range.
We'll now look at the different ways of creating ranges in Kotlin.
..
Operatorval oneToFive = 1..5
In the code above, we have created a closed range. This variable oneToFive
will include the following values: 1, 2, 3, 4, 5. We can loop over it using the for
loop construct.
for (n in oneToFive) { print(n) }
The code above can be shortened to:
for (n in 1..5) { print(n) }
We can also create a range of characters:
val aToZ = "a".."z"
The variable aToZ
will have all the letters in the English alphabet.
rangeTo()
FunctionThe ..
operator can be replaced with the rangeTo()
extension function to create a range. For example, we can also do this 1.rangeTo(5)
and it would still have the same results as using the ..
operator as discussed earlier.
val oneToFive: IntRange = 1.rangeTo(5)
downTo()
FunctionThis is another extension function that will create a range starting from a given number down to another one.
val fiveToOne = 5.downTo(1)
We can modify the range using the step()
function. This will modify the delta between each element in the range.
val oneToTenStep = 1..10 step 2 // 1, 3, 5, 7, 9
The code above will contain odd numbers between 1 and 10.
in
OperatorThe in
operator is used to ascertain whether a value is present in a given range.
if (5 in 1..10) { print("Yes 5 is in the range") // prints "Yes 5 is in the range" }
In the code above, we checked to see if 5 is in the range 1..10 using the in
operator. We can also do the opposite by using !n
to check if 5 is not in the range.
Collections are used to store groups of related objects in memory. In a collection, we can retrieve, store or organize the objects. Kotlin provides its collections API as a standard library built on top of the Java Collections API. (We'll discuss interfaces in Kotlin in a future post.)
You should note that these interfaces are linked to their implementation at compile time. You can't see the implementation source code in Kotlin, because the collections are actually implemented by the standard Java Collections such as ArrayList
, Maps
, HashMap
, Sets
, HashSet
, List
and so on. To really understand the collections API in Kotlin, you'll need to be familiar with these basic classes and interfaces in Java.
In this section, we'll learn about the List
, Set
and Map
collections in Kotlin. (If you want a refresher on arrays in Kotlin, kindly visit the first tutorial in this series.)
Kotlin's collections give us the ability to achieve a lot with just a little code—unlike in Java, which seems to need a lot of code to achieve a little! Kotlin has two variants of collections: mutable and immutable. A mutable collection provides us with the ability to modify a collection by either adding, removing or replacing an element. Immutable collections cannot be modified and don't have these helper methods.
Note that the addition, removal or replacement of an element in an immutable collection is possible via operator functions (we'll get to that shortly), but these will end up creating a new collection.
Iterable
InterfaceThe Kotlin Iterable
interface is at the top of the collections class hierarchy. This interface enables collections to be represented as a sequence of elements (which can be iterated over, naturally).
public interface Iterable<out T> { public abstract operator fun iterator(): Iterator<T> }
Collection
InterfaceThe Kotlin Collection
interface extends the Iterable
interface. The Collection
interface is immutable. In other words, you have read-only access to collections. The Set
and List
interfaces (more about these shortly) in Kotlin extend this interface.
Some of the functions and properties available in the Collection
interface are:
size
: this property returns the size of the collection.isEmpty()
: returns true if the collection is empty or false otherwise. contains(element: E)
: returns true if the element specified in the argument is present in the collection.containsAll(element: Collection<E>)
: returns true if the element in the collection passed as argument is present in the collection. public interface Collection<out E> : Iterable<E> { public val size: Int public fun isEmpty(): Boolean public operator fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean }
MutableIterable
InterfaceThis interface in Kotlin gives us a specialized mutable iterator from the parent Iterable
interface.
public interface MutableIterable<out T> : Iterable<T> { override fun iterator(): MutableIterator<T> }
MutableCollection
InterfaceThe MutableCollection
interface in Kotlin is a specialized interface that enables collections to be mutable. In other words, add and remove operations can be performed on a given collection. This interface extends both the Collection
interface and the MutableIterable
interface already discussed above. The MutableSet
and MutableList
interfaces (we'll get to them shortly) in Kotlin extend this interface. The functions available in this interface—apart from the ones available in its parents—are:
add(element: E)
: adds the element passed as an argument to the collection and returns true if successful or false if the collection does not support duplicates and the element is already present. remove(element: E)
: removes the element passed as an argument from the collection. Returns true if successful or false if it was not present in the collection.addAll(elements: Collection<E>)
: adds all the elements in the collection passed as arguments to the collection. Returns true if successful or false if nothing was added.removeAll(elements: Collection<E>)
: removes all of the elements present in the collection passed as arguments. Returns true if successful or false if nothing was removed. retainAll(elements: Collection<E>)
: retains only the elements present in the collections passed as arguments. Returns true if successful or false if nothing was retained. clear()
: removes all elements from this collection.public interface MutableCollection<E> : Collection<E>, MutableIterable<E> { override fun iterator(): MutableIterator<E> public fun add(element: E): Boolean public fun remove(element: E): Boolean public fun addAll(elements: Collection<E>): Boolean public fun removeAll(elements: Collection<E>): Boolean public fun retainAll(elements: Collection<E>): Boolean public fun clear(): Unit }
Now you have learned about the top interfaces in the collection class hierarchy in Kotlin, let's now look into how Kotlin handles collections such as Lists, Sets and Maps in the remaining part of the tutorial.
A list is an ordered collection of elements. This is a popular collection that is widely used. Let's look at different ways of creating a list in Kotlin.
listOf()
FunctionIn Kotlin, we can create an immutable (read-only) list using the listOf()
helper function from the Kotlin standard library. This function returns a Kotlin List
interface type.
var numbers: List<Int> = listOf(1, 2, 3, 4, 5) var names: List<String> = listOf("Chike", "Nnamdi", "Mgbemena") for (name in names) { println(name) }
Running the code above will print:
Chike Nnamdi Mgbemena
Moreover, we can pass values of different types into the listOf()
as arguments and the result will still work—it will be a list of mixed type.
var listMixedTypes = listOf("Chike", 1, 2.445, 's') // will still compile
emptyList()
FunctionThis function just creates an empty immutable list and returns a Kotlin List
interface type.
val emptyList: List<String> = emptyList<String>()
listOfNotNull()
FunctionThis function creates a new immutable list containing only elements that are not null. Notice that this function returns a Kotlin List
interface type also.
val nonNullsList: List<String> = listOfNotNull(2, 45, 2, null, 5, null)
The List
interface from the Kotlin standard library extends only the Collection
interface. In other words, its only parent is the Collection
interface. It overrides all the functions in the parent interface to cater for its special needs and also defines its own functions, such as:
get(index: Int)
: a function operator that returns the element at the specified index. indexOf(element: E)
: returns the index of the first occurrence of the element passed as an argument in the list, or -1 if none is found. lastIndexOf(element: E)
: returns the index of the last occurrence of the element passed as an argument in the list, or -1 if none is found. listIterator()
: returns a list iterator over the elements in the list.subList(fromIndex: Int, toIndex: Int)
: returns a list that contains the portion of the list between the specified start and end indices. println(names.size) // 3 println(names.get(0)) // "Chike" println(names.indexOf("Mgbemena")) // 2 println(names.contains("Nnamdi")) // 'true'
arrayListOf()
Function This creates a mutable list and returns a Java ArrayList
type.
val stringList: ArrayList<String> = arrayListOf<String>("Hello", "You", "There")
mutableListOf()
FunctionTo add, remove or replace values in a list, we need to make the list a mutable one. We can convert an immutable list to a mutable one by calling the function toMutableList()
on the list. However, note that this method will create a new list.
var mutableNames1 = names.toMutableList() mutableNames1.add("Ruth") // now mutable and added "Ruth" to list
To create a mutable list of a certain type from scratch, e.g. String
, we use mutableListOf<String>()
, while for mixed types we can just use the mutableListOf()
function instead.
// a mutable list of a certain type e.g. String val mutableListNames: MutableList<String> = mutableListOf<String>("Josh", "Kene", "Sanya") mutableListNames.add("Mary") mutableListNames.removeAt(1) mutableListNames[0] = "Oluchi" // replaces the element in index 0 with "Oluchi" // a mutable list of mixed types val mutableListMixed = mutableListOf("BMW", "Toyota", 1, 6.76, 'v')
Any of these functions will return a MutableList
Kotlin interface type. This interface extends both the MutableCollection
and List
interfaces discussed earlier in this section. The MutableList
interface adds methods for the retrieval or substitution of an item based upon its position:
set(index: Int, element: E)
: substitutes an element in the list with another element. This returns the element previously at the specified position. add(index: Int, element: E)
: inserts an element at the specified index. removeAt(index: Int)
: gets rid of the element at a particular index. val mutableListFood: MutableList<String> = mutableListOf<String>("Rice & stew", "Jollof rice", "Eba & Egusi", "Fried rice") mutableListFood.remove("Fried rice") mutableListFood.removeAt(0) mutableListFood.set(0, "Beans") mutableListFood.add(1, "Bread & tea") for (foodName in mutableListFood) { println(foodName) }
Running the code above, we produce the following result:
Beans Bread & tea Eba & Egusi
Note that all these functions create a Java ArrayList
behind the scenes.
A set is an unordered collection of unique elements. In other words, it can't have any duplicates! Let's look at some of the different ways of creating a set in Kotlin. Each of these creates a different data structure, each of which is optimized for a certain kind of task.
setOf()
FunctionTo create an immutable (read-only) set in Kotlin, we can use the function setOf()
, which returns a Kotlin Set
interface type.
// creates a immutable set of mixed types val mixedTypesSet = setOf(2, 4.454, "how", "far", 'c') // will compile var intSet: Set<Int> = setOf(1, 3, 4) // only integers types allowed
Note that the Kotlin Set
interface extends only the Kotlin Collection
interface and overrides all the properties available in its parent.
hashSetOf()
Function Using the hashSetOf()
function creates a Java HashSet
collection which stores elements in a hash table. Because this function returns a Java HashSet
type, we can add, remove, or clear elements in the set. In other words, it's mutable.
val intsHashSet: java.util.HashSet<Int> = hashSetOf(1, 2, 6, 3) intsHashSet.add(5) intsHashSet.remove(1)
sortedSetOf()
FunctionUsing the sortedSetOf()
function creates a Java TreeSet
collection behind the scenes, which orders elements based on their natural ordering or by a comparator. This set is also mutable.
val intsSortedSet: java.util.TreeSet<Int> = sortedSetOf(4, 1, 7, 2) intsSortedSet.add(6) intsSortedSet.remove(1) intsSortedSet.clear()
linkedSetOf()
FunctionThis function returns a Java LinkedHashSet
type. This mutable set maintains a linked list of the entries in the set, in the order in which they were inserted.
val intsLinkedHashSet: java.util.LinkedHashSet<Int> = linkedSetOf(5, 2, 7, 2, 5) // 5, 2, 7 intsLinkedHashSet.add(4) intsLinkedHashSet.remove(2) intsLinkedHashSet.clear()
mutableSetOf()
FunctionWe can use mutableSetOf()
to create a mutable set. This function returns a Kotlin MutableSet
interface type. Behind the scenes, this function simply creates a Java LinkedHashSet
.
// creates a mutable set of int types only val intsMutableSet: MutableSet<Int> = mutableSetOf(3, 5, 6, 2, 0) intsMutableSet.add(8) intsMutableSet.remove(3)
The MutableSet
interface extends both the MutableCollection
and the Set
interfaces.
Maps associate keys to values. The keys must be unique, but the associated values don't need to be. That way, each key can be used to uniquely identify the associated value, since the map makes sure that you can't have duplicate keys in the collection. Behind the scenes, Kotlin uses the Java Map
collection to implement its map collection type.
mapOf()
FunctionTo create an immutable or read-only Map
collection in Kotlin, we use the mapOf()
function. We create a map with this function by giving it a list of pairs—the first value is the key, and the second is the value. Calling this function returns a Kotlin Map
interface type.
val callingCodesMap: Map<Int, String> = mapOf(234 to "Nigeria", 1 to "USA", 233 to "Ghana") for ((key, value) in callingCodesMap) { println("$key is the calling code for $value") } print(callingCodesMap[234]) // Nigeria
Running the code above will produce the result:
234 is the calling code for Nigeria 1 is the calling code for USA 233 is the calling code for Ghana
Unlike the List
and Set
interfaces in Kotlin that extend the Collection
interface, the Map
interface doesn't extend any at all. Some of the properties and functions available in this interface are:
size
: this property returns the size of map collection.isEmpty()
: returns true if the map is empty or false otherwise.containsKey(key: K)
: returns true if the map contains the key in the argument. containsValue(value: V)
: returns true if the map maps one or more keys to the value passed as an argument.get(key: K)
: returns the value matching the given key or 'null' if none is found. keys
: this property returns an immutable Set
of all the keys in the map.values
: returns an immutable Collection
of all the values in the map. mutableMapOf()
FunctionThe mutableMapOf()
function creates a mutable map for us so that we can add and remove elements in the map. This returns a Kotlin MutableMap
interface type.
val currenciesMutableMap: MutableMap<String, String> = mutableMapOf("Naira" to "Nigeria", "Dollars" to "USA", "Pounds" to "UK") println("Countries are ${currenciesMutableMap.values}") // Countries are [Nigeria, USA, UK] println("Currencies are ${currenciesMutableMap.keys}") // Currencies are [Naira, Dollars, Pounds] currenciesMutableMap.put("Cedi", "Ghana") currenciesMutableMap.remove("Dollars")
The MutableMap
interface doesn't extend the MutableCollection
interface; it's only parent is the Map
interface. It overrides the keys
, entries
and values
properties from the parent interface in order to redefine them. Here are some of the extra functions available in the MutableMap
interface:
put(key: K, value: V)
: inserts the key, value pair into the map. This will return the previous value linked with the key or null if the key was not previously used. remove(key: K)
: removes the key and its linked value from the map. putAll
(from: Map<out K, V>)
: updates the map with all the data from the given map. New keys will be added, and existing keys will be updated with new values. clear()
: removes all elements from the map. We can get the value for a key using the get()
function. We can also use square bracket notation as a shortcut for get()
.
print(currenciesMutableMap.get("Nigeria")) // will print Naira print(currenciesMutableMap["Nigeria"]) // will print Naira
hashMapOf()
FunctionUsing this function returns a Java HashMap
type that is mutable. The HashMap
class uses a hash table to implement the Java Map
interface.
val personsHashMap: java.util.HashMap<Int, String> = hashMapOf(1 to "Chike", 2 to "John", 3 to "Emeka") personsHashMap.put(4, "Chuka") personsHashMap.remove(2) print(personsHashMap[1]) // will print Chike
linkedHashMap()
FunctionThis function returns a Java LinkedHashMap
type that is mutable. The LinkedHashMap
class extends Java HashMap
and maintains a linked list of the entries in the map in the order in which they were inserted.
val postalCodesHashMap: java.util.LinkedHashMap<String, String> = linkedMapOf("NG" to "Nigeria","AU" to "Australia","CA" to "Canada") postalCodesHashMap.put("NA", "Namibia") postalCodesHashMap.remove("AU") postalCodesHashMap.get("CA") // Canada
sortedMapOf()
FunctionThis function returns a Java SortedMap
type which is mutable. The Java SortedMap
class sees that the entries in the map are maintained in an ascending key order.
val personsSortedMap: java.util.SortedMap<Int, String> = sortedMapOf(2 to "Chike", 1 to "John", 3 to "Emeka") personsSortedMap.put(7, "Adam") personsSortedMap.remove(3)
Remember, implementation of these collection interfaces in Kotlin happens at compile time.
Kotlin provides us with many useful operator functions called extension functions that can be invoked on collections. Let's take a look at some of the most useful.
last()
FunctionThis operator function returns the last element in a collection such as a list or set. We can also supply a predicate to search within a subset of elements.
val stringList: List<String> = listOf("in", "the", "club") print(stringList.last()) // will print "club" // given a predicate print(stringList.last{ it.length == 3}) // will print "the" val intSet: Set<Int> = setOf(3, 5, 6, 6, 6, 3) print(intSet.last()) // will print 6
first()
FunctionThis operator function returns the first element when invoked on a collection such as a list or set. If a predicate is given, it then uses the predicate to restrict the operation to a subset of elements.
print(stringList.first()) // will print "in" print(intSet.first()) // will print 3
max()
FunctionInvoking this operator function on a collection such as a list or set returns the largest element, or null if no largest element is found.
val intList: List<Int> = listOf(1, 3, 4) print(intList.max()) // will print 4 print(intSet.max()) // will print 6
drop()
FunctionCalling this operator function returns a new list or set containing all elements except the first n elements.
print(stringList.drop(2)) // will print "club"
plus()
FunctionThis operator function returns a collection containing all elements of the original and then the given element if it isn't already in the collection. This will end up creating a new list instead of modifying the list.
print(intList.plus(6)) // will print [1, 3, 4, 6]
minus()
FunctionThe opposite of the plus()
function is the minus()
function. It returns a collection containing all elements of the original set except the given element. This also ends up creating a new list instead of altering the list.
print(intList.minus(3)) // will print [1, 4]
average()
FunctionCalling this operator function will return an average number of elements in the collection.
print(intList.average()) // will print 2.6666666666666665
Most of these extension functions are available in the Kotlin collections standard library. You are advised to check out the documentation to learn about the others.
In this tutorial, you learned about the range and collections API in the Kotlin programming language. In the next tutorial in the Kotlin From Scratch series, you'll be introduced to functions in Kotlin. See you soon!
To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts!