Leveraging default values in Java

Default values are a powerful feature of Kotlin. They allow us to write fewer overloaded functions and can even help us replace the builder pattern. Unfortunately, we can't leverage this feature from Java. Because of this, we have to think carefully about how to write functions if they will be used from both Kotlin and Java. If a class written in Kotlin will be used from Java, you may have to consider whether or not you can make heavy use of default values.

To help make this decision, we can consider making use of the @JvmOverloads annotation to generate Java method overloads, which will make use of our defined default values.

In this snippet, we've defined a class, Student, that includes a primary constructor. This constructor includes four properties that all contain a default value:

class Student(
val firstName:String = "",
val lastName: String = "",
val grade: Int = 10,
val classes:List<String> = listOf("Home Room", "Math")
)

With this class declaration, we can instantiate instances of Student in Kotlin using 0-4 argument values in the constructor:

// Main.kt
fun main() {
val defaultStudent = Student()
val student = Student(
"John",
"Smith",
12,
listOf("Home Room", "Math", "Science", "Music")

)
}

From Java, however, we are required to pass all the argument values to the constructor:

// Main.java
public class Main {
public static void main(String[] args) {
Student student = new Student(
"John",
"Smith",
11,
Arrays.asList("Home Room", "Math", "Science", "Music")

);
}
}

To improve this, we can add the @JvmOverloads annotation to the constructor of our Student class:

class Student @JvmOverloads constructor(
val firstName:String = "",
val lastName: String = "",
val grade: Int = 10,
val classes:List<String> = listOf("Home Room", "Math")
)

Adding this annotation will tell the compiler to generate constructor overloads that use the default values. After adding this annotation, it's then possible for us to invoke the Student constructor from Java without having to include each argument value:

// Main.java
public class Main {
public static void main(String[] args) {
Student student = new Student(
"John",
"Smith",
11,
Arrays.asList("Home Room", "Math", "Science", "Music")
);

Student defaultStudent = new Student();
}
}

How does this work? It works because the Kotlin compiler generates multiple constructors that all call through to each other and include the defined default values. The following code demonstrates what the Kotlin compiler generates for our Student class once the @JvmOverloads annotation has been added:

public final class Student {
...
@JvmOverloads
public Student(@NotNull String firstName,
@NotNull String lastName,
int
grade, @NotNull List classes) {
...
}

@JvmOverloads
public Student(@NotNull String firstName,
@NotNull String lastName,
int
grade) {
this(firstName, lastName, grade, (List)null, 8,
(DefaultConstructorMarker)null);
}

@JvmOverloads
public Student(@NotNull String firstName,
@NotNull String lastName) {
this(firstName, lastName, 0, (List)null, 12,
(DefaultConstructorMarker)null);
}

@JvmOverloads
public Student(@NotNull String firstName) {
this(firstName, (String)null, 0, (List)null, 14,
(DefaultConstructorMarker)null);
}

@JvmOverloads
public Student() {
this((String)null, (String)null, 0, (List)null, 15,
(DefaultConstructorMarker)null);
}
}

In cases where your function or constructor arguments are generally called sequentially, this solution might work well for you. The limitation of not being able to use named arguments to explicitly control the order of passed arguments means that even the generated overloads may not provide the same level of convenience as the Kotlin version of the function.

Using the @JvmOverloads annotation allows you to write concise, flexible APIs in Kotlin, and allows the compiler to generate the appropriate overloads for Java clients. This reduces the amount of code you must write, and can allow you to focus on writing idiomatic Kotlin code. In the next section, we'll examine how to make companion objects easier to work with from Java.