Vol 1: How Kotlin eased my life in a Java world
Part 1 of a series of posts on why coding in kotlin makes so much sense over java.
Hello wonderful people,
Over my 8 years of experience as a coder/Quality engineer the language which gave me the comfort of feeling at home was and still is “Python” 🐍 till date.
It allowed me to truly solidify concepts in programming as well as gave me the freedom to write concise code quickly to turn my ideas into executable logic and it was a ton of fun.
The dynamic nature/duck typing, No worries about types, use of higher order functions and focus on writing readable and explicit code while solving problems was something which i really adore in the language.
When I switched jobs a year back. Most of the test automation code in my new company was written in Java. And even though I quickly started learning and became fluent in the language, Java and its constructs felt too verbose and syntax heavy to me. I found myself spending more time worrying about types all the time rather than actually getting code out and though i was productive with it in a short amount of time. It never felt ease like Python.
Until i discovered Kotlin close to 3 months back. And this is the story of how Kotlin changed my life in a world full of Java code. 🙂
With that pretext. Let’s dig into what are some of the features of Kotlin language which simplify our life as a programmer in the JVM world.
In researching for this post, I went through multiple blogs and most of these examples are from a talk given at Google IO by none other than the lead kotlin language designer Andrey Breslav 🙌
Classes
1.1 Concise classes with automatic Getters and Setters
One of the trademark constructs in Java is a bean/POJO which on a high level is primarily a bunch of Getters/Setters and constructors to represent an object.
Let’s say we wanted to represent a Person having a name and age. Below is how we would write this in Java.
And here is the equivalent code in Kotlin: 😆
Kotlin allows us to define name and age as properties in the constructor itself with automatic getters and setters generated. Pretty slick right?
Also Kotlin allows to directly access the properties via their name. Avoiding the whole getProperty() or setProperty() syntax. So much less clutter.
1.2 Data Classes are cheap and concise
Classes in Kotlin are cheap to create and can be created in any .kt file without requiring the
File name to match the class name itself and we have a modifier called as data
which unlocks so
much additional features out of the box.
Let’s see an example of this:
Let’s examine the above code:
- parseName is a function that takes a full name and returns a list of separated first and last names
- In main function we are unpacking them and asserting if the function did its job correctly.
Another cool feature to note: Kotlin has string interpolation so we can directly refer to variables inside a string using
$
symbol likeprintln("$first $last")
The above code is quite ugly since we are abusing a list to store and return the result and using the weird slicing syntax to get elements.
We can quickly refactor this code and replace list with a class with 2 properties. With that, we
could quite easily use dot
to access first name and last name.
Sweet, the code is already cleaner. However if we run this code we observe below output.
Jane Doe
Equals does not work...
Process finished with exit code 0
However we that the two objects are not equal. In Java to make this work we would have to override
and create equals(), hashCode()
and other convenience methods.
In Kotlin we can directly use data classes which generates the above methods and other convenience functions for us.
data class FullName(val first: String,
val last: String)
If we were to run the same code again this would work just fine.
Functions
1.1 Functions are first class citizens
Kotlin like many modern languages treats functions as a first class citizen. Meaning functions do not need an enclosing class.
Lets understand this with an example.
Below is a simple StringUtils class having some convenience methods to get the first word in a string.
This is similar to how we would write this code in Java. Though this is definitely not idiomatic.
Surely we can do better here.
Turns out we can, Observe the below code for a second. We obviously moved the functions outside the
class and removed StringUtils class itself which was obvious but where is the overloaded
getFirstWord
method?
Turns out Kotlin supports default parameters and so we could always give separator a default value of
" "
and avoid having to write multiple overloads and the code already looks so much concise.
Obviously i could always pass specific separator value as needed using the named parameter syntax as below:
val first = getFirstWord("Jane,Doe", separator = ",")
1.2 Extension functions
Wouldn’t the above code read so much nicer if we were able to something like below:
"Jane Doe".getFirstWord()
Turns out Kotlin provides a special construct to extend the functionality of a class using functions called extension functions.
All we did is provided the class which we want to extend, in this case String
followed by the
function name, Since we are working off a string we could remove the word
param and instead just
refer to the string using this
and voila! with this we could achieve our objective and attach new
functionality to an existing class which might not be in our control (In this case the Standard
String class)
Kotlin also supports extension properties.
How do we do this?
Essentially what we can do is define a new property on String class like
val String.lastWord : String
and then define a get() which returns us the required value.
val String.lastWord : String
get() {
return this.split("")[1]
}
We can use this just like a normal property with a clean syntax like "John Doe".lastWord
fun main() {
val last = "John Doe".lastWord
println("Last word (Using extension properties) $last")
}
Here is the final code with all the above changes made:
1.3 Inner functions
Kotlin supports local functions without any other ceremony around it. Lets dig into this with an example.
Generally when working with data whether it be in JSON/YAML format we typically might need to use recursion to walk through in the hierarchy.
In the below code:
We have a simple Element
base class and a Container
class which can wrap multiple Element
objects. Also we have a Text
class which inherits from Element and holds a text.
If we want to extract texts from all the leaf nodes, then we need to write a function as
extractText
which takes an element and then either adds it’s text to a StringBuilder
object or
if it’s a container type then it recurses till it has walked through all elements.
This looks good right? We are using top level functions, however we can improve this code a lot.
If you observe extractFunction()
function is only being used inside the extension function on
Element
so its a good candidate to make it local.
Let’s move it as a local or inner function and also move out StringBuilder
and make it a local
variable.
Sweet.
We can take this a bit further, We can inline val text = e
since we are just assigning the
variable to text and using it. Also we can convert this if else
statements into a when
statement
which is more nicer and provides a useful is
clause.
Let’s see how it looks like when these refactorings are performed.
This is so much more intuitive than the noisy if else
chain of statements.
Can we improve something more on this?
Turns out we can.
for (child in e.children) {
extractText(child)
}
We need to iterate in all elements and then recursively call the function right. This could be very easily replaced with below:
e.children.forEach(::extractText)
The overall function looks like below. We have already made this so much simpler.
However if you see there is an annoying else
clause in the when statement. Since we know exactly
how many classes inherit from Element, we can improve this by making the Element
class as sealed
sealed class Element
And now we can get rid of the else clause since we know that Element
subclasses are known
The final code looks like below:
Just by applying idioms of Kotlin language we have been able to reduce the code complexity and increase readability at the same time.
And that’s it for this post.
I would talk about other cool features in a future post as well as how I implemented these in my own api tests. Stay tuned.
If you find this article useful or informative in any way, why not share it with a friend or colleague.
See you all in the 2nd part of this series! Until then, Happy testing and coding! Cheers! ☮️
Comments