I was asked to highlight that you can use the
invoke() operator on a class’s
companion object as a way to have a “validation constructor”.
First of all, cool! I didn’t realise you could do this, but today I learned and it makes sense:
a companion object is initialized when the corresponding class is loaded (resolved), matching the semantics of a Java static initializer.
An initialized companion object is still an instance of a class (of type
MyClass.Companion) with which you can overload operators.
Overloading invoke() in a companion object
Let’s see this in action. I haven’t included any third-party libraries, so while the tweet above refers to an
Option<User> I’ll just be using Kotlin’s optional type:
And what does it look like to use?
Should I overload the invoke() operator in a companion object?
I’m not a fan — I would be slightly astonished if I encountered this in a project.
For me, because it looks so much like a constructor invocation, I would expect that these were initializing objects of type
IceCream or crashing at runtime because of some validation error inside the constructor.
Are there any hints that the type of these values is not
IceCream ? Yes!
- if you explicitly specify the type, or hover over the value, you’ll see that they’re of type
- when you try to use it and get a type error, you’ll realise it’s not
- If you hold cmd/ctrl and hover over the
()with your mouse, you’ll see it’s clickable and will take you to the declaration of the
So what alternatives are there?
You can use factory classes or even named functions in your companion objects to be more explicit.
I spoke with Paco about these factories returning
Option<MyClass> instead of
MyClass? or even throwing an exception in case of invalid parameters and he shared:
“This conversation on Twitter highlighted one improvement we could have in our codebases. By moving to validated constructors, we can avoid a whole set of runtime errors.
“These errors would be highly conditional on the parameters on the constructor, which means difficult to trigger while debugging, and would need to be documented and taken into account when refactoring.
“By simply creating a new validated constructor that returns null or an `Option` (i.e. Arrow/Koptional on a failure), we’re conveying the needs for our object to be constructed, and those will be explicitly mandated across future calls and refactors without requiring users to check the documentation.”