Vol 2: How Kotlin eased my life in a Java world
2nd part in a series on how to write idiomatic Kotlin, Read first part here
Mutable (var) and Immutable types (val) in Kotlin
In Kotlin, variables are typically preferred to be declared as val
and borrows these
constructs from other languages.
Consider below example
We have a Robot
Class with 3 properties which are val
by default. We are then creating a new
Robot
object and printing its properties.
Line 6 throws a compiler warning as:
Warning:(6, 5) Variable is never modified and can be declared immutable using ‘val’
Sweet! A language which explicitly encourages immutability. 😍 We can update chappie as val
to
fix this. Read an in-depth explanation of val and var in this
ProAndroidDev
article
The above code looks a bit repetitive and can be improved:
Observe Line 8–12: We are essentially extracting the properties of chappie
the robot every time
and then printing them. This could be replaced by using a with
clause to import things into scope.
Maps
So, what is the most common data structure we all use as programmers. List or Map right?
The simplest way of creating a map, similar to Java is shown below. Here we are using a Hashmap to create a key value mapping of strings. Pretty standard stuff.
// Old Java way val map = HashMap<String, String>() map.put("Stack", "Jeff") map.put("Overflow", "Joel")
However, In Kotlin we can do this in a slightly more readable way. Folks coming from python would recognize the use of indices to assign a key value in an existing map like below:
// We can replace .put with indices to make it a little more nicer map["Another"] = "Jared" map["Hello"] = "World"
We can also represent map as a builder with just pairs of key values as below
// Or even create a map with use of builders such as below val anotherMap = mapOf( Pair("Stack", "Jeff"), Pair("Overflow", "Joel") )
However, Even this is not enough. Kotlin gives a much more readable way of representing the above
construct by the use of a to
infix function. This mostly reads exactly how you would think about
this in your mind.
val finalMap = mapOf( "Stack" to "Jeff", "Overflow" to "Joel")
So now you know of a very clean and concise way of creating a map. However most often than not, you might want to iterate over this map to achieve some functionality.
Here is the traditional way of doing this:
// Iterate over map the old way for (entry in map.entries) { println("${entry.key} -> ${entry.value}") }
While this works, There is a slightly better and concise way of representing the above common logic,
We can directly unpack the result of .entries
into a Pair and then use it. Much more simpler!
// Unpacking the result values and using it directly for ((key, value) in map.entries) { println("$key -> $value") }
Lifting assignments out of if elses or when blocks
How many times we have written simple code like below: Based on a given status code in an API response, We decide and do something with the result. Either pass/fail an assertion or in this case just report the result out.
The above code can be idiomatically written in kotlin by lifting the assignment out of if and the
last statement in conditional (if or else) is automagically returned without need to mention a
return statement and observe that the type of anotherResult
is also type inferred.
Just like if else, we have the powerful when
clause as a highly powered switch case statement.
Let’s assume you have a simple function which depending on different status codes returns what those status code’s mean actually.
Now while this code works, It’s quite noisy on the eyes and by now you might have got an idea that there is a much better way of representing this idea in kotlin. We can lift the return out of when in order to avoid typing this all the time for every case and also group related conditions together like below:
fun apiResponsesIdiomatic(response : Response) : String { return when(response.statusCode) { in setOf(200, 201, 204) -> "Success" 300 -> "Redirection" 400 -> "Bad request" 401 -> "Unauthorized" 500 -> "Server error" else -> "Unknown" } }
Also, we could even assign this expression to the function just like we did earlier with the if else statements:
Nullability
Kotlin aims to eliminate Null pointer exception which is also referred to as the Billion dollar mistake, by introducing a set of explicit constructs to mark some type as either Nullable. Also by default all types in Kotlin are non nullable.
Consider above code:
Do you notice a ?
after String in the argument of readMeByChar
function (Line 5), This implies
that text
can be nullable and any attempt to perform an action like getting length of the string
would result in a compiler warning. Since text can be null, we need to explicitly handle for the
null case and that is where kotlin really shines.
Kotlin forces you to consider nullability as a first class problem and always write code considering it.
In above code text
has been marked nullable. What if we want to take some action based on if text
has a value or is passed as null? In Kotlin, we have a special operator called Elvis operator
?:
We can write a statement like below which translates to:
If text is not null then return its value, However if it is not then just assign a default value to the constant s. This is a concise way of representing this logic and saves us the trouble of writing an if else statement.
val s = text ?: " "
This is a big topic and can lead to a bit of initial discomfort while understanding. Please read the official docs to grasp this even further
Functional style
Kotlin is a multi paradigm language and supports a really nice functional style of writing code.
Let’s see an example of this below:
We have a simple function which takes nos from 1 to 100 and then if the no is divisible by 16 then prints hex value of the same.
Essentially the for with the if condition appears like a filter map right? And so we can easily rewrite this as below with filter and map accepting a lambda function.
val list = nums .filter { it -> it % 16 == 0 } .map { it -> "0x" + it.toString(16) }
Also it ->
is optional here and we can remove it to get a bit more conciseness
val list = nums .filter { it % 16 == 0 } .map { "0x" + it.toString(16) }
Comparing this with the earlier construct with for and if else blocks, this is much more expressive and clearly communicates the intent.
Simple considerations
Raw Strings
In Kotlin, we can enclose long strings with a Triple quotes """
and have it treated as a raw
string just like python and allows us to easily write a sane string instead of having an army of
+ and "\n"
to represent even the most basic of JSON structures.
// Awesome val rawString = """ Tell me and I forget. Teach me and I remember. Involve me and I learn """.trimIndent()
// Nightmare in Java to even write a simple JSON in a .java file String hello = "{\n" + " \"Foo\": {\n" + " \"Bar\": \"Woah\"\n" + " }\n" + "}"
Imports
When working in any professional setup, It can so happen that there are multiple classes with the same name that you might want to use in the same class. In Java 8 we have to resort to referring to a fully qualified path for one of the classes.
Well no more. In Kotlin you can simply alias the class with an as
keyword. This makes the
consuming code so much more readable.
import foo.Bar // Bar is accessible import bar.Bar as bBar // bBar stands for 'bar.Bar'
Does this list end here?
Well no, Kotlin has lot of goodness hidden which is left to be explored and this series is probably just the tip of the iceberg.
If you are still a skeptic after reading this. I would just encourage you to take a leap of faith and try it out. It’s so much fun! I promise.
I have so far not encountered even a single Dev who is criticizing Kotlin’s feature and for most of us it brings a breath of fresh air as a multi paradigm language in the JVM universe with modern languages.
And this wraps up the two part series. If you found this useful. Why not share it with a friend or colleague. Until next time. Happy coding. Cheers!
Comments