Kotlin in a nutshell

Federico Mete
10 min readFeb 23, 2022

What is Kotlin?

It is a free and open source programming language, developed by JetBrains and released on 2016, that targets the JVM, Android, JavaScript and Native. When targeting the JVM, Kotlin produces JAVA compatible bytecode, but any code snippet written with it is much smaller compared to JAVA as it is much less verbose.

Kotlin is statically typed and supports type inference, allowing it to maintain correctness and performance while keeping the source code concise. It supports both object-oriented and functional programming styles, enabling higher-level abstractions through first-class functions and simplifying testing and multithreaded development through the support of immutable values.

Basics

To declare variables (and properties) you must use “var” and “val” keywords;var” declare variables that can be reassigned, and, on the other side, “val” declare read-only variables.

Kotlin uses the standard Java classes for collections and enhances them with a distinction between read-only and mutable collections. The read-only interface provides operations for accessing collection elements, and the mutable interface extends the corresponding read-only interface with write operations: adding, removing, and updating its elements.

With Ranges / Progressions you can easily define intervals that can be used for iteration in for loops or contains expressions.

String templates are string literals that can contain embedded expressions, and can help you to avoid noisy string concatenations.

Functions

To declare functions you must use the fun keyword; when they are called, named-argument syntax can be used to make them much more readable, especially on functions with many parameters.

Functions marked with the infix keyword can be called omitting the dot and the parentheses, similar to operators like “in”, “is”, “as”, etc.

Extension functions let you extend the API of any class, without having to inherit from the class or use design patterns such as decorator; you can also define new properties for existing classes using extension properties.

Higher-order functions take other functions as arguments or return them. You can create such functions by using a function type as the type of a function parameter or return value. These functions facilitate code reuse within the parts of a single component and let you build powerful generic libraries.

Classes, interfaces and objects

To declare a class, you must specify the class name, the class header, and the class body, surrounded by curly braces; by default declared classes are public and final (and to make them extensible you should add the open keyword)

If the main purpose of a class is to hold data, Kotlin allows you to create “data classes” (=value objects), so hashCode, toString, copy, and other standard functions derivable from the data are automatically generated.

To make the code even more concise, you can declare properties and initialize them on the primary constructor (that goes after the class name and optional type parameters).

When declaring properties, the initializer, getter, and setter are optional; to reference a property backing field from the accessor body you can use the field identifier.

You can use “by” keyword to delegate properties and reuse logic controlling how property values are stored, initialized, accessed, and modified, which is a powerful tool for building frameworks.

Kotlin’s provides a lazy function that takes a lambda and returns a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result; subsequent calls to get() simply return the remembered result.

Initializer blocks and secondary constructors provide more flexibility for initializing class instances.

In Kotlin, nested classes aren’t inner by default (as in JAVA); you must use the keyword inner to store a reference to the outer class.

Object expressions are Kotlin’s replacement for JAVA’s anonymous inner classes, with added power such as the ability to implement multiple interfaces and to modify the variables defined in the scope where the object is created. These expressions are executed (and initialized) immediately, where they are used.

Object declarations, on the other side, are Kotlin’s way to define a singletons, and are initialized lazily, when accessed for the first time.

Companion objects, are object declarations inside classes, and they are initialized when the corresponding class is loaded. Even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces.

Interfaces in Kotlin are similar to JAVA’s but can contain default implementations (which JAVA supports only since version 8) and properties.

Class delegation using the “by” keyword helps to avoid many similar delegating methods in your code. A derived class can implement an interface by delegating all of its public members to a specified object.

Lambdas

Lambdas expressions are essentially anonymous functions that you can treat as values; code on them can access and modify variables in the container function.

As functions are first-class on Kotlin, you can pass lambdas as arguments to other functions (when you have a single lambda parameter, you can pass it outside of parentheses).

You can also pass lambdas as arguments to methods that take a JAVA functional interface (an interface with a single abstract method, also known as a SAM interface) as a parameter.

Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure; memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead. But, it appears that in many cases this kind of overhead can be eliminated by inlining the lambda expressions. When an inline function is compiled, its bytecode along with the bytecode of a lambda passed to it is inserted directly into the code of the calling function, which ensures that the call happens with no overhead compared to similar code written directly.

Even though non-local return (return expression placed in a lambda that return from the enclosing function) is forbidden when lambda is passed to non-inline functions, you can use it when lambda is passed it to inline functions or anonymous functions.

To return from the lambda itself when passed to a non-inline function, you should use labeled returns.

You can create references to methods, constructors, and properties by prefixing the name of the function with ::, and pass such references to functions instead of lambdas.

Most common operations with collections can be performed without manually iterating over elements and in a less verbose way, using functions such as filter, map, all, any, and so on, and passing lambdas as arguments.

When the processing of an Iterable includes multiple steps, they are executed eagerly: each processing step completes and returns its result — an intermediate collection. Sequences, on the other side, allow you to combine multiple operations on a collection without creating collections to hold intermediate results, as actual computing happens only when the result of the whole processing chain is requested (similar to JAVA Streams)

Lambdas with receivers are lambdas in which you can directly call methods on a special receiver object.

The with standard library function allows you to call multiple methods on the same object without repeating the reference to the object; apply lets you construct and initialize any object using a builder-style API.

Type system

Kotlin’s support of nullable types detects possible NullPointerException errors at compile time.

For dealing with nullable types concisely, Kotlin provides tools such as…

  • Safe calls (?.) and the Elvis operator (?:)
  • Let function
  • Not-null assertions (!!)

The as? operator provides an easy way to cast a value to a type and to handle the case when it has a different type to avoid CastExceptions.

Operators

Kotlin allows you to overload some of the standard mathematical operations by defining functions with the corresponding names.

By defining functions named get, set, and contains, you can support the [] and in operators to make your class similar to Kotlin collections.

Destructuring declarations let you initialize multiple variables by unpacking a single object, which is handy for returning multiple values from a function. They work with data classes automatically, and you can support them for your own classes by defining functions named componentN.

Generics

Kotlin’s generics are fairly similar to those in Java: you declare a generic function or class in the same way.

As in Java, type arguments for generic types only exist at compile time, so, you can’t use types with type arguments together with the is operator. However, type parameters of inline functions can be marked as reified, which allows you to use them at runtime to perform is checks and obtain java.lang.Class instances.

Variance is a way to specify whether one of two generic types with the same base class and different type arguments is a subtype or a supertype of the other one if one of the type arguments is the subtype of the other one.

By default, generic types are invariant, meaning that List<String> is not a subtype of List<Object>. However, you can declare a class as covariant on a type T parameter if the parameter is used only in out positions (T objects are returned, and you can safely read subtypes of T as T )

The opposite is true for contravariant cases: you can declare a class as contravariant on a type T parameter if it’s used only in in positions (T objects are taken as arg, and it’s safe to use supertypes of T, as you know that they comply with T type)

Sometimes you want to say that you know nothing about the type argument, but you still want to use it in a safe way. The safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type will be a subtype of that projection. The star-projection syntax can be used on these cases (<*>)

--

--