More Kotlin Lovin’
As I learn about the differences between Kotlin and Java, its older brother, I thought I’d collect some of the things I liked in a short post. It quickly spilled over into two short posts. You can read the part one here.
Less Null Pointers
Kotlin supports stronger type safety than Java including the support for non-nullable types. This means that you should encounter less of the dreaded NullPointerException
issues (NPEs). Also you will need a lot less null checking throughout the code base.
Kotlin does this by defining what types can or cannot hold null and by default null is not allowed. For example, trying to define a string as null will give a compile time error.
// this will result in a compilation error
val myString: String = null
The above results in Null can not be a value of a non-null type String
.
If you really need a variable to be null you can define it with var myString: String? = null
, the ?
appended to the type tells the compiler that it can be assigned null.
Smart Cast
In Java, if you are unsure that a cast is going to work, perhaps the you need to decide the exact implementation of an interface an object is, you have to first check the object and then do the cast. Kotlin provides a better way with its smart cast ability.
Rather than the instanceof
keyword, you can check the object type with is
. Having done the check, and assuming the object has not undergone any changes, you can omit the cast. For example:
interface Shape;
class Circle(val radius:Double) : Shape
class Square(val side:Double): Shape
fun area(shape: Shape): Double {
if (shape is Circle )
return shape.radius * shape.radius * Math.PI
else if (shape is Square)
return shape.side * shape.side
else
throw RuntimeException("Unknown shape")
}
fun main(args: Array<String>) {
val circle = Circle(1.0)
println(area(circle))
val square = Square(2.0)
println(area(square))
}
In line 7, there is no need to cast the shape
object to a Circle
before accessing the radius
property. Kotlin’s compiler has done this magic for you in the background. It makes sense; you have already protected against the object not being the correct type, so Kotlin is removing some boilerplate that makes code difficult to read.
Extensions
I won’t go into too much detail about how this works (think static utility class) but Kotlin allows you to write functions that extend the functionality of existing classes, both Java and Kotlin.
For example, you might be using a class, Person
, with firstName
and lastName
properties and want to extend it to add a function to return fullName
but don’t have access to the underlying code. Extending the class and adding is one option, but to demonstrate function extensions, let’s add this to our code.
fun Person.fullName() = firstName + " " + lastName
The Person
object will then have the fullName
function available to it, at least within your code. This has allowed Kotlin to extend many of the existing, familiar objects in the Java ecosystem without having to reinvent things.
Same Java Collections API
There is no need to learn another collection framework when moving from Java to Kotlin. All the familiar Set
, Map
, List
and others are still available as are their implementations. Kotlin does not change the underlying objects or how they are represented.
This is convenient because it helps with interoperability with Java. Since the underlying objects are the same, there is no need to map the objects from one type to another whenever passing either way between the two languages. One of the exciting things that Kotlin gives is extensions to this set of familiar objects, providing functionality that we would usually have to get from a third party library.
The setOf Operator
There is one thing about the Java Collection API that most developers will agree is frustrating: constructing an instance from a number of items. There are a number of ways to do so, and things have improved with the evolution of the language, but it all feels a little clunky, especially when compared to other languages.
Kotlin comes to the rescue with a series of operators in the form setOf
or mapOf
to quickly construct the collection objects. Here is how to create a HashSet
:
val mySet = hashSetOf(1, 2, 3, 4, 5)
As always with Kotlin, the type can be inferred to be a HashSet<Integer>
based on the assigned value. It gets better; maps are also easy to create:
val myMap = hashMapOf('a' to "alphabet", 'b' to "bicycle", 'c' to "cat")
Infix Functions
In the previous example the to
operator was used to create the Pair
objects required to create the map. This is an example of an infix function. Infix functions can only have a single parameter and it cannot have a default value. Furthermore, they must be denoted by the infix
keyword.
The infix notation makes it easier to read the map construction above, but infix functions can also be used using the standard dot notation. For example, the a
pair could be written as 'a'.to("alphabet")
. In this case the to
is written as an extension function.
Decomposing Pairs
In the map construction example, the map is being created from a list of Pair
which holds two generic objects. It is possible to easily decompose the Pair
into its constituent parts.
val myPair = Pair(123, "favourite number")
val (num, desc) = myPair
This decomposition will be regularly used when using for loops in Kotlin. The standard Java for loop, with its three part definition is not available in Kotlin. At first this appears to remove the ability to use index tracking without keeping track of the index outside of the loop.
Kotlin solves this problem my using an object extension and Pair decomposition. The extension function withIndex
allows a for-each type loop to change to a Pair that can be decomposed. An example will make it clear:
val numbers = listOf(10, 15, 34, 42, 77)
for ( (index, number) in numbers.withIndex()) {
println("At position $index is number $number")
}
There are a few more interesting and exciting things I have learned about Kotlin so check back here soon for the final part of why Kotlin is taking over from Java.