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.
Top comments (0)