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.
1. Ranges
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.
The ..
Operator
val 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.
The rangeTo()
Function
The ..
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)
The downTo()
Function
This 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.
The in
Operator
The 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.
2. Collections
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.
The Iterable
Interface
The 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> }
The Collection
Interface
The 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 }
The MutableIterable
Interface
This 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> }
The MutableCollection
Interface
The 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.
Lists
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.
Using the listOf()
Function
In 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
Using the emptyList()
Function
This function just creates an empty immutable list and returns a Kotlin List
interface type.
val emptyList: List<String> = emptyList<String>()
Using the listOfNotNull()
Function
This 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'
Using the arrayListOf()
Function
This creates a mutable list and returns a Java ArrayList
type.
val stringList: ArrayList<String> = arrayListOf<String>("Hello", "You", "There")
Using the mutableListOf()
Function
To 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.
Sets
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.
Using the setOf()
Function
To 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.
Using the 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)
Using the sortedSetOf()
Function
Using 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()
Using the linkedSetOf()
Function
This 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()
Using the mutableSetOf()
Function
We 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
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.
Using the mapOf()
Function
To 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 immutableSet
of all the keys in the map.values
: returns an immutableCollection
of all the values in the map.
Using the mutableMapOf()
Function
The 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
Using the hashMapOf()
Function
Using 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
Using the linkedHashMap()
Function
This 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
Using the sortedMapOf()
Function
This 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.
Collections Operation Functions
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.
The last()
Function
This 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
The first()
Function
This 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
The max()
Function
Invoking 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
The drop()
Function
Calling this operator function returns a new list or set containing all elements except the first n elements.
print(stringList.drop(2)) // will print "club"
The plus()
Function
This 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]
The minus()
Function
The 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]
The average()
Function
Calling 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.
Conclusion
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!
-
Android SDKIntroduction to Android Architecture Components
-
Machine LearningCreate Chatbots on Android With IBM Watson
-
Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?
-
Android SDKQuick Tip: Write Cleaner Code With Kotlin SAM Conversions
-
Android SDKAndroid O: Phone Number Verification With SMS Tokens
No comments:
Post a Comment