DEV Community

Anthony Bruno
Anthony Bruno

Posted on • Originally published at anthonybruno.dev on

Common Java patterns in Kotlin

Recently, I’ve been using Kotlin for a project at work. As someone coming from a Java background, I’ve been taking notes on certain patterns that are used in Java, that can be done nicer in Kotlin. I feel like when using a new language, it’s easy to fall into the trap of writing non-idiomatic code simply because it’s what I would’ve done in another language.

I wrote this article with the idea that this is what I would’ve wanted when starting with Kotlin, and I plan on updating it as I learn more techniques. Anyway, let’s begin!

Multi-line Strings

Sometimes, it is nice to embed SQL or have HTML snippets in our application. In these cases, we want to be able to have a String that spans multiple lines. If we had it on a single line, it would be near impossible to read!

Java

In Java 11 and below, we have to resort to simple String + concatenation. Make sure you don’t forget to add new line endings \n or else when the String is built, it will all end upon one line!

// Java 11 and older
String query = "SELECT w.id, w.title, w.date, we.weight, e.name\n" +
        "FROM workouts as w\n" +
        "LEFT JOIN workout_exercises as we ON w.id = we.workout_id\n" +
        "LEFT JOIN exercises as e on we.exercise_id = e.id\n" +
        "WHERE w.user_id = ?";

However, starting from Java 13, we have a much nicer way of writing this!

// Java 13 and above
String query = """
        SELECT w.id, w.title, w.date, we.weight, e.name
        FROM workouts as w
        LEFT JOIN workout_exercises as we ON w.id = we.workout_id
        LEFT JOIN exercises as e on we.exercise_id = e.id
        WHERE w.user_id = ?
        """;

This is an example of a Text Block, which is a preview feature in Java 13 and 14. At the time of writing, this feature will be finalised in Java 15.

Kotlin

For Kotlin, the syntax is similar to what is available in Java 13+.

val query = """
    SELECT w.id, w.title, w.date, we.weight, e.name
    FROM workouts as w
    LEFT JOIN workout_exercises as we ON w.id = we.workout_id
    LEFT JOIN exercises as e on we.exercise_id = e.id
    WHERE w.user_id = ?
""".trimIndent()

You might be asking why we have to call the trimIndent function at the end.This is because if we don’t, Kotlin will construct the String including the initial whitespace indentation on each line. As we are only inserting that indentation for readability purposes, we have to call trimIndent which will remove this initial whitespace from each line.

I think this is a case where the Java way of Text Blocks is a bit better, as it will automatically trim the whitespace for us. However, Kotlin is here now and (at the time of writing) Java 15 is still a month or so away!

String Concatenation

Another common thing we like to do with Strings is to construct them with variables. For instance, we receive some input from the user and we want to create a message based on that input.

Java

Unfortunately, Java does not have a nice, modern way to do this and we have to resort to simply using + to build our String.

String name = "bob";
int age = 18;
String message = "My name is " + name + " and my age is" + age;

We do have other options that are useful in certain situations, such as StringBuilder and String#format. But for simple, straight forward situations, we can only use +.

Kotlin

In Kotlin, we can use template expressions inside a String. This greatly improves readability when a String is concatenated with multiple values.

val name = "bob";
val age = 18;
val message = "My name is $name and my age is $age"

// Can also use ${expression} for more complex usages
val message = "My name is $name and my age in 10 years will be ${age + 10}"

Static Utility Methods

When we want utility methods that we want to use without having create a new Object, it is common practice to add them as static methods on a class.

Frequently, ‘Util’ classes exist that only contain static methods. This is a pattern employed by popular libraries like GoogleGuava and ApacheCommons.

Java

public class StringUtils {

    public static char getLastLetter(String str) {
        if (str.isEmpty()) {
            throw new IllegalArgumentException("Provided string must not be empty");
        }
        return str.charAt(str.length() - 1);
    }

}

StringUtils.getLastLetter("abc") // => c

Kotlin

In Kotlin, we have a few options.

Firstly, we can have a top-level function. That is, a function that is not attached to a class. This language feature does not exist in Java.

We can call the function from anywhere we want, without needing to create a new object, or prefix a class name in front of the function call.

// Using a top-level function

fun getLastLetter(str: String): Char {
    if (str.isEmpty()) {
        throw IllegalArgumentException("Provided string must not be empty")
    }
    return str[str.length - 1]
}

getLastLetter("abc") // => c

Next, we can add a method to an object. This is roughly equivalent to the static method way we saw in Java, and the calling code is the same.

// Using an Object
// https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html
object StringUtils {

    fun getLastLetter(str: String): Char {
        if (str.isEmpty()) {
            throw IllegalArgumentException("Provided string must not be empty")
        }
        return str[str.length - 1]
    }

}
StringUtils.getLastLetter("abc") // => c

Finally, we have an extension function. This is a cool feature that allows us to attach a method to an existing class, without the need of creating a subclass or a new wrapped type!

// Using an extension function
// https://kotlinlang.org/docs/reference/extensions.html
// Preferred!
fun String.getLastLetter(): Char {
    if (this.isEmpty()) {
        throw IllegalArgumentException("Provided string must not be empty")
    }
    return this[this.length - 1]
}

"abc".getLastLetter() // => c

This extension function feature was one the initial features I saw when looking at Kotiln where I thought that it was really useful!

Singletons

In some cases, we want to be able to define and use singleton’s in our code.

Java

With Java, we can do this by creating a class with a private constructor. The class then provides a public static field that can be accessed from calling code.

public class BeanFactory {

    public static final BeanFactory INSTANCE = new BeanFactory();

    private BeanFactory() {
        // Prevent outside world creating a new BeanFactory
    }

    public Beans createBakedBeans() {
        return new BakedBeans();
    }
}

Kotlin

In Kotlin, we greatly reduce boilerplate code by simply defining a new objecttype.

object BeanFactory {

    fun createBakedBeans(): Beans {
        return BakedBeans()
    }

}

For more information about object, please read the relevant docs.

Conclusion

I’ve covered how to write common Java techniques in idiomatic Kotlin. Often, the equivalent Kotlin code is shorter and more readable.

My advice for Java developers starting with Kotlin: always be learning! If you find yourself encountering a new problem in Kotlin and you solve it by writing Java-ish code, slow down and see if there’s a better way to do it in Kotlin.Have a search on Google or talk to your favourite coworker about it.This way, you will continuously learn best practices while still being productive!

Top comments (4)

Collapse
 
jillesvangurp profile image
Jilles van Gurp

There are a few more things.

The builder pattern is basically obsolete with Kotlin because of several language features:

  • You can have blocks with a receiver. So instead of chaining setters, you can simply assign values to properties in a block with a receiver. This is also the basis for creating internal DSLs in Kotlin; which is much nicer than having a builder.
  • If you have a legacy Java builder, you can use the apply extension function on it and get some of the benefits that way. It basically removes the need for chaining calls like you would with a builder.
  • Functions and constructors can have arguments with default values. So instead of having gazillions of constructors, you simply have just one with sensible defaults for the things you want. Instead of needing a builder, you simply just call the constructor with the right arguments and rely on defaults for most of them.
  • Named arguments ensure that such constructor or function calls remain readable. Intellij offers a nice feature to add names to arguments. Such code is also more robust against method signature changes over time.
  • Data classes have a copy constructor where you can override values. This allows you to have immutable objects that you can easily create variants of by copying a prototype.

java.util.Optional is obsolete because you have nullable types in Kotlin and strong guarantees for that.

Inheritance is basically not needed anymore because you have extension functions and interface delegation. E.g. you can implement the Map interface and delegate to a mapOf() instead of extending some Map implementation. Extension functions work on classes, interfaces, and even type aliases. So you can trivially add new functions to existing types instead of having to extend types to be able to add stuff. This also works with things in the Kotlin or Java standard libraries. Even final classes.

Collapse
 
clavelm profile image
Mathieu CLAVEL

For singleton in java, an enum can be used to skip the serialization problem, as described at the end of Joshua Bloch's Effective Java 3rd edition, Item 3:


A third way to implement a singleton is to declare a single-element enum:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

This approach is similar to the public field approach, but it is more concise,
provides the serialization machinery for free, and provides an ironclad guarantee
against multiple instantiation, even in the face of sophisticated serialization or
reflection attacks. This approach may feel a bit unnatural, but a single-element
enum type is often the best way to implement a singleton. Note that you can’t
use this approach if your singleton must extend a superclass other than Enum
(though you can declare an enum to implement interfaces).

Collapse
 
babisr profile image
Babis

Java is trying really hard to catch-up with features that are available to other JVM languages (scala & kotlin) since a long time ago.

Collapse
 
aussieguy profile image
Anthony Bruno

Yep, I agree. The other JVM languages are definitely pushing Java to be better. Hopefully with Java's move to 6 month release cycles, they can slowly claw back some ground!