Friends of OpenJDK Today

Beautify Third-Party APIs with Kotlin

December 21, 2021

Author(s)

  • Avatar photo
    Nicolas Frankel

    Nicolas is a developer advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). ... Learn more

Scala has popularized the "Pimp my library" pattern:

This is just a fancy expression to refer to the ability to supplement a library using implicit conversions.

-- Pimp My Library Pattern in Scala

Kotlin does provide the same capability. However, it achieves it via extension functions. While the generated bytecode is similar to Java's static methods, the developer experience is the same as adding functions to existing types.

This approach has limitations, though. One cannot update the type hierarchy.

Imagine a library that offers a component with an open/close lifecycle. The component opens when you instantiate it, but you need to make sure to close it after usage, e.g., a file, a stream, etc.

Before Java 7, you had actually to close the component explicitly:

Component component;
try {
    component = new Component();
    // Use component
} finally {
    component.close();
}

Java 7 introduced the try-with-resource statement so that you can write something like this:

try (Component component = new Component()) {
    // Use component
}                                                // 1
  1. Component is closed here in a generated finally block

However, Component must implement AutoCloseable.

Kotlin provides the use() extension function on Closeable.
Hence, one can replace the try-with-resource statement with a simple function call:

Component().use {
  // Use component as it
}                                                // 1
  1. Component is closed here in a finally block

That being said, imagine that the library above implements neither Closeable nor AutoCloseable. We cannot use use(). Kotlin delegation to the rescue!

The Delegation pattern is widespread in Object-Oriented languages:

In delegation, an object handles a request by delegating to a second object (the delegate). The delegate is a helper object, but with the original context. With language-level support for delegation, this is done implicitly by having self in the delegate refer to the original (sending) object, not the delegate (receiving object). In the delegate pattern, this is instead accomplished by explicitly passing the original object to the delegate, as an argument to a method.

-- Wikipedia

Implementing the Delegation pattern in Java requires writing a lot of boilerplate code. The more methods the original class has, the more boring it is:

interface class Component {
    void a();
    void b();
    void c();
}

public class CloseableComponent extends Component implements Closeable {

    private final Component component;

    public CloseableComponent(Component component) {
        this.component = component;
    }

    void a() { component.a(); }
    void b() { component.b(); }
    void c() { component.c(); }
    public void close() {}
}

Kotlin supports the Delegation pattern out-of-the-box via the by keyword. One can rewrite the above code as:

interface Component {
    fun a() {}
    fun b() {}
    fun c() {}
}

class CloseableComponent(component: Component) : Component by component,
                                                 Closeable {                  // 1
    override fun close() {}
}
  1. Delegate all calls of a(), b(), and c() to the underlying component

We can finally write the desired code:

CloseableComponent(RealComponent()).use {
    // Use component as it
}

Even better, it works with third-party code to improve an external library with this approach.

The icing on the cake, one can also call the code from a try-with-resource Java statement:

try (CloseableComponent component = new CloseableComponent(new RealComponent())) {
    // Use component
}

As I wrote above, one can do it in Java also. In general, however, the sheer amount of boilerplate code that one needs to write to implement delegation is a significant impediment. Kotlin makes it a breeze.

We miss one last step to make our code easier to write. How do we get the CloseableComponent? Let's create an extension function on Component:

fun Component.toCloseable() = CloseableComponent(this)

And now, usage is fluent:

RealComponent().toCloseable().use {
    // Use component
}

In this post, we have seen how to improve the API provided by third-party libraries. We achieved it by combining Kotlin extension functions and delegation.

To go further:

Originally published at A Java Geek on December 19th, 2021

Topics:

Related Articles

View All

Author(s)

  • Avatar photo
    Nicolas Frankel

    Nicolas is a developer advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). ... Learn more

Comments (0)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard