Mastering Nullability and Collections in Kotlin: Unleash the Full Power of Your Code.
Daniel Brian
Posted on April 24, 2023
In this article, we're going to look at two important concepts in Kotlin OOP;
1. Nullability
2. Collections
1. Nullability
- NullPointer Exceptions are thrown by the program at runtime and sometimes cause application failure or system crashes.
- NullPointer exceptions are caused by:
- Explicit call to throw NullPointer()
- Use of the null assertion operator !!
- Data inconsistency with regard to initialization
- Java interoperations eg attempts to access a member on a null reference.
- Kotlin is designed to avoid errors due to null values. The code below demonstrates this design choice;
fun main() {
var message : String = "Hello world"
message = null
println(message)
}
- When you try to run the above code you will realize that it is not possible to assign a null value to the message variable and the compiler will display an error.
- To allow a variable to hold null, we can declare a variable as nullable string, written as
String?
fun main() {
var message : String? = "Hello world"
message = null
println(message)
}
- If you try to access the length property of the message variable while the message variable is null, the exception will be thrown.
fun main() {
var message : String? = "Hello world"
message = null
var length = message.length
println(length)
}
- To avoid these errors, you will need to check for a null value before accessing the property. Here's the update for the above code in using the length method.
fun main() {
var message : String? = "Hello world"
message = null
if (message != null){
var length = message.length
println(length)
}else {
println(null)
}
}
- We can deal with nulls in the following ways:
- Smart casting - It is used if you add a condition and it allows for treating a variable as not null after you check that it is not null.
- Safe call - It means calling the right side, if the left side is not null.
- Elvis Operator - It is used to provide a default value when a value could be null.
- Not null insertion - The "Not null assertion" is an unsafe option, as it throws an exception when a value is null.
1. Smart - Casting
- Kotlin compiler tracks conditions inside the
if expression
. If the compiler founds a variable is not null or type nullable then the compiler will allow to access the variable.
fun main() {
var message : String? = "Hello world"
//Smart cast
if (message != null){
var length = message.length
println(length)
}else {
println(null)
}
}
- While using
is
and!is
for checking the variable the compiler tracks this information and internally cat the variable to the target type.
fun main() {
var message : Any? = "Hello world"
//Smart cast using is
if (message is String){
var length = message.length
println("The string length is $length")
}
}
- This is done inside the scope if
is
or!is
returns true. - In addition we can use !is for smart cast.
fun main() {
var message : Any? = 67
//Smart cast using is
if (message !is String){
println("Object is not String")
} else {
var length = message.length
println("The string length is $length")
}
}
2. Not null insertion : !! Operator
- The not null assertion (!!) operator converts any value to non-null type and throws an exception if the value is null.
- If anyone want NullPointer Exception then he can ask explicitly using this operator.
fun main() {
var message : String? = "Meercedes Benz"
//using the not null assertion
println(message!!.length)
// Using a null
// The length method we throw an error
message = null
println(message!!.length)
}
3. Elvis Operator
- It returns a non- null value or a default value, when the original variable is null.
- In other words, if left expression is not null then elvis operator returns it, otherwise it returns the right expression.
fun main() {
val str: String? = null
val length = str?.length ?: -1
println("Length of the string is $length")
}
- we can also throw and return expressions on the right side of the elvis operator and it is very useful infunctions. Hence, we can throw an exception instead of returning a default value in theright side of the elvis operator.
val name = firstname?: throwIllegalArgumentsExceptions("Enter valid name")
4. Safe call
- A safe call is when
?.
is used instead of. syntax
between an object and its function or property. - A safe call calls the right side if the left side is not null. Otherwise, It does nothing and returns null.
fun main() {
var str: String? = "Happy Birthday"
println(str?.length)
str = null
println(str?.length)
}
The output of the first pritnln will be the length of our string ,14, the last println will be null.
- We can use the safe call operator with let(), also() and run() if value is not null.
let() method
- The lambda expression present inside the let is executed only if the variable firstName is not null.
val firstName: String? = null
firstName?.let { println(it.toUpperCase()) }
- Here, the variable firstName is null, so the lambda expression is not executed to convert the string to Upper Case letters. Kotlin program of using let.
fun main(args: Array<String>) {
// created a list contains names
var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
// created new list
var newlist = listOf<String?>()
for (item in stringlist) {
// executes only for non-nullable values
item?.let { newlist = newlist.plus(it) }
}
// to print the elements stored in newlist
for(items in newlist){
println(items)
}
}
also() method chain with let()
- If we want to apply some additional operation like printing the non-nullable items of the list we can use an
also()
method and chain it with alet()
orrun()
:
fun main(args: Array<String>) {
// created a list contains names
var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
// created new list
var newlist = listOf<String?>()
for (item in stringlist) {
// executes only for non-nullable values
item?.let { newlist = newlist.plus(it) }
item?.also{it -> println(it)}
}
}
run() method
- Kotlin has a run() method to execute some operation on a nullable reference.
- It seems to be very similar to let() but inside of a function body, the run() method operates only when we use this reference instead of a function parameter:
fun main(args: Array<String>) {
// created a list contains names
var stringlist: List<String?> = listOf("Geeks","for", null, "Geeks")
// created new list
var newlist = listOf<String?>()
for (item in stringlist) {
// executes only for non-nullable values
item?.run { newlist = newlist.plus(this) } // this reference
item?.also{it -> println(it)}
}
}
2. Collections in Kotlin
- A collection usually contains a number of objects of the same type called elements or items.
- In Kotlin collections can be either mutable or immutable.
- Immutable collections supports read-only functionalities and cannot be modified whereas mutable collections supports both read and write functionalities.
- We will look at several collections which include:
- Lists
- Sets
- Map
Lists
- It is an ordered collection in which we can access elements using indices.
- In a list, we can have repeating elements.
fun main() {
var groceries = listOf("carrots","onions","kales")
println(groceries)
}
- The result type is
list<T>
whereT
is the type of the elements in the list. In the code above our lists consists of string hence its type islist<String>
- We add elements to alist using the plus sign (+). You can add a single element to a list or you can add two lists together.
fun main() {
var groceries : List<String> = listOf("carrots","onions","kales")
var numbers : List<Int> = listOf(12,45,78)
println(groceries + numbers)
}
- We can check the number of items in a list using the
size
property.
fun main() {
var groceries : List<String> = listOf("carrots","onions","kales")
var numbers : List<Int> = listOf(12,45,78)
val shopping = groceries + numbers
println(shopping.size)//6
}
- We can use the isEmpathy method or compare the size of a list to zero to check if the list is empty.
- You can get an element at a certain position using the index that is the box brackets.
fun main() {
var groceries : List<String> = listOf("carrots","onions","kales")
var numbers : List<Int> = listOf(12,45,78)
// using the + adding sign
val shopping = groceries + numbers
//using the size property
println(shopping.size)//6
// Using the isEmpty() property
println(shopping.isEmpty())//false
}
- We use the
contains method
orin operator
to check if a set contains a certain element. In the above code we can add the following code.
println("carrots" !in list)//true
println("apple" in list)// false
- With mutable lists you can use methods like add or remove to add or remove a certain element.
fun main() {
val mutableList = mutableListOf("apple", "banana", "cherry")
// adding an element to the list
mutableList.add("date")
// removing an element from the list
mutableList.remove("banana")
// updating an element in the list
mutableList[0] = "apricot"
// printing the contents of the list
println(mutableList)
}
Set
- It is a collection of unordered elements which are unique. It does not support duplicate elements.
fun main() {
val setList = setOf("apple", "banana", "cherry")
for (items in setList){
println(items)
}
}
- With mutable sets you can use methods like add or remove an element. Set preserve the order of the elements.
fun main() {
// create a mutable set
val mutableSet = mutableSetOf<Int>()
// add elements to the mutable set
mutableSet.add(1)
mutableSet.add(2)
mutableSet.add(3)
// remove an element from the mutable set
mutableSet.remove(2)
// check if an element is in the mutable set
val containsOne = mutableSet.contains(1)
// iterate over the mutable set
for (element in mutableSet) {
println(element)
}
}
Map
- Map keys are unique and hold only one value for each key. Each key maps to exactly one value.
- Maps are used to store logical connections between two objects. The values in a map can be duplicate with unique keys.
fun main() {
// create an immutable map
val immutableMap = mapOf(
"apple" to 1,
"banana" to 2,
"orange" to 3,
"pear" to 4,
"grape" to 5
)
// access values in the map
val bananaValue = immutableMap["banana"]
// add a new element to the map
val newMap = immutableMap + ("kiwi" to 6)
// remove an element from the map
val removedMap = immutableMap - "pear"
// iterate over the map using restructuring
for ((key, value) in immutableMap) {
println("$key -> $value")
}
}
- Maps do not allow duplicates so when you add a new association, it removes the old one.
- You can remove certain elements from a map using the minus sign.
- Kotlin supports the restructuring of a map in a for loop.
fun main() {
// Create a mutable map
val myMap = mutableMapOf<String, Int>()
// Add items to the map
myMap["apple"] = 3
myMap["banana"] = 5
myMap["orange"] = 2
// Print the map
println("My map: $myMap")
// Update an item in the map
myMap["banana"] = 6
// Print the updated map
println("My updated map: $myMap")
// Remove an item from the map
myMap.remove("orange")
// Print the updated map
println("My final map: $myMap")
}
- You can add new associations to a map using the bracket and assignment. In addition, we can also remove an association by using the remove method.
💖 💪 🙅 🚩
Daniel Brian
Posted on April 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
githubcopilot AI Innovations at Microsoft Ignite 2024 What You Need to Know (Part 2)
November 29, 2024