Is there a way to mitigate this problem...
Sort of, but it is not an out-of-the-box solution. More about it below.
... is this the price I have to pay for mixing Java and Kotlin?
No, it is not. If we turn this problem around you will notice that it is Java that is incapable of providing default values and thus fails to be convenient when used with Kotlin. Construction of complex objects is the main reasons why Builder Pattern exists. But Kotlin gives us a solution to avoid most of the problems related to construction of complex objects and makes builder pattern obsolete.
How to deal with it?
There is not one solution but multiple. I'll list at least two that came to my mind right away:
- Primary + secondary constructor;
- Factories.
Primary + secondary constructor
Using this option you can create a primary constructor with the whole list of
parameters your class should hold and introduce a secondary constructor that only accepts the values that are required or cannot be set by default:
class Example(val param1: Any, val param2: Any? = null, val param3: Any? = null) {
// In this situation we have to use different argument names
// to make explicit use of primary constructor.
// e.g. if we remove `param1 = ` or rename `requiredParam` to `param1`
// we will get an error saying: "There's a cycle in the delegation calls chain"
constructor(requiredParam: Any) : this(param1 = requiredParam)
}
Factories
In the case of factories, everything looks almost the same. Although, this solution will be more verbose in Java but it eliminates the need for using named arguments and gives us the freedom to do more preparation before object initialization. We can even make it asynchronous (as if it was a network call)!
class Example(val param1: Any, val param2: Any? = null, val param3: Any? = null) {
object Factory {
fun from(param1: Any): Example {
return Example(param1)
}
}
}
Conclusion
There is no "correct" solution. We can pick the one we like or maybe figure out a new one.
- Primary + secondary constructor:
- (+) less verbose when used from Java;
- (-) if secondary constructor change you will have to update Java code;
- (-) requires different names for constructor arguments.
- Factories:
- (+) gives more freedom: e.g. you can calculate something within
from function and it will be more readable in comparison to constructors;
- (+) hides constructor implementation. You will be able to change constructors later without modifying any Java code or factory functions;
- (-) will be more verbose when used from Java.
@JvmOverloadsto create all the overloaded constructors with default parameters applied when the class is used from Java. But you don't want to use that for something with 15 parameters that have defaults, because then you're looking at 2^15 constructors. Not sure what else could be done.2^15 constructorsis definitely something to think about, doubt that anyone would do this actually (knowing the cost).