An intelligent bet for Kotlin. Part 1

An intelligent bet for Kotlin. Part 1

An intelligent bet for Kotlin. Part 1


November 16, 2020


The Java industry is moving forward along with the world around it. And software companies, as part of its community, are very much in tune with the trends. Progressive and novel things often emerge from technology crossovers. Here’s why BellSoft, determined to keep you informed of everything Java-related, has invited me to make a two-parter on the young and advanced Kotlin language.

If you’re a Software Developer or any kind of professional working in the IT sector, you’ve probably heard of it. Kotlin has absorbed all the best features from other languages, while keeping full Java interoperability. Among the benefits, its supporters enumerate conciseness, expressiveness, simplicity. A very flat learning curve for Java developers is also a fact to consider for some organisations before deciding if they should switch to Kotlin.

In the first article, I’ll be going through Kotlin’s most useful characteristics, like object destructuring from JS, and type interference, and many more. Here you’ll see its areas of application and how the language makes the developers’ lives easier when writing code compared to others.

But more importantly, in the second part, we’ll discuss async programming with coroutines, observe how the language impacts the world now and even look into its future! You will basically learn everything you need to apply Kotlin to your systems.

Its history

Kotlin was first released in 2011 by the company JetBrains. If you don’t know about them, they were first known for creating the popular IntelliJ IDEA. Now they’re also very well-known as the creators of the Kotlin language.

Although their first release was in 2011, it wasn’t until 2016 when Kotlin v1.0 came out, the first official release from which JetBrains will provide backwards compatibility.

According to different sources, JetBrains engineers started writing their own language primarily due to not finding any that fulfils their needs. The goal was to build a concise, elegant and expressive language also able to be compiled fast.

Another direction is crafting code into a domain-specific language (DSL) specialized for certain parts in an app. Instead of using design patterns and building unmanageable architectural solutions from scratch, Kotlin is here to help. It features numerous applications: you can basically write your own language without parsing software, IDEs, etc. It allows replacing long chains of factory methods and code duplication with readable constructions without any external tool.

Writing a new language is not easy, but writing a new language with full interoperability with an existing language like Java is even more difficult. However, despite the challenges, JetBrains seems to be doing a very good job so far, seeing how warm a welcome their language has received from the community.

Applications

The foundation is that Kotlin is super versatile. Regardless of its comparatively young age, it’s an archetype for generating benchmark tests with JMH on par with much more “mature” Java, Groovy and Scala. Kotlin is good for low-level profiling to assess the performance and scalability of the systems. Here’s a custom example of Kotlin-based microbenchmark:

@BenchmarkMode(Mode.Throughput) // Benchmark mode, using overall throughput mode.
open class MyBenchmark{
    @Benchmark //Just like in Java.
    fun testMethod(): Int {

        return sequenceOf(1..10).flatten()            
            .map{ it * 2 }
            .filter { it % 3  == 0 }
            .map{ it+1 }
            .sum()
    }
}

Another direction is crafting code into a domain-specific language (DSL) specialized for certain parts in an app. Instead of using design patterns and building unmanageable architectural solutions from scratch, Kotlin is here to help. Replace long chains of factory methods and code duplication with readable constructions without any external tool.

Or how about configuring Gradle builds? Also possible. There’s an official plugin from JetBrains to compile Kotlin code to target JVM (apply plugin: "kotlin”), JavaScript (apply plugin: "kotlin2js”), or Android:

buildscript {
    ext.kotlin_version = ''

    ...

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

As you can see, just over four years, Kotlin has become a multi-purpose ecosystem in itself. With it, you can write code, create microservices, generate JUnit tests, build microbenchmarks, and even assemble projects (which Java can’t do).

Why is Kotlin becoming so popular and widely adopted in our industry? Let’s go through its key features to understand the reasons behind its popularity.1

Simplicity and pragmatism above all

If there’s one thing that characterises Kotlin, this is its focus on pleasing developers’ wishes, an obsession in making their lives and their day-to-day work as smooth and simple as possible. When we write code daily at a professional level, it’s the small and apparently unimportant things that we face very often that kill our productivity. They produce a constant sense of waste of time and demotivation in us. Kotlin has removed quite a few unnecessary annoyances, very well-known and recognised by all, although for one reason or another nothing had been done until now. Now I’ll cover some of them briefly.

Multiplatform

Kotlin is a multiplatform language which can be executed in different supported platforms. You can target different runtime environments like JVM, Android, JS or many others with the use of Kotlin/JVM, Kotlin for Android, Kotlin/JS and Kotlin/Native.

Kotlin Multiplatform, JetBrains’ open source code sharing SDK, is proven to accelerate development time across several teams who work on the same application for Android, iOS and web, up to 30%.2 It allows using a single business logic code in cross-platform software and basically taking advantage of one engineering team instead of two or three.

Such a solution brings significant savings (in terms of both money and time), fewer bugs and QA processes, higher efficiency. To achieve all these benefits, JetBrains have their progressive runtime supported by BellSoft. In 2019, the two companies entered a partnership: BellSoft started providing updates and security patches in the same way it supports its own Liberica JDK. Due to Kotlin code being 100% Java-interoperable, I would say that Liberica JDK is the runtime for Kotlin.

However, keep in mind that the status of the Multiplatform project is still in the alpha phase. It’s not stable yet, and you could expect some migration issues.

Types

Kotlin types are very similar to Java (numbers, characters, booleans, arrays and strings) with one main difference: primitives do not exist in Kotlin. Although primitives can’t be explicitly defined, Kotlin’s compiler will translate types into primitives in the JVM bytecode every time that it’s possible to favour a lower memory footprint.

Creating a new instance

Some languages like Java or C++ require the use of the “new” operator to create a new instance of an object. We’re all used to it, but is it indispensable? Why not just call the constructor?

This is precisely what Kotlin does. We no longer need to use “new” in Kotlin, calling an object’s constructor is enough to create a new instance.

For example, we can create an instance of an Order object like this:

val order = Order(“id”, 10, BigDecimal(12.5), OrderType.BUY)

End of statements

For instance, why are we forced to write a semicolon at the end of each line in some languages? Providing the ability to write multiple statements in one single line is unnecessary and possibly not recommended for readability purposes, so why not accept a break line as the standard to represent the end of a statement? If you’re a Java or C++ developer, you’ve probably missed a semicolon thousands of times. Think about all the time wasted. Kotlin doesn’t require using a semicolon to close a statement, although if you do it, it will still compile.

Type inference and immutability

Kotlin also reduces the verbosity of having to declare the type of variables twice. Type inference allows us just to declare the creation of a new variable, inferring the type from the right-hand side part of the statement.

Another improvement in variable declarations is that we don’t have to use any additional modifier to declare a variable as immutable. Kotlin provides just two keywords: var and val. Much simpler than in other languages, if you need a mutable variable use var; if you need an immutable variable use val, as simple as that.

For instance, the following snippet would be allowed. We can override the value of a mutable variable defined with “var”.

var order: Order = Order("id1", 10, BigDecimal(12.5), OrderType.BUY)
order = Order("id2", 12, BigDecimal(15.5), OrderType.SELL)

On the other hand, this situation won’t be allowed, as “order” is a immutable variable:

val immutableOrder: Order = Order("id1", 10, BigDecimal(12.5), OrderType.BUY)
immutableOrder = Order("id2", 12, BigDecimal(15.5), OrderType.SELL) //Compilation error!

Data classes

Kotlin provides a very neat and concise way of defining immutable objects. Here they’re called “data classes”. Declaring a class with immutable fields has always been specially over-complicated and verbose in Java, while in Kotlin you can achieve this in a single line:

data class Employee(val id: String, val name: String, val age: Int) {}

The NullPointerException dilemma

If you’re a Java developer, you’ve definitely suffered the issues of having null as a possible value for an object or variable. What would be the simplest solution to this problem? Common sense tells us that not allowing nulls in the first place would probably be the simplest and safest solution; and that’s exactly what Kotlin does. If you try to assign a null value to an object in Kotlin, you’ll get a compilation error. So, by default, objects are not nullable, you’d have to explicitly tell the compiler that the object is nullable to allow nulls. That’s great, isn’t it? Once more, simplicity and safety first.

val order: Order = null //Compilation error!
val nullableOrder: Order? = null

String interpolation

I bet you’ve missed this feature hundreds of times. If you use a language with no support for string interpolation, having to concatenate different parts of a string to build the expected format is quite cumbersome. Kotlin provides a straightforward and fine way to do string interpolation. For instance, you could do this:

val name = "John"
val surname = "Smith"
println("My name is $name $surname")

Object destructuring

Kotlin brings to us one of the coolest features available in JavaScript, object destructuring. We can do something like this:

val (name, age) = Employee(“id1”, “John”, 35)

Looks great, although unfortunately, this can only be done with data classes. If we wanted to use string interpolation without data classes, we are forced to implement component1() and component2() operators in our Order object, something that I’m personally not very happy with. For instance, if our Employee class weren’t a data class, we would have to do the following to be able to use object destructuring:

class Employee(val id: String, val name: String, val age: Int) {
   operator fun component1(): String = name
   operator fun component2(): Int = age
}

You could also use Kotlin extensions to implement the required operators for the Employee class:

private operator fun Employee.component1(): String = name
private operator fun Employee.component2(): Int = age

You must be wondering why we have to do that to be able to use object destructuring. The actual reason is that Kotlin compiler compiles a data class to the following if you decompile the corresponding class file:

// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package com.coding.kotlin

public final data class Employee public constructor(id: kotlin.String, name: kotlin.String, age: kotlin.Int) {
   public final val age: kotlin.Int /* compiled code */

   public final val id: kotlin.String /* compiled code */

   public final val name: kotlin.String /* compiled code */

   public final operator fun component1(): kotlin.String { /* compiled code */ }

   public final operator fun component2(): kotlin.String { /* compiled code */ }

   public final operator fun component3(): kotlin.Int { /* compiled code */ }
}

You can see that Kotlin provides a component operator for each property defined in the data class. However, if you modify Employee class not to be a data class, just a simple Kotlin class, then you will see that Kotlin doesn’t override those operators.

// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package com.coding.kotlin

public final class Employee public constructor(id: kotlin.String, name: kotlin.String, age: kotlin.Int) {
   public final val age: kotlin.Int /* compiled code */

   public final val id: kotlin.String /* compiled code */

   public final val name: kotlin.String /* compiled code */
}

One thing that Kotlin’s object destructuring solves nicely is the complex and messy manner that Java provides to iterate through the entries in a Map. In Kotlin we can simply do the following:

for ((key, value) in myMap) {
   println("$key, $value")
}

This is definitely much more pleasant and still as fast!

Default arguments

In some languages, we have to duplicate constructors to allow the optionality of some of its arguments, something that adds clutter to our classes.

Kotlin allows the option of providing default values for our arguments when they’re not specified.

For instance, if we define a default value for our order type:

constructor(userId: String, quantity: Int, price: BigDecimal, type: OrderType = OrderType.BUY)

We can now instantiate an Order object without specifying the order type:

val buyOrder = Order("id", 10, BigDecimal(13.2))

Named arguments

If you’ve worked with complex domains with a considerable number of fields, it’s often difficult to keep track of what argument in the constructor corresponds to what field in the component. Kotlin solves this by providing the ability to name arguments in the constructor, this allows a more explicit declaration of arguments in a constructor, improving its readability considerably. Also, we wouldn’t have to place arguments in the same order in this case.

For example:

val sellOrder = Order("id", type = OrderType.SELL, price = BigDecimal(3.0), quantity = 1)

In general, we have less rigidity and a more expressive and readable language.

Async programming - Coroutines and Channels

Kotlin takes a different approach to languages like Java to run multi-threaded applications. The way Kotlin works is very similar to what Golang does.3

Golang provides its goroutines and channels in the same way that Kotlin provides coroutines and channels.

Kotlin coroutines are very lightweight components used to run tasks within a given scope. You can think of channels as pipes used to communicate between coroutines to achieve a common goal safely. The main benefit of Kotlin’s approach is that writing asynchronous code becomes much simpler. We’ll go through the reasons behind it in our next article in the series.

Kotlin’s coroutines and channels are definitely a fascinating topic that probably deserves a sole article, but we’ll leave it here for the time being in this introductory piece on Kotlin. I’m going to give a more extended introduction to them in Part 2 of this small series.

One last thing to mention is that Kotlin has some handy libraries to help us develop asynchronous applications, such as Ktor, an asynchronous framework for microservices and web applications in general.4

We could definitely go on for much longer showing all the existing features that Kotlin has come up with. But this is probably enough to give you a good idea of the radical change of mindset that this language has brought to the JVM world. A language design driven by developers, focused on productivity, simplicity and comfortability to make us enjoy what we do. A battle against boilerplate code and unnecessary clutter.

This is the kind of pragmatism that Kotlin is aiming for, minuscule things that may seem insignificant by themselves. But when put together, they make a huge difference in terms of productivity!

Tune in next time…

As you could see after this introduction to Kotlin language, it’s a language designed with pragmatism and conciseness always in mind. Instead of reinventing the wheel with a completely new language, its developers have basically taken the nicest features from different languages in order to build one of the most beloved languages nowadays — a very sensible approach that guarantees success. Why make up something new when you already know what people love in other languages?

The result is a language that keeps all the good parts of Java but simultaneously brings a more flexible and pragmatic approach that makes our day-to-day work as developers much more enjoyable. If you ask me, I think that JetBrains’ team has demonstrated that they know what developers like and what they don’t, a knowledge probably acquired from their experience building the most popular IDE.

In my next article, we’ll be digging in a bit more into a few more interesting Kotlin features and discuss how Kotlin is currently doing in the market. We’ll also be talking about one very exciting aspect: what future lies ahead for Kotlin as a programming language.

References

  1. Kotlin Language Guide.
  2. How Kotlin Multiplatform helps reduce app development time.
  3. The Go Programming Language.
  4. Ktor: Build Asynchronous Servers and Clients in Kotlin.
Author image

The Bored Dev

Software Developer & Expert, Special for BellSoft

BellSoft LTD [email protected] BellSoft LTD logo Liberica Committed to Freedom 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 111 North Market Street, Suite 300 CA 95113 San Jose US +1 702 213-59-59