The character of Kotlin: Conciseness

Published Apr 04, 2018
The character of Kotlin: Conciseness

Originally published at: blog.kotlin-academy.com

In the previous article, I’ve explained why Kotlin is like the Iron Man. Now I want to continue this metaphor and show conciseness of Kotlin.

In Marvel universe, since Iron Man was created, other countries were trying to make their own versions. If you compare actual Iron Man and weak attempts to reproduce it, you can see that his suit is much smaller while much more powerful.

The power of Iron Man is that it is not a spaceship or a tank, but instead, it is the concise yet powerful structure. Every additional element is a cost because it needs to be moved, powered and maintained. Thanks to the fact that Iron Man costume is small, Tony Stark can easily defeat huge spaceship that uses much more power and resources.

This is also a huge power of Kotlin: It is highly concise language. Let’s see some important features that support it.

Class creation

Let’s discuss how we define the data model. We need to define a user that has id, name, surname and age. This is how we would define it in Java:

// Java 
public class User {
    private int id;
    private String name;
    private String surname;
    private int age;

    public User(int id, String name, String surname, int age) {
        this.id = id;
        this.name = name;
        this.surname = surname;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

This is how the same class looks in Kotlin:

class User(
        var id: Int,
        var name: String,
        var surname: String,
        var age: Int
)

Looks impossible? Yet we still have constructor, encapsulated fields etc. Part of Kotlin philosophy is that common thing should be simple. Defining data model is really common, so it shouldn’t be surprising that Kotlin is designed to support it. Let’s analyze transition step by step.

First of all, we don’t need setters and getters because they are build into properties. We can always change how property serves or setts value:

var name: String = "" get() = field.toUpperCase()
    set(value) {
        if(value.isNotBlank()) {
            field = value
        }
    }

Therefore direct equivalent of Java class User would be following:

class User {
    var id: Int
    var name: String
    var surname: String
    var age: Int
    
    constructor(id: Int, name: String, surname: String, age: Int) {
        this.id = id
        this.name = name
        this.surname = surname
        this.age = age
    }
}

Since it is really common that object has one dominant way of construction, Kotlin introduced the feature called primary constructor. Basically, it is constructor defined after class name that specifies fields that can be used during class creation. This is how we can use it in our class:

class User(id: Int, name: String, surname: String, age: Int) {
    var id: Int = id
    var name: String = name
    var surname: String = surname
    var age: Int = age
}

Next important improvement is that very often values for properties are passed via primary constructor. This is why Kotlin made more concise notation that allows properties to be defined in primary constructor. When we use it, we have our final notation:

class User(
        var id: Int,
        var name: String,
        var surname: String,
        var age: Int
)

Of course, we don’t need to do this transition step by step. IDE can do it for us:
youtube.com/watch?v=L4DMzvkXEWw

You may ask “What if I need to define other constructors?”. For instance, let’s say that we want to have constructor where we don’t pass id, but we want it generated automatically. We can do it using normal constructor (called “secondary” in Kotlin):

class User(
        var id: Int, 
        var name: String, 
        var surname: String, 
        var age: Int
) {

    constructor(name: String, surname: String, age: Int):
            this(UsersRepo.getNextId(), name, surname, age)
}

We can also provide default value for this property:

class User(
        var name: String,
        var surname: String,
        var age: Int,
        var id: Int = UsersRepo.getNextId()
)

I moved it to last position so user can create an object by User("Marcin", "Moskala", 25). Without it, we would have to use naming argument syntax if we wanted to omit first argument User(name = "Marcin", surname = "Moskala", age = 25).

On this point, you can notice that we don’t need to use new keyword to use a constructor. It also represents conciseness of Kotlin, and it also makes sense if you think about it. A constructor is like a function that created an object, and in Kotlin it is treated as a function. If you reference constructor, it is actually implementing function interface. It means it is really a function. This is how we can use constructor reference to map list of strings to list of objects:

class Person(val name: String)
val names = listOf("Tony", "Bruce")
val persons = names.map(::Person) // Person("Tony"), Person("Bruce")

Data modifier

This is not the end of Kotlin features that highly improve conciseness. In object -oriented languages like Java, we often need to define objects that represent some data. Above class, User is a perfect example. When we have some users, we want to make some operations on them like holding them on collections, comparing them or displaying their data for debugging purposes. This is why it is a very common pattern to define set of functions that allows it:

  • toString — used to display an object or during debug to represent an object in the clearer way.
  • equals — used to check if data in two objects are the same, it is also needed to keep an object on Set or to call distinct method.
  • hashCode — needed to keep objects in different kinds of collections like HashMap or HashSet.

These functions are so commonly added to classes that IDEA IntelliJ has special support for them:
youtube.com/watch?v=sn9OrZccNec

Kotlin made it even simpler. All you need to do is to add data modifier before a class:

data class User(
        var name: String,
        var surname: String,
        var age: Int,
        var id: Int = UsersRepo.getNextId()
)

When you have it, you can use methods from the list presented above:

data class User(var name: String?, var surname: String?, var age: Int)

val user = User("Marcin", "Moskala", 24)
println(user) // Prints: User("Marcin", "Moskala", 24)
println(user.toString()) // Prints: User("Marcin", "Moskala", 24)

println(user.equals(User("Marcin", "Moskala", 24))) // Prints: True
println(user.equals(User("Maciej", "Moskala", 26))) // Prints: False

println(user.hashCode()) // Prints: 184607782
println(user.hashCode()) // Prints: 184607782

data modifier creates also two more important features. First of all, it allows object deconstruction:

val user = User("Marcin", "Moskala", 24)
val (name, surname, age) = user
print("$name $surname of age $age") // Marcin Moskala of age 24

This way data classes can be used like a tuples and we don’t really need native tuple support. Another method that data modifier gives is copy. It creates new instance with concrete values changed:

val user = User("Marcin", "Moskala", 25)
val user2 = user.copy(name = "Maciej", age = 27)
user.age = 26

print(user) // User(name=Marcin, surname=Moskala, age=26)
print(user2) // User(name=Maciej, surname=Moskala, age=27)

This feature is really important, because thanks to that we can make object easily immutable and we will be able to easily make copies with changed properties. This is very useful when we are programming in functional style.

Operators

Another feature that improves conciseness and readability is operator overloading. Basically, we prefer to use == instead of equals method. It is more readable and more concise. Same with addition and more other operators. This is why Kotlin made the convention that functions with concrete names can be used like operators. For instance, we can compare data class objects using == instead of equals:

val user = User("Marcin", "Moskala", 25)

println(user == User("Marcin", "Moskala", 24)) // Prints: True
println(user == User("Maciej", "Moskala", 26)) // Prints: False

To show some bigger example, let’s say we need class that represents complex numbers. This is how we can define it together with some basic operators:

data class Complex(val real: Double, val img: Double) {

    operator fun plus(i: Double) 
        = Complex(real + i, img)

    operator fun plus(c: Complex) 
        = Complex(real + c.real, img + c.img)

    operator fun times(i: Double) 
        = Complex(real * i, img * i)

    operator fun times(c: Complex) 
        = Complex(real * c.real - img * c.img, real * c.img + img * c.real)
}

// Usage
val c1 = Complex(1.0, 2.0)
val c2 = Complex(2.0, 3.0)
println(c1 + 1.0) // Prints: Complex(real=2.0, img=2.0)
println(c1 + c2) // Prints: Complex(real=3.0, img=5.0)
println(c1 * 2.0) // Prints: Complex(real=2.0, img=4.0)
println(c1 * c2) // Prints: Complex(real=-4.0, img=7.0)

This notation is simple, concise and highly readable. Just like the whole Kotlin.


If you need help with learning Kotlin, remember that I give consultations.

To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter and observe Twitter. To reference me on Twitter, use @MarcinMoskala.

Discover and read more posts from Kotlin Academy
get started