Introduction

Kotlin, like Java, is a statically typed language that works on the JVM. Simply put, it can fully interoperate with Java code and provides us with more expressive and succinct syntax, as well as several other improvements.

Most of these improvements stem from a willingness to fix problematic issues that Java simply cannot fix while maintaining backward compatibility. For example, perhaps some of the most interesting improvements – are several mechanisms that eliminate the risk of the unexpected occurrence of NullPointerException.

What’s more, Kotlin designers removed checked exceptions and raw types and introduced such interesting concepts as function types, companion objects, and extension functions.

In this tutorial, you’ll learn about all these features and a few more. For those who are new to Kotlin, definitely have a look at how to set up your environment, in Kotlin Basics: Kotlin 101.

Null-safety

While it’s difficult to create an application that is completely error-proof, Kotlin has been equipped with some instruments which can prevent us at compile time from NullPointerException — one of the most frequently occurring exceptions in Java that often results in serious problems.

Let’s see how they do it.

Nullable and Non-Null Types

First of all, Kotlin designers introduced nullable and non-null types for variables. This makes the compiler responsible for checking whether the code is null-safe.

Let’s find out how to use it:

var value: String = "string"
assertNotNull(value.length)
        
value = null; // compilation error

As you can see, String is a non-null type. Kotlin allows developers to assign a sample String and get its length.

However, when you want to assign null, the compiler will protest with an error: Null can not be a value of a non-null type String”.

Now, let’s look at a nullable String type:

var value: String? = null
        
println(value.length) // compilation error

This time, the compiler forbids invoking length as the variable may be null.

As you know, casting two unrelated types in Java always results in ClassCastException:

Object s = "abc";
Integer i = (Integer) s; // ClassCastException

However, Kotlin provides developers with safe casts, which result in null if one type cannot be cast to another one:

var stringValue: String? = "string"
var intValue: Int? = stringValue as? Int

assertNull(intValue)

Operators

There are three possible ways of calling a method on a nullable reference:

  • using the safe-call operator ?.
  • checking the reference inside an if statement beforehand
  • using the not null assertion operator !!.

The first two options are null-safe. The third can throw NullPointerException if the reference is null.

Let’s analyze how the safe-call operator works:

value?.length

The example checks the actual length of the string with a null-safe operator. When the reference points to an object, it returns its length. Otherwise, it returns null.

Explicitly checking whether a reference is pointing to null inside an if statement is a common practice.

In this case, the syntax is the same as you see in Java, and you should know that if the compiler sees the value isn’t null, it allows you to read the object’s length:

if(value != null) {
    assertNotNull(value.length)
}

The third option is using the !!. operator:

value!!.length

You should also know how to assign a null-safe value; to do that, you can use an if statement:

val length: Int = if (value != null) value.length else -1

Or you can use the Elvis operator:

val length = value?.length ?: -1 // Elvis operator

Both options work the same way. When the value on the left side of the ?: operator is null, it returns the value standing on the right side. If not, it returns length.

Collections

In order to deal with nulls, Kotlin provides some useful features for collections.

To skip nulls while working with a Java collection, you should use filter() firstly and then perform the actions you need to:

Lists.newArrayList("a", "b", null).stream()
    .filter(Objects::nonNull)
    // do something
    .collect(Collectors.toList());

In Kotlin, when you want to perform some actions on the non-null elements of a collection, you can use let together with the ?. operator. This simply omits null elements while iterating:

val list: List<String?> = listOf("a", "b", null)
for (elem in list) {
    elem?.let { assertNotNull(it) } 
}

On the other hand, there’s a built-in filterNonNull() method, which returns a new collection without nulls:

val list: List<String?> = listOf("a", "b", null)
val nonNullList = list.filterNotNull()
assertEquals(nonNullList, listOf("a", "b"))

Exceptions

We also get minor improvements in the way exceptions are handled in the language. These apply to both catching and throwing exceptions, as well as to the exceptions themselves.

Just like in Java, Kotlin enables us to use a try-catch block – which must include the try block and at least one of the catch or finally blocks.

A key difference is that in Kotlin, try is also an expression that can return a result. The result comes from either the last expression of the try block or the last expression of the catch block that is evaluated, and the finally block has no influence on the return value.

Let’s see this in action:

val value: Int = try { 5 } catch (e: IOException) { 6 }

The same as it’s with try, throw is an expression in Kotlin. The return type of throw is a Nothing type — it contains no value.

Let’s see a simple way to use throw:

val length: Int = sampleString?.length ?: 
  throw IllegalArgumentException("String must not be null")

Last but not least, it’s worth understanding that Kotlin provides no checked exceptions. The language designers decided not to support these – as they usually do more harm than good.

Functions

As Java’s Single Abstract Method types are limited in their functionality, the creators of Kotlin decided to introduce proper function types.

You’re going to learn how they work, over the course of these next few sections.

Declaring Function Types

To declare a function type in Java, you need a functional generic interface with the apply() method. For example:

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

BiFunction<String, String, String> concat = (String a, String b) -> a.concat(b);

The apply() method can take as many parameters as you want. It can return a value of declared type as well as no value.

In Kotlin, this works quite differently.

First, let’s take a look at a sample function:

val concat: (String, String) -> String = { a, b -> a + b }
assertEquals("AB", concat("A","B"))

You can see that there is a list of parameter types between parentheses. A result type is on the right side of the arrow. If you want to define a parameterless function, you can leave the parentheses empty.

Furthermore, you can declare Unit as a result type, which means that you want to return no value.

Kotlin functions allow developers to declare a receiver object:

val concat: String.(String, String) -> String = { a, b -> plus(a).plus(b) }

assertEquals("ABC", "A".concat("B", "C"))

The receiver object is declared before the function signature and is separated by a dot. It enables a function to operate in its context.

There is also a keyword suspend that you can use before a declaration of a function type. This makes the function a suspending function.

Instantiating Functions

There are three ways of instantiation functions in Java. Firstly, you can use a lambda expression:

(String a, String b) -> a.concat(b);

The second option is using an anonymous class:

new BiFunction<String, String, String>() {
    @Override
    public String apply(String s, String s2) {
        return s.concat(s2);
    }
};

The last possibility is assigning an existing method:

String::concat

In Kotlin, like in Java, you can instantiate a function type with a simple lambda expression:

{ a, b -> a + b }

There’s also a possibility to use an anonymous function:

fun(a: String, b: String): String { return a + b }

You can use a top-level member of a class:

String::plus

Finally, what may be new for some — it’s allowed to use a constructor as well:

::String

Another novelty is that Kotlin enables classes to implement a function type, and developers can use it for instantiation:

class StringConcatenation: (String, String) -> String {
    override fun invoke(p1: String, p2: String): String {
        return p1 + p2
    }
}

Generics

In Java, generics sometimes aren’t safe or not intuitive; that’s why some rules have changed in Kotlin.

Invariant Arrays

First of all, arrays are invariant in Kotlin. Let’s imagine you have an Animal class and a Dog class that extends Animal. In Java, you could assign an array of Dogs to an array of Animals reference.

You can’t do this in Kotlin.

The in and out Keywords

The new keywords in and out are supposed to complement the behavior of wildcards known in Java. Although these words are pretty meaningful, let’s find out what they actually do.

The out keyword is a substitution for <? extends Object>. <out T> tells us that you can expect T as a result of producing methods, but it cannot be used for consumption. On the contrary to <? extends Object>, the out keyword makes the compiler fail compilation if you decide to add a method that takes T as a parameter.

Let’s take a look at the Iterable interface:

public interface Iterable<out T> {
    public operator fun iterator(): Iterator<T>
}

The in keyword works the other way around — it ensures us that you can safely consume type parameter T, but you cannot return it.

Here you have an example:

public interface Comparable<in T> {
    public operator fun compareTo(other: T): Int
}

Star-Projection

There is also the star-projection mechanism, which looks like <*>. It means that you don’t know what the type can be. Yet, it’s still safe to use.

You can apply star-projection to three situations:

  • when used with <out T> it converts the type to <out Any?>
  • when used with <in T> it converts the type to <in Nothing>
  • when used with <T> it converts the type to an equivalent of <out Any?> for consuming and <in Nothing> for producing

Clearly, generics in Kotlin bring some nice features to the table that aren’t available to us in Java.

Extension Functions

There are plenty of situations when you may need new functionality for your class. In Java, you can do this by creating a new child class that includes new functionality. The other way is to use the Decorator Pattern.

Let’s use the pattern on String class which cannot be extended:

class StringDecorator {

    private String string;

    public StringDecorator(String string) {
        this.string = string;
    }

    public String concat(String s) {
        return string.concat(s).concat("DEF");
    }
}

StringDecorator stringDecorator = new StringDecorator("ABC");
String result = stringDecorator.concat("XYZ");

assertEquals("ABCXYZDEF", result )

Both of those solutions require quite a bit of overhead and complexity. Kotlin provides extension functions to solve that problem.

You can define extension functions in almost the same way as regular functions. The only difference is that you need to specify the receiver type before the function name.

Let’s look at an example:

fun String.appendString(str : String): String {
    return plus(str)
}

val sampleString = "ABC"
val concatenatedString = sampleString.appendString("DEF")

assertEquals("ABCDEF", concatenatedString)

Similarly to extension functions, you can extend any class with new properties. Here you have a quick example:

val String.size: Int
    get() = length

val sampleString = "ABC"

assertEquals(3, sampleString.size)

There is plenty of additional information in this article on Kotlin’s extension functions.

Properties

It may come as a surprise, but designers of Kotlin replaced class fields with properties. What’s the difference? At first glance, they look the same, but a property is equivalent to a field with its accessors.

You can declare two different types of properties: var or val. The former one is a regular type, and the latter is read-only. You should keep in mind, that read-only applies only to the reference, not to the object itself.

Let’s take a look at a sample class:

class Product {
    val id: String = "empty"
    var price: BigDecimal? = BigDecimal.ZERO
}

Accessors are generated automatically. For vars, both setters and getters are generated, but only getters are generated for vals. And just like in Java, you can modify the visibility of a property:

private val id: String? = "empty"

Even though accessors are generated automatically, programmers are allowed to write our own implementation of getters and setters when necessary.

What’s more, you can define access modifiers for accessors, and you can access the backing field using the field keyword:

var price: BigDecimal? = BigDecimal.ZERO
    private set(value) = if (value == null) { field = BigDecimal.ONE } 
        else { field = value }

You should be aware that although you can’t declare fields explicitly when a property needs a backing field, the compiler will generate it. The generated field is always private.

Other Improvements

There are plenty of improvements that are surely not as spectacular as the null-safety mechanism or function types.

However, they are worth mentioning as they seem to be attractive to many Java developers.

Companion Objects

Java allows developer defining static methods and fields which are class members. Here’s a short example:

class A {
    private static String name = "A";

    public static String getName() {
        return name;
    }
}

assertEquals("A", A.getName())

Kotlin makes declaring static members inside classes impossible. Therefore it’s recommended to use methods with package access.

However, there’s a mechanism in Kotlin that behaves the same as static members. To use it, we need to declare a companion object inside our class.

Let’s see an example:

class A {
    companion object {
        fun returnClassName(): String {
            return "A"
        }
    }
}

assertEquals("A", A.returnClassName())

In fact, there’s a way to generate members of companion objects as static methods and fields. To do that, you need to use the @JvmStatic annotation:

companion object {
    @JvmStatic fun foo() { ... }
}

There is one major advantage of using companion objects. There are no more class members. All members that would be static in Java are organized into objects what accords with object-oriented programming principles.

What’s more, you can use all mechanisms available to objects such as inheritance, extension functions when working with companion objects.

The is Operator

In Kotlin, the is operator is the equivalent of Java’s instanceof operator:

if(value instanceof String) {
    //do some actions
}

It allows developers to check at runtime whether or not a tested object is an instance of some class.

Let’s check that out:

val value: Any = "string"

if(value is String) {
    assertEquals(6, value.length)
}

What’s more, the compiler is smart enough that – when the is operator returns true, the object is automatically cast to the class used in the condition. Smart casts work in condition blocks as well:

(value is String && value.length == 6)

The is operator also can be very helpful with when or while expressions:

when(value) {
    is String -> assertEquals(6, value.length)
    is Int -> assertEquals(6, value.absoluteValue)
}

Delegation

There is an interesting approach that enables developers to use the Delegation Pattern without any unnecessary code.

In Java, implementation of that pattern would look like this:

interface Engine {
    String makeSound();
}

class V6Engine implements Engine {
    @Override
    public String makeSound() {
        return "Vroom";
    }
}

class Car {
    private V6Engine v6Engine;

    public Car(V6Engine v6Engine) {
        this.v6Engine = v6Engine;
    }

    public String makeSound() {
        return v6Engine.makeSound();
    }
}

Car car = new Car(new V6Engine());
assertEquals("Vroom", car.makeSound());

Let’s analyze how you can do that in Kotlin:

interface Engine {
    fun makeSound(): String
}

class V6Engine: Engine {
    override fun makeSound(): String {
        return "Vroom"
    }
}

class Car(e: Engine) : Engine by e

val car = Car(V6Engine())
assertEquals("Vroom", car.makeSound())

You can observe that using by e indicates that all methods of the Engine interface will be provided by an object stored in the e property. The compiler does this automatically under-the-hood.

Constructors

In Java, it’s common practice to generate constructors with boilerplate code that simply initialize class fields:

class Example {
    
    private Long id;
    private String name;

    public Example(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

Example example = new Example(1L, "Example");

To deal with this, Kotlin introduces a new way of creating such constructors that avoid the boilerplate code: the primary constructor.

A primary constructor is a part of the class header. You may omit the constructor keyword when there are no annotations or visibility modifiers. Otherwise, annotations or modifiers should be on the left side of the keyword.

Primary constructors don’t have any logic and are responsible only for initialization. You can also use them to declare var or val properties. Here’s an example:

class Example constructor(val id: Int, var name: String)

What’s more, regular constructors declared inside the class body are now called secondary constructors in Kotlin.

Constructors are defined now by the constructor keyword, not by the class name. The secondary constructors are not able to declare val or var properties.

Let’s see an example:

class SecondExample {
    val id: Int
    constructor(id: Int) {
        this.id = id;
    }
}

The last important thing you should know is that instances of a class are created without using the new keyword. You only need to use the class name with arguments between parentheses:

var example = Example(1, "Example")

Data Classes

There is a new type of class in Kotlin — the Data Class. As the name suggests, its purpose is to carry data. The big advantage of a Data Class is that most of its utility functions are generated automatically.

Let’s take a look at how to define one:

data class Student(val id: Int, val name: String, val lastName: String)

The compiler generates the following functions using properties declared in the primary constructor:

  1. equals()
  2. hashcode()
  3. copy()
  4. componentN() functions

Whenever you want to exclude some property from being part of automatically created functions, you should declare it inside the class.

The copy function is very useful when you want to copy the object with minimal changes to it:

val student = Student(1, "John", "Smith")
val student2 = student.copy(id = 2, name = "Anne")

There are two facts you should remember when using data classes.

First, that the compiler won’t generate equals(), hashCode() or toString() if the class or its superclass contains any implementation of them.

And second, that you can’t implement componentN() and copy() functions by yourself.

Overloading Operators

Let’s have a look at yet another interesting thing you can do in Kotlin — you can overload operators like +, , *, < and many more. You can do so by writing a member function or an extension function with the proper name.

Here are some quick but highly interesting examples:

class Plane(var currentSpeed: Double) {
    
    operator fun inc(): Plane {
        currentSpeed += 50.0
        return this
    }

    operator fun minus(number: Double) {
        currentSpeed = if(currentSpeed < number) 0.0 else currentSpeed - number
    }

    operator fun invoke(speed: Double) {
        currentSpeed = speed
    }

    operator fun compareTo(plane: Plane): Int {
        return currentSpeed.compareTo(plane.currentSpeed)
    }
}

var plane = Plane(0.0)
plane++
assertEquals(50.0, plane.currentSpeed)

var plane = Plane(1000.0)
plane - 500.0
assertEquals(500.0, plane.currentSpeed)

var plane = Plane(0.0)
plane(150.0)
assertEquals(150.0, plane.currentSpeed)

var plane = Plane(0.0)
var plane2 = Plane(150.0)
assertTrue(plane < (plane2))

The complete list of operators you can overload is on the Kotlin documentation web page.

Mutable and Immutable Collections

Unlike Java, the Kotlin Collections API has separate interfaces for mutable and immutable collections. You can use these to enforce a behavior on collections without throwing any exceptions as Java implementations do.

Let’s take a brief look at List interfaces:

val mutable = mutableListOf(1, 2, 3, 4, 5)

mutable.add(6)
mutable.removeAt(0)

val immutable = listOf(1, 2, 3, 4, 5)

immutable.contains(5)
immutable.size == 5

As you can see, there are List and MutableList interfaces. The List interface is for ensuring that the collection will be immutable. That’s why it doesn’t contain any mutators.

On the other hand, there is the MutableList interface, which allows programmers to change the collection as they want.

There are also mutable and immutable interfaces for sets and maps.

No More Primitive Types

During compilation, some Java types are converted to their corresponding types in Kotlin. This rule applies to both primitive types as well as their wrappers.

Here’s the list of mappings:

  • boolean -> kotlin.Boolean
  • byte -> kotlin.Byte
  • char -> kotlin.Char
  • double -> kotlin.Double
  • float -> kotlin.Float
  • int -> kotlin.Int
  • long -> kotlin.Long
  • short -> kotlin.Short

It’s worth noting that the compiler maps primitives to non-null types. By contrast, it converts wrapper classes such as Long or Integer to nullable classes, such as kotlin.Long? or kotlin.Int?.

Conclusion

Kotlin is becoming a polished, mature, highly powerful language.

In this article, you’ve learned many new features that you may encounter in Kotlin. Most of these features were introduced in order to eliminate some of the problems developers often see in Java.

Probably the most important is the elimination of the often unexpected occurrence of NullPointerException. On the other hand, many new features available in Kotlin reduce the amount of boilerplate code in developers’ daily routines.

Due to the complete interoperability between Kotlin and Java, you don’t have to use features of only one language. You can benefit from both at the same time. That’s why Kotlin is worth considering when choosing the technological stack for a new project.

As usual, all the code samples are available in the GitHub project.

guest
0 Comments
Inline Feedbacks
View all comments