2

I have a Kotlin data class with 15 fields. When I construct an instance of this class in my Kotlin code using the primary constructor, I can make use of useful features such as:

  • omitting fields with default values
  • use named parameters

However, when constructing an instance of this Kotlin class from Java code, I am faced with the fact I need to provide all 15 parameters in the constructor, in the correct order, without the ability to name them. This is especially inconvenient when this Java code is a unit test, where I'm not really interested in filling all those fields, but only one or two useful for the test.

When using purely Java, I would not face this problem, as I would use a (Lombok) builder to construct the instance of the object, having the flexibility to only provide the fields I'm interested in.

Is there a way to mitigate this problem, or is this the price I have to pay for mixing Java and Kotlin?

6
  • 3
    There's an annotation you can use on the constructor from the Kotlin side @JvmOverloads to 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. Commented Aug 21, 2020 at 15:58
  • @Tenfour04, interesting! Just posted a different solution but did not know about the one you mentioned. And 2^15 constructors is definitely something to think about, doubt that anyone would do this actually (knowing the cost). Commented Aug 21, 2020 at 16:00
  • Works for 4 parameters, though (16 constructors is certainly manageable. I might go all the way up to 5 or 6 parameters). Commented Aug 21, 2020 at 16:02
  • Oops, I'm wrong. It doesn't create every possible combination of variables. It's one overload per parameter with default. For each shorter overload, the last parameter is omitted. Commented Aug 21, 2020 at 17:59
  • @Tenfour04, that annotation is indeed useful, thanks for mentioning it! Commented Aug 24, 2020 at 12:53

1 Answer 1

1

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:

  1. Primary + secondary constructor;
  2. 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.

  1. 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.
  2. 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.
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your detailed answer Jenea, I marked it as accepted, even though I hoped there would be something better.
@Benjamin, you are welcome. Maybe in future will be a better solution but for now, this is what we have as far as I know.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.