New era of java?

Each year we hear the same old story that Java is too old and it has to stay in the past, it has outdated syntax and features are like from ninetieth or something similar. It might be true in some areas, but today java maintainers do a really good job to modernize language and at the same time we have a lot of other JVM languages that can be used instead of java. One of such languages is Kotlin. Nowadays kotlin became a trend language - it can be used in different cases: traditional backend, web-frontend, mobile-frontend, native applications. Concise and effective syntax is a distinctive feature of this language, but not only this makes it so interesting to use. Let’s take a closer look to Kotlin together - there are a lot of interesting things.

Named arguments and default arguments (less overloading and more readability)

Kotlin allows us to use named and default arguments which makes our code much more readable with less code lines:

  • we don’t need to write a lot of overloaded methods
  • we can easily change order of arguments if we use named arguments
  • we don’t have to name variables before sending them to method - it can be done just during method invocation
fun main(args: Array<String>) {
    val duckService = DuckService()
    println(duckService.createDuck("James", "grey", 2))       //Duck(name=James, color=grey, age=2)
    println(duckService.createDuck("James"))                  //Duck(name=James, color=white, age=1)
    println(duckService.createDuck(age = 3, color = "brown")) //Duck(name=JavaRubber, color=brown, age=3)
}

data class Duck (val name: String, val color: String, val age: Int)

open class DuckService {

    open fun createDuck(name: String = "JavaRubber", color: String = "white", age: Int = 1): Duck {
        return Duck(name, color, age)
    }
}

class CustomDuckService: DuckService() {

    override fun createDuck(name: String, color: String, age: Int): Duck { //Should not contain default values
        return Duck(name, color, age)
    }
}

We have to keep in mind that:

  • if argument with no default value comes after default argument - we have to use named arguments
  fun createDuck(name: String = "JavaRubber", color: String): Duck {
          return Duck(name, color, age)
  }
  
  createDuck(color = "grey")
  • if we are overriding some method with default values it is not allowed to provide default values there
  • if we are using positional arguments together with named arguments all positional arguments have to come before named arguments

String literals and string formatting

Literals

In kotlin there are two types of string literals: raw strings and escaped strings. The last are just like strings in java - they may contain escape symbols. Raw strings can contain arbitrary text which means we can write text with special characters, new lines.

Escape string literals are delimited with double quotes"":

 "this is the escape literal \""

Raw strings are delimited with triple quotes """

 """this is
      a raw string
 """

Raw strings make regexp a bit cleaner because you don’t need to escape backlash with another backslash

Formatting

In kotlin we can use variable just inside of the string using $ symbol

 "Unexpected currency $currencyName"

More over we can use expressions inside the string using curly braces like this:

 "The book has ${book.pages.size} pages"

This approach looks much neater then string formatting in java:

 String.format("The book has %d pages", book.pages.size)

Data classes

We all know that getters and setters are needed, but usually we don’t want to write them. More over when they are already written this code is almost never read by others just because it is usually simple and should not contain any heavy logic.

Most of us are probably using lombok for creation of setters and getters. In Kotlin we can have them by just defining our class as a data class.

Data class should have at least one property and all properties should be var or val. It can not be abstract, sealed, inner or open.

data class Duck (val name: String, val color: String)

Every property declared in this class will be used in automatically created equals(), hashcode (), toString(), copy() methods.

Explicit modifiers

Kotlin requires explicit modifiers for class member that can be overridden: which means that overridable members should have open modifier and in the deriving class those members should have override modifier:

 open class Parent {
    open fun do() {
    }
 }
 
 class Child : Parent {
    override fun do() {
    }
 }

override modifier allows overriding in the next child class, so if we want to prohibit overriding we have to use final modifier.

In cases when we have ambiguity during methods inheritance like this:

 class Parent1 {
    open fun do() {
    }
    
    open fun doNotDo() {
    }
 }
 
 interface Parent2 {
     fun do() {
     }
  }
 
 class Child : Parent1, Parent2 {
    override fun do() {
      super<Parent1>.do()
    }
    
    fun doNotDo() {
    }
 }

Child class inherits do method from both Parent1 and Parent2 we have to explicitly specify which implementation has to be called or write our own implementation, but the same logic is in java as well.

The same rules are applicable for properties, but we have to remember that we can override val property with var, but not vice versa - the reason for this is: declared val property has only getter when var property is declared with both getter and setter. So if val property is declared in the parent class we automatically have setter for it exposed and inherited by child classes and it is impossible to hide setter method (therefore impossible to override var with val property)

NOTE: In Kotlin all classes are final by default, we have to specify open modifier explicitly

Static members

On of the most unusual part as for me is how kotlin allows us to create static fields and methods.

Static fields

We have several ways to provide static fields and for all of them we have to use named objects or companion objects:

  • const modifier turns field into static final field:
object Duck {
  const val NAME = "JavaRubber"
}

fun main(args: Array<String>) {
  println(Duck.NAME) //JavaRubber
} 
  • lateinit modifier helps us initialize variables with non-null type, but not in the constructor - for example we know that field will be initialized and we don’t want to make it nullable. In this case field will be static and modifiable.
  class Duck {
  
    companion object {
      lateinit var name: String
      fun setUp() {
        name = "JavaRubber"
      }
    }
  }
  
  fun main(args: Array<String>) {
    Duck.setUp()
    println(Duck.name) //JavaRubber
  }

NOTE: The property type has to comply with this restrictions: non-nullable type, not primitive type. If this property will be accessed before it will be initialized - corresponding exception will say this explicitly (UninitializedPropertyAccessException: lateinit property name has not been initialized). The access level is the same as the access level of getter. Property has to marked as modifiable variable with var modifier.

  • @JvmField annotation makes field static:
class Duck {

    companion object {
        @JvmField
        val NAME = "JavaRubber"
    }
}

fun main(args: Array<String>) {
    println(Duck.NAME) //JavaRubber
}

Static methods

Kotlin has two types of static methods:

  • top-level methods that are defined in the package:
package devblog.static_elements

fun describeStatically() {
    println("This is the description from top-level method")
}
  • methods that are defined in the named objects or companion objects:
class Duck {

    companion object {
        @JvmStatic
        fun printDuckDescription() {
            println("My name is JavaRubber duck and I am really cute duck actually")
        }
    }
}

fun main(args: Array<String>) {
    describeStatically()       //This is the description from top-level method
    Duck.printDuckDescription()//My name is JavaRubber duck and I am really cute duck actually
}

Visibility

In kotlin there are four access modifiers: public, internal, protected, private - which are almost the same as in java, but:

  • the default modifier is public
  • internal modifier is sort of default modifier in java.

We can use these modifiers for class properties, but not for local variables. When class property has access modifier - the same modifier will be used for it’s getter, but we can explicitly specify the modifier for setter like this:

  class Duck {
    var name: String = "JavaRubber"
        private set 
  }

Also we can specify classes, properties and functions on top-level:

package javarubberduck

internal fun rubber()
public class Duck

In the context of top-level members - protected modifier can not be used.

Nullability

Kotlin avoids NullPointerException by introducing nullable and non-nullable types:

val name: String?
var lastName: String

In this example name variable is nullable (? indicates about it) when lastName is non-nullable. So you can safely use lastName without worrying about NPE.

Kotlin introduces several convenient ways to use nullable values:

  • after checking nullability in if statement compiler already knows that it can trust this variable:
var name: String?

name = "Duck"

if (name != null ) {
  println(name.length)
}
  • we can use ? operator to access members of the nullable variable. In this case if value of name variable is null - null will be returned, but if the value is present - length field will be accessed and real value will be returned
var name: String?

name = "Duck"

println(name?.length)
  • Elvis operator can also help us. It checks whether the variable or expression is null. If it is null it will return value from the right side of the expression, if not null - from the left side
var name: String?

name = "Duck"

val length = name?.length ?: 0
  • to safely call method of the nullable type we can use let: it will execute piece of code only if object is not null:
var name: String?

name = "Duck"

name.let{ println(name.length)}

In case we think that we can trust variable with nullable type we can use !! operator to indicate that this nullable type will be converted explicitly to non-nullable type.

var name: String?

name = "Duck"

println(name!!.length)

Smart cast

Kotlin’s compiler is pretty smart one: it can autocast your object to needed class once you did check of the type:

  if (salary is BigDecimal) {
    salary.multiply(bonus)
  }

Extensions

Another interesting feature of Kotlin is that we can create our own extension of the existing classes without inheritance. There are possibility to create functional extension and property extension by adding receiver type before the name of the property or function:

class Duck 

fun Duck.quack() = println("quack")

val Duck.name: String
    get() = "Jake"
    
Duck.quack()

NOTE: initializers are not allowed in property extension and function extensions are resolved statically which means they are related not to the runtime object, but to the class that was declared

Object expression and declaration

If we want to create an instance of the anonymous class in kotlin we have to use an object expression:

open class Duck (name: String) {
  open val name: String = "Duck"
}

object: Duck() {
  override val name = "JavaRubber"
}

As derives from the name object expression this object can be assigned to some variable when object declaration is just a statement:

object JavaRubberDuck: Duck() {
  override val name = "JavaRubberDuck"
}

Object declaration represent singleton which is initialized only when it will be used for the first time (lazily).

Overloading of operators

Kotlin allows us to overload operators - which should be used just as a convenient way to write concise code. For example if we want to compare two ducks by the length of their name we can do it with >, < operators just by implementing this kind of code:

class Duck (val name: String) : Comparable<Duck> {

    override operator fun compareTo(other: Duck): Int {
        return this.name.length - other.name.length
    }
}

fun main(args: Array<String>) {
    val duck1 = Duck("Nick")
    val duck2 = Duck("Dick")
    println(duck1 > duck2)
}

So for overloading we have to:

  • create member function or extension function with receiver
  • this function should have operator modifier which is a marker for compiler
  • it has to have argument if we are overloading binary operator (if it is unary operator there should be no arguments)

NOTE: At the same time it is not possible to overload standard operators for basic types because those methods are already declared and they are final. Even we we try to overload it with extension function it will be shadowed by the member function because member function has a precedence.

NOTE: Kotlin allows us to overload == which means that Java rule that this operator can be used only for primitive types to check equality of values is not applicable here as far as we can overload this operator for specific type to do proper evaluation using equals method.

Extension functions of the collections

In kotlin standard java collections are used but with additional methods which are provided thanks to extension mechanism:

  • convenience method toList, toSet, toMutableSet, toMutableList, toSortedSet
  • filter and map methods can be called directly on collections and we can use them like this:
fun main(args: Array<String>) {
  val ducks = listOf()
  ducks.filter{ it.name == name }
  ducks.filter{ duck -> duck.name == name }
}

As you can see we can use: ** explicit variable name duck or implicit it ** lambda expression can be moved outside from brackets to curly braces according to the rule, that if lambda is last method argument - it can be moved outside ** filter method returns filtered collection and not a stream like in java

  • methods like any, all, count, find that can be used directly on collections and return collection
  • method like flatMap has almost the same logic as in java, but returns collection instead of stream
  • method that I really like is groupBy which creates a map based on you criteria:
fun main(args: Array<String>) {
  val groupedDucks = listOf(Duck("Jake"), Duck("Drake"), Duck("Mike")).groupBy { it.name.length }
  val expectedResult = hashMapOf<Int, List<Duck>> (
    4 to listOf(Duck("Jake"), Duck("Mike")),
    5 to listOf(Duck("Drake"))
  )
  println(expectedResult == groupedDucks)
}

data class Duck(val name: String)

Intellij IDEA support

Intellij IDEA is a product of JetBrains as well as Kotlin so naturally that this IDE has outstanding built-in support for Kotlin:

  • autocompletion and syntax support
  • training courses to master kotlin using Intellij IDEa which I found really useful (a specially when you read docs and do exercises in parallel)
  • code style template, for now (February 2019) there are two of them: old and new one - the recommended is a new one.

Integration with Spring

Kotlin is integrated with Spring pretty good. The things you need to remember to use kotlin in your spring boot projects:

  • folder structure is a bit different
  • additional kotlin dependencies are needed
  • kotlin plugin is needed

Folder structure

There is no need to have exactly the same package structure as folder structure in Kotlin.

Dependencies and plugins

In order to use kotlin in our project (built using gradle) we have to include such dependency:

plugins {
  id "org.jetbrains.kotlin.plugin.allopen" version "1.3.21"
}

allOpen {
    annotation("com.javarubberduck.OurCustomAnnotation")
}

AllOpen plugin is needed to make some classes marked with specific annotations specified in allOpen block open. Just keep in mind that all classes in kotlin are final which means that we will have problems when we want to use spring, because a lot of classes has to be opened in order to be used by spring. So AllOpen plugin helps us to specify which classes will be converted from final to open, but as far as those classes are typical for spring jetBrains created wrapper on top of AllOpen plugin which makes opened already predefined set of annotations like: @Transactional, @Cacheable, @SpringBootTest, @Component, @Async. This plugin has name spring and can be enabled like this:

plugins {
  id "org.jetbrains.kotlin.plugin.spring" version "1.3.21"
}

Also in order to use kotlin features we need to add standard kotlin library to our dependency:

dependencies {
  compile "org.jetbrains.kotlin:kotlin-stdlib:1.3.21"
}

If we are using gradle we will need also to add kotlin-gradle-plugin which help us create kotlin resources and compile classes:

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21"
    }
}

Typical questions about kotlin

Can I use kotlin and java at the same time?

We can easily use kotlin and java in one project and we can even have a mix of classes in one package written in kotlin and java. But at the same time we have to remember some rules.

Invoke java code from kotlin

  • if java code contains method or variables with names that are kotlin keywords we can still use them, but with single quotes:
class Duck {
  public void is() {
    System.out.println("is");
  }
}

--- different file

fun main(args: Array<String>) {
    Duck().`is`()
}
  • void return type in kotlin will be converted to Unit class which is known during the compilation which means that if any variable was using the result of void method - it will be immediately assigned to Unit object during compilation

  • if java class contains fields with getters and setters in kotlin they will be visible just like fields, but if the field in java class does not have public getter - this field won’t be visible, because kotlin does not allow fields that can be set-only.

NOTE: setters and getters names have to follow to the naming conventions

  • when we are calling java methods from kotlin the return value of them can be null - this means that java calls are not NPE safe. So kotlin can not rely on java api completely and as far as we can not avoid java calls kotlin has to treat java calls a bit differently. That’s why kotlin checks those invocations in a relaxed way. It means that kotlin’s compiler won’t complain during the compilation time, but we might get an exception at runtime (NullPointerException or IllegalStateException in case if we were trying to assign null to non-null type variable)
class Duck {
  public String is() {
    return null;
  }
}

--- different file

fun main(args: Array<String>) {
    val value: String? = Duck().`is`()
    val value2: String = Duck().`is`() //    val value2: String = Duck().`is`()
    println(value)
    println(value2)
}
  • if nullability annotations were used in java - most of them are used by kotlin to create nullable or non-nullable types. For now kotlin considers these annotations:
    JvmAnnotationNames.JETBRAINS_NULLABLE_ANNOTATION,
    FqName("androidx.annotation.Nullable"),
    FqName("android.support.annotation.Nullable"),
    FqName("android.annotation.Nullable"),
    FqName("com.android.annotations.Nullable"),
    FqName("org.eclipse.jdt.annotation.Nullable"),
    FqName("org.checkerframework.checker.nullness.qual.Nullable"),
    FqName("javax.annotation.Nullable"),
    FqName("javax.annotation.CheckForNull"),
    FqName("edu.umd.cs.findbugs.annotations.CheckForNull"),
    FqName("edu.umd.cs.findbugs.annotations.Nullable"),
    FqName("edu.umd.cs.findbugs.annotations.PossiblyNull"),
    FqName("io.reactivex.annotations.Nullable")
  • nullability annotations can be also used with type arguments in generic types
  Collection<String> // (Mutable) Collection<String!>
  Coleection<@NotNull String>  // (Mutable) Collection<String> 
  • @NonNull annotation from the JSR-305 is also supported:
  @NonNull(when = When.ALWAYS) // Non-null type in kotlin
  @NonNull(when = When.MAYBE) // Nullable type in kotlin
  @NonNull(when = When.NEVER) // Nullable type in kotlin
  @NonNull(when = When.Unknown) // Kotlin treats this as platform type
  • kotlin uses it’s own basic types that are mapped to the java types. To see details you can use this link

  • kotlin arrays are invariant which means that it is not possible to put Integer inside the array with type String. It was done to prevent possible runtime error. Arrays of the primitive types have their own types like IntArray, CharArray.

  • to invoke java method’s with varargs we can use spread operator *

  • the really nice thing is that kotlin does not have checked exception which means that if you invoke java methods that throw checked exceptions, you don’t need to do anything with them.

  • inheritance between java classes and kotlin classes is can be done as usual without additional hassles

Updated: