Article
Kotlin for Java: Part I — Constructors & Builders
February 25, 2019
Remember the day you switched over from Java to Kotlin? It was such a good day. You discovered that a simple data model did not have to be 500 lines long and it could still have all the same logic. Best of all, Kotlin is interoperable with Java, meaning you can write with all the fancy cool new tools and it would all work seamlessly with the older code. Right?
No! Never! Keep dreaming!
Sure, some of it does just work. And even the parts that do not can be coerced to get the job done. But that does not make for the best development culture, and it is something most people do not want to do themselves. Writing Kotlin to be used in Java only takes a little extra effort and if you are writing a library that is to be consumed by others, then the extra effort is mandatory. Luckily there are some easy tricks and simple tools to help smooth over the rough edges. Some of these tools are provided by Kotlin itself and others are just simple strategies in the way you write code to make sure that you handle all the use cases.
Kotlin offers wonderfully terse constructors; you can ensure that Java can easily invoke the constructors with a few simple techniques.
Constructors
One thing that Kotlin provides is default parameters: a super nifty tool that allows you to provide default values for some of the parameters in a constructor (or method). With this set up you can provide only the specific values needed and the class will fill in the rest. This works well with Kotlin, but back in Java land you are forced to set each parameter regardless of default parameters set.
In order to gain the same functionality in Java, you would need to create multiple secondary constructors that allow for a different subset of all the parameters.
A data class with multiple constructors:This looks exceptionally nasty and can grow out of control as more parameters are added. The solution to this is something the Kotlin language was kind enough to provide. It is an annotation called @JvmOverloads
. Simply add this annotation to a constructor and let the magic happen.
There are a few things of note here. The required
property must always be populated when calling this constructor. The nonnull
and nullable
properties are optional and when omitted will default to the values defined in the constructor. There is one rather large caveat since Java does not support named parameters. If you would like to define the nullable
parameter, you must define the nonnull
parameter as well, even if you just want it to be the default value. The reason for this is because the@JvmOverloads
annotation is creating secondary constructors in sequential order of the parameters and does not create every possible combination. This may not seem that bad on a simple model like this, but can be rather cumbersome when you have models with a dozen parameters or more.
Ordering parameters can be very important and in some cases, a full-blown Builder
may be a better option.
Builders
A tried and true tool of Java is the Builder, a class that helps a user construct models setting only the values that are required. These classes work equally well in Kotlin, although they are rarely needed since default parameters work just fine too.
A simple model with a builder:This works like any normal builder would, but thanks to a couple of fun Kotlin tools, it is slightly more condensed and automatically enforces that all the correct parameters are passed in.
lateinit
ensures that the required parameter is set or an exception is thrown when you callbuild
. The exception is pretty descriptive too and looks something like this:lateinit property <property name> has not been initialized
.apply
simplifies the setter methods by automatically returning theBuilder
because it is the caller. In many cases, this can condense the method to a single line.- The
newBuilder
method allows for easy copying and modification of the data model. It acts as a great replacement for the Kotlincopy
method that exists on alldata
classes. @JvmOverloads
is still present on the constructor which allows the simplest of use-cases to bypass theBuilder
entirely.
Now you can build data models with complete functionality and provide a better development culture for everyone, regardless of whether they are using Kotlin or Java. And be sure to catch Part II, where I will dive into sealed classes
and how to make them work effectively when interoperating with Java.