Kotlin for Java Developers

27 January, 2022 |  Vladimir Djurovic 
img/kotlin-java-logo.png

In recent years, Kotlin has seen extremely fast growth, especially in the area of Android development. Ever since Google announced that Kotlin will be officialy supportedd language on Android, it’s usage has expoloded. And not only in Android, it has see wide adoption in other fields where Java and JVM were traditionally used.

A lot of developers cite Kotlin’s modern features and syntax sugar as gigantic productivity boost. It has been dubbed “Java++” or even “Java killer”.

In this post, I’ll outline some major major features that Kotlin offers, to make it more familiar to Java developers.

Table Of Contents

Kotlin-Java interoperability

One of the most compelling features of Kotlin is that it’s almost 100% inter-operable with Java. This means that you can call Java code from Kotlin and vice-versa. You can use all the great Java libraries you are used to in your Kotlin project, just like you would do in Java.

In addition, the tooling is the same. You favorite build tool will work with Kotlin (Maven, Gradle). You can use your favorite IDE to write Kotlin code. You can deploy Kotlin code to JVM.

Variables

Unlike Java, in Kotlin there is a distinction between mutable and immutable variables. Immutable variables are denoted with keyword val. They must be initialized at the point of declaration, and their value can not be changed later.

val x : Int = 1 // correct declaration
val y : Int     // compiler error, must be initialized

x = 3          // error, value of x can not be changed

If you are used to C-based language like Java, this declaration must seem weird. In Kotlin, variable type is declared after variable name, separated by :. This also goes for method parameters, the type goes after the name.

The other kind of variable are mutable variables, denoted by use of keyword var. Their value can be changed freely.

var mutableVariable : String = "text" // this variable can be modified
mutableVariable = "different text"

var inferredVariable = 3 // no type declaration

Note that in Kotlin you don’t need to declare the type of the variable. Instead, the type can be inferred from the value assigned to the variable.

Late initialization

Generally, in Kotlin you need to initialize the variable where you declare it. This may not always be achievable or desirable, so Kotlin support late initialization using lateinit modifier.

This modifier allows us to deffer variable initialization till the time we need to use it:

lateinit var text : String // not initialized
.....
text = "some text"
print(text)

Note that we did not need to initialize variable text with a value, since weused lateinit. But, it is important to understand that variable needs to be initialized before it is used the first time. Otherwise, you will get an error.

In our example, we set the value of the text before we use it in print function.

Null safety

One of the most prominent features of Kotlin is null safety . Basically, the compiler will report an error when ever there is a possibility that null can appear instead of real value. This is a great help in avoiding NullPointerException errors.

By default, variables in Kotlin do not allow null values. You need to explicitly declare that your variable allows null values.

var nonNullValue : String = null // this is not allowed
var nullAllowed : String? = null // variable allows null values

The way you do this in Kotlin is to append ? to variable type. This denotes that null values are allowed for a variable.

Constants

Constants in Kotlin are denoted using keyword const. But, unlike Java, constants in Kotlin don’t need to be defined within a class. They can be defined in top level, so they can be accessed globally:

// file constants.kt
package my.package
const val MY_CONSTANT = 1

To use this constant in any other file, simply import it:

import my.package.MY_CONSTANT
...
var x = MY_CONSTANT

Functions

Functions in Kotlin are first-class objects. They can be used as parameters to other functions, or can be returned from other functions.

Function definition in Kotlin is a bit different from Java, but it follows the same basic principle. This is a sample function definition:

fun doubleValue(value : Int) : Int {
    return 2 * value
}

Function is denoted with keyword fun. As with variables, the return type of function and types of parameters are defined after the name.

Unlike Java, functions in Kotlin do not need to be defined within a class. You can simply define your functions in a file and import them where ever you need to use them:

// file functions.kt
package my.functions

fun double(x : Int) : Int {
    return 2 * x
}
....
// file usage.kt
import my.functions.double

var x = double(3) // call the function

Named arguments and default values

Kotlin functions can have default values for their arguments. This allows us to call the function without specifying all parameters. Consider this example:

fun foo(text : String, number: Int = 1) : String {

}

// call function without number, default is used
var output = foo("some text")

A complement to this feature are named arguments. This allows us to further limit the amount of data we need to pass to the function.

Consider the case where you have a function with a number of arguments of the same type. It could be difficult to remember which arguments is in which place, which can lead to confusion or bugs. Let’s see an example:

fun person(firstName: String = "John", 
            lastName: String = "Doe",
            middleName: String = "Joe") {

}
// call the function
person(firstName = "Tom", middleName = "Jones")

This makes calling functions with a lot of arguments much easier.

Classes and enums

Like Java, Kotlin is object oriented language. Class definitions will be familiar to Java programmers, since it uses the familiar class keyword:

class MyClass {}

Unlike Java, there is no new keyword in Kotlin when creating an object:

val myClass = MyClass()

Constructors

Now we’re coming to a difference from Java. In Kotlin classes, there are several ways to initialize an object:

  • primary constructor

  • initializer block

  • secondary constructors (can be more than one)

Primary constructor is declared right in the class header:

class MyClass constructor(name: String, surname: String){
    val fullName = name + surname
}

When object of this class is created, fields are initialized in the order of declaration. If there are any initializer blocks, they are executed as well:

class MyClass (name: String, surname: String) {
    val fullName = name + surname

    init {
        println("Initializer block called")
    }
}

In addition to this, primary constructor can be used to declare and initialize class fields:

class Person(val firstName: String, val lastName: String, age: Int) {

}
// create object
val person = Person("John", "Smith", 25)

Secondary constructors are declared inside class body. They allow for different ways to initialize a class:

class Person(val name: String) {
    val age : Int

    constructor(name: String, age: Int): this(name) {
        this.age = age
    }
}

Note that, if class has a primary constructor, secondary constructor must invoke it in order to fully initialize a class.

Inheritance

Class is in Kotlin are final by default, meaning they can’t be extend. In order to allow class to be extended, it must be declared with an open keyword:

open class Base {
    open fun foo(){}
}
// extend the class and override method
class Derived : Base {
    override fun foo() {
    // override the method
    }    
}

In order to override a method, it must also be declared with open keyword in base class. In derived class, method override must be denoted with override keyword.

In Kotlin, it is also possible to override properties, not just methods. The principle is the same:

open class Base {
    open val myField  = 1    
}

class Derived : Base {
    override val myField = 5
}

Data classes

Data classes in Kotlin are equivalent to Java POJOs. Their main purpose is to hold state information. In Java, this often leads to large classes with a lot of boilerplate, which are tedious to write and maintain, even with tools like Lombok.

The main problem with Java POJOs is that for every fields, you need to generate getter/setter pair. In addition, these classes need to implement hashCode/equals/toString methods in order to be fully compliant objects.

Kotlin’s data classes remove the need for boilerplate. You just declare class fields, and all additional methods are generated automatically by compiler.

data class Person(val firsName : String, 
                  val lastName: String,
                  val age: Int) {

}

This is it! No need for any more code. In order to access any of the properties, you just use dot notation:

var person = Person("Joh", "Doe", 25)
var fullName = person.firstName + person.lastName

Behind the scenes, Kotlin compiler generate getters and setter for each property. If you want to customize it, you can explicitly define a getter or setter for any property.

Note: Data classes are a lot similar to Java record classes, introduced in Java 15.

Objects and companions

Object expressions in Kotlin define anonymous classes, eg. classes that don’t need to be explicitly declared with class keyword. They are good fit for one-time use, where defining entire class would be an overkill.

In Java, the closes equivalent would be anonymous inner class, but object is far more powerful construct. Let’s see an example in action:

var x = object(val prop1 = "hello",
                    val prop2 = 5)
println(x.prop1) // will print "hello"

On object declaration can be inside a class. This is often used to mark a companion object:

class MyClass {
    companion object Inner {
        fun foo() : String = return "foo"
    }
}
// call function foo
MyClass.foo()

If you look more closely into this, it very much resembles calling static methods in Java classes. Kotlin does not have static keyword concept, so companion objects are used in that place.

Note: If you you look at the definition of the foo() function, it might seem weird. But, it is a shorthand way of writing simple methods in Kotlin.

Control flow

Kotlin has two main constructs for conditional checks, if and when.

Keyword if is used exactly as in Java, but it can also be used as an expression:

var x = 5;
if (x < 10){
    // do something
} else {
    // do something else
}
var y = if (x < 10) x else 20 // assign value to variable y

Using if as an expression is pretty straight forward: if condition evaluates to true, assign value to variable, otherwise assign the value from else branch.

The equivalent of Java switch keyword in Kotlin is when. Semantics is the same, although the syntax is a bit different:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
}

// when can also be used as an expression
var a = when(x) {
    "2" -> 2
    "3" -> 3
}

Just like if, when can also be used as an expression. It will assign a values from the matching branch to a variable.

Loops

Just like Java, Kotlin provides familiar types of loops, namely for and while loops. The syntax is almost the same, so any Java programmer will feel right at home:

for(item in collection) {
    // ...
}
while(x < 5) {
    // ...
}
// iterate over index
for(i in array.indices) {
    println(array[i])
}

As you can see, these look exactly as Java loops. The only difference is when you want to loop over an array with indices. You need to use indices function, since Kotlin does not support old C style i = 0;i < x;i++ loop control.

Just like in Java, you can use break and continue in loops to control execution.

Final thoughts

If you’ve came this far, you can see that Kotlin offers some really nice features compared to Java. Some of them can be classified as just “syntax sugar”, but many others offer substantial boost in productivity compared to Java. Code is often more concise and readable, although it can get messy if you overuse advanced features.

So, is Kotlin a “Java killer”? I would have to say NO. Although Kotlin offers a lot new, shiny stuff, there is just no compelling reason to abandon Java. After doing few relatively simple projects in Kotlin, I’ve decided to switch back to Java for my personal projects.

If you are beginner trying to get into programming, I would recommend learning Java as your first object oriented language. Once you learn Java, learning Kotlin is simple.

With recent improvements in Java, such as records, var keyword, switch expression etc. the gap between “modern” and “old fashioned” is closing. Although Java will probably never be as “hipp” as Kotlin, it is still reliable, robust and nice language. And with new release every six months, it will just keep getting better fast.