Understanding Kotlin code
Even as an experienced Java developer, it was not easy for me at first to read and understand the many Kotlin examples and existing Kotlin code - especially such code, which made use of several of the many Kotlin features simultaneously.
But struggling to understand even short code examples of a new language - although you have been working with Java, for example, successfully for many years - is a major hurdle trying to familiarize yourself with a new syntax.
In order to clear this hurdle as soon as possible, I would like to venture with you “the jump into the deep end” right away and carefully walk you through a Kotlin example. After that, I hope you will have all the tools under your belt not to be deterred by Kotlin (any longer), but rather enjoy a flying start.
Although it is relatively simple Kotlin code, even for Java connoisseurs it is not necessarily straightforward, especially because it uses and combines several Kotlin language constructs all at once:
val strings = listOf("Hello", "World", "!")
val lengths = strings.map { it.length }
But very soon you will understand exactly what this code does and - I am convinced - be thrilled, what Kotlin has to offer and does so much better than Java.
Declaring variables
Let’s start with the very first line.
It is a declaration of the variable strings
, followed by its initialization.
Standing out right away is the val
keyword, which does not (yet) exist in Java.
Also, the declaration of the variable’s type seems to be missing. How is this even possible, given that Kotlin is a strongly typed language?
To get to the bottom of these questions, let’s have a look at a similar example in Java:
Following is a declaration of the variable i
of type int
in Java, which is initialized directly with the value 42
:
int i = 42;
In Kotlin, the same variable declaration looks like this:
var i : Int = 42
This looks a bit “reversed”, compared to Java.
Variable declarations in Kotlin do not begin with the type of the variable, but always with the keyword var
or val
(the difference between these two will be explained shortly).
This is followed by the name of the variable.
Finally, to specify the type, it is immediately noted after a :
.
If you like, you can initialize the variable with an assignment just like in Java, as in the example.
var
or val
?
What is the difference between declaring a variable with var
or val
?
Basically it’s the same as the one between
int i = 42;
and
final int i = 42;
in Java.
Variables that are declared with var
(variabel) can be assigned a different value later - for those with val
(value) this is forbidden.
The same applies here as in Java: only the variable itself may be immutable.
If the object referenced is mutable, it can be altered by appropriate methods/properties - no matter if val
or var
.
Type inference
If you look closely, you will notice that we did not specify its type when declaring strings
in the original example.
In most cases, this is not necessary.
Rather, the Kotlin compiler can often recognize the type of a variable via the so-called type inference, if an initialization follows the declaration directly.
That’s why it’s enough to write
var i = 42
or
var map = HashMap<String, Collection<Long>>()
instead of Java’s
HashMap<String, Collection<Long>> map = HashMap<>()
which only really became readable with the introduction of the diamond operator (<>
) in Java 7.
In the example discussed in this chapter, the Kotlin compiler automatically determines that strings
is of type List<String>
.
This is inferred from the return type of the standard function listOf
, which is List<T>
, where T
is the type of each element.
Thus, thanks to the smart Kotlin compiler, one is usually spared from manual type specifications.
Of course, you can still do this, for example, to widen the type to Collection<String>
, or simply because it meets your personal taste (or the project’s style guide).
Speaking of listOf
, even though I have not even mentioned this ‘built-in’ function, as a Java developer you can probably guess its signature - in Java, it would look something like this:
public static <T> List<T> listOf(T ... elements)
as a static method embedded in a utility class - let’s call it CollectionUtils
.
We could have imported it statically (not shown) to call it without the CollectionUtils.
prefix.
In Kotlin, this is done quite similarly. But the signature is syntactically a bit different - and Kotlin even allows us to declare functions at the top level, without any class “surrounding” it!
Declaring functions
Spoiler alert: this is what the declaration of listOf
in Kotlin really looks like:
public fun <T> listOf(vararg elements: T) : List<T>
Let’s go through this step by step:
public
is no surprise for Java professionals, so far so good.
Kotlin is lots of fun
But already the second keyword lets even experienced Java users down, because there is no fun
in Java (well, at least not as much as in Kotlin ;-) ).
Just like variable declarations initiated with their own keywords (var
or val
), functions are also declared with a specific keyword.
Its name fun
stands, unsurprisingly, for function.
The return type is last
As we have seen in variable declarations, their type is noted at the very end, after their name.
This also applies to functions.
Their return type follows the function name (and any parameters), separated by a :
.
So in our listOf()
example it is List<T>
.
But one after the other: following fun
there is a generic type declaration <T>
- exactly how it is done in Java.
The fact that the name of the function (listOf
) is next, is likewise no deviation from the Java syntax.
But several of these follow within the parentheses:
If we omit vararg
for a moment and look at the declaration of the (single) parameter elements
, we notice the striking resemblance to a common variable declaration: first the name, then a :
, followed by the type.
Only val
or var
is missing - simply because in Kotlin function parameters are always implicitly val
, hence “final
”.
(This is one of the numerous opportunities that Kotlin uses to implement best practices, which came up over the years and which Java has never been able to enforce due to backward compatibility.)
The vararg
keyword is simply Kotlin’s substitute for the ...
known from Java, which you would note down between the name and the type of the parameter.
As already said, all that follows is the return type of the function, separated by a :
, and with that the function is fully declared.
Top-level functions
Do you remember that I mentioned you may declare functions without an outer class in Kotlin?
So you can do without the “popular” utility classes like CollectionUtils
, which are nothing more than containers for static methods.
listOf
is just such a top-level function provided by the Kotlin standard library in the kotlin.collections
package.
So its fully qualified name is kotlin.collections.listOf
.
But thanks to static imports, which you already know from Java, you rarely need the fully qualified name, and just listOf
is enough.
Of course, you can also declare functions on package level yourself and benefit from this feature.
Named parameters, default parameters & more
Functions in Kotlin offer many more useful features that you might know from other languages. For example, when calling a function, you can reference parameters by name or save a lot of method overloading by using default values for arguments.
But since we just started to get to know Kotlin, and further details would throw us off track at this point, I’d like to refer you to the further reading of this book for more information, or to the official documentation of Kotlin functions, if you want to know more right now. Everyone else, please follow me to the next line of the example:
val lengths = strings.map { it.length }
Higher-order functions
Now it gets really interesting, and Kotlin flexes its muscles.
If you look at the example, you will immediately notice the val
keyword followed by a variable name.
So we are basically dealing with a variable declaration, which is then defined by the =
and the code that follows.
Now recall that strings
is a list of Strings
:
val strings = listOf("Hello", "World", "!")
Now we call a method called map
on it - but that looks a bit strange for Java eyes.
And you might think I mistyped curly braces for parentheses.
But what looks like a typo turns out to be a very common and extremely elegant Kotlin idiom.
But to explain this, I have to introduce you to higher-order functions first, if you do not already know them from other languages.
A higher-order function is a function that returns and/or accepts other function(s) as arguments.
This means that you can pass one function to another as an argument in Kotlin - and that is exactly what we do in our example.
The map
method is part of strings
(rather of its type List<String>
) in Kotlin.
(More specifically, this is a so-called extension function of the type Iterable<T>
- such extension functions are another very clever concept in Kotlin - but to introduce them right here would be too distracting, so I ask you for your patience once again.)
So you can call map
on strings
.
If you have not already encountered it in Java 8+, you’ll ask yourself, “What’s map
doing anyway?“.
As the name suggests, the method maps or “transforms” one value to another.
More specifically, calling map
on a collection transforms each element in it, creating a new collection consisting of the same number of elements.
Each of them, however, converted to a new value according to the rules of map
.
Next question: “What does ‘transformed’ mean?”. Good question!
That’s exactly what we can determine with map
.
To do this, map
accepts another function, which in turn takes exactly one element of the list (in our case, a String
, since we call map
on a list of Strings
) as an argument, and returns “something else”.
For example, we could write a simple transformation function that accepts a String
and returns its length (Int
).
That’s easy, and with the knowledge from the previous sections, you may even be able to formulate it yourself, but certainly understand it:
fun getLengthOfString(str: String) : Int {
return str.length
}
The only stumbling block may be length
instead of length()
.
Why this is will become clear in the chapter about properties.
So far so good.
Now all we have to do is pass this function to map
.
map
will then go through all the elements in strings
and call our transformation function for each of them.
To do this, it passes the element - that is, a String
- to our function and accepts its return value, which is the passed String's
length - an Int
.
But how exactly do we do that?
As Kotlin supports higher-order functions, functions (in most cases) can be treated the same way as ordinary variables. That is, for example, you can assign a function to a variable:
val getLengthOfString = fun(str: String) : Int {
return str.length
}
Compared to a common function declaration you’ve already encountered, the syntax looks a bit different:
The function itself is “anonymous” because we did not name it after fun
.
Instead, we declare a variable called getLengthOfString
.
Which is of type function!
So now we have a reference to a function in getLengthOfString
- that’s something Java does not offer.
Once we have done that, we can pass that reference to map
as an argument:
val lengths = strings.map(getLengthOfString)
This looks strange to Java developers, because getLengthOfString
is not a simple variable, but a reference to a function.
However, this is how we can pass getLengthOfString
to the map
function, which in turn transforms every element in our strings
list, resulting in a new collection of Ints
representing the length of each String
in strings
.
But what about the strange curly braces from the example?
To introduce those, we need to inline our transformation function:
Like any other variable, we can inline getLengthOfString
by replacing that reference directly with its true value - in this case, the transformation function itself.
Sounds complicated? It really is not - and this is how it’s done:
val lengths = strings.map(fun(str: String) : Int {
return str.length
})
As you can see, we have literally replaced the reference getLengthOfString
with its actual value.
But since the function is anonymous anyway and can no longer be referenced “from the outside”, we can write it even shorter by using a lambda expression:
val lengths = strings.map({ str: String -> str.length })
These look very similar to those of Java: our lambda expression accepts a single argument str
of type String
, retrieves its length
(in case of doubt please just think of the empty parentheses: str.length()
) and returns that (Int
).
As you can see, we have “forgotten” to explicitly specify the return type Int
.
And actually we don’t need to: the Kotlin compiler infers the type automatically, because it recognizes that str.length
returns an Int
- hence so does the entire lambda expression.
But the compiler can do even more: although we do not explicitly tell it that the argument str
is of type String
, it does not get confused in any way and still recognizes that “automagically”:
val lengths = strings.map({ str -> str.length })
But how does it do that?
Well, the compiler knows that strings
is of type List<String>
.
So the individual elements iterated over by map
are of type String
- so is the argument str
.
How this works in detail will become clear later in the section on extension functions - but at this time, we are still after the curly braces.
Fact is: we can omit the manual type declaration of str
.
But we can take this another step further. If there only is one single argument, whose type is known, you can even omit its declaration altogether:
val lengths = strings.map({ it.length })
Ok, now the declaration is gone - but a suspicious it
took its place!?
First, we omitted the type of str
and just declared the name of the parameter to be able to reference it.
Now that we even dropped the name, the Kotlin compiler steps into the breach and simply names the single parameter it
, so that we can access it.
it
is thus always the name of the one and only parameter, if you omit its declaration.
We are almost done and just need to get rid of those parentheses!
Again, the Kotlin compiler helps us: It allows us to note the last argument (in our case the one and only) outside the parentheses:
val lengths = strings.map() { it.length }
Frankly, yes, that looks a bit weird if you have a Java background.
It appears to be a call to map
with no arguments, followed by a misplaced code block.
But in reality it is the call of map
with exactly one argument - namely the lambda Expression {it.length}
- noted outside the parentheses, just like Kotlin allows.
It becomes more readable, if you know that there is a special rule if there is exactly one argument: in this case you can simply omit the parentheses.
This way, not only the parentheses disappear, but hopefully so does your possible confusion:
val lengths = strings.map { it.length }
Bingo!
We have reconstructed the call to map
from the original example.
It gets passed exactly one single argument, of type “a function that expects a String
as an argument and returns an Int
”.
map
then iterates the strings
list, calls the passed function on each element (String
), adds the returned Int
to a new, internal list, which is then finally assigned to lengths
.
Phew!
As you can see, though unfamiliar at first, Kotlin’s code is very legible and compact once you get behind the secrets of the many powerful and clever features that Kotlin’s creators have built into their language.
Thus, now the “jump into the deep end” is done, and I hope Kotlin has become much easier to read for you now.
In the further course of the book a lot more highlights and special features of Kotlin await you.