The lesson about classes shows how to use secondary constructors, but then strongly recommends not to use them, and to favor creating a top-level function that would return an object instead.
I’ve seen that secondary constructors have been removed and reintroduced in Kotlin.
So, is there a kind of official recommendation about how to deal with multiple ways to build an object?
It says it is just not good practice; and to exploit Kotlin named arguments with default parameters to achieve what used to be done with several constructors in Java. Then adds that if you need anything specific, to prefer that way I described rather than a secondary constructor. Meh. I’m puzzled.
I almost never use secondary constructors, nor init-blocks.In my classes are the logic that either update the fields or queries the fields.
If you would use a class A almost always to construct another class B, I add a function to class A that constructs class B and I would put the logic of construction in A.
If the classes are more stand-alone, so they are very tight to their fields, I most of the time would create top-level extension-functions.
In either way, you can write the code fluently.
And I almost never put a lambda in a constructor, unless it stores the lambda.
I would much rather prefer to use it in a top-level function.
In this case, I would probably put an extension-function inside FooKt:
fun State.buildFoo() = Foo(calculateBar()).apply{
//do something.
}
Secondary constructors are not officially discouraged, but in my practice, it looks ugly and is not really needed in kotlin, so I would agree, it is better to avoid them. The general way for alternative builders is a builder function in companion object. The only case, when secondary constructor makes sense is when you use it a lot and it is more concise to write MyType(...) instead of MyType.build(...).
I suppose I can see why someone would suggest that especially when things like data classes and primary constructors exist. You would want class constructors to reflect the necessary and sufficient parameters that a class needs, and functions which convert different data sets and data feeds into those necessary and sufficient parameters should be semantically separate from the constructors.
Personally I’ve been using the companion object for a lot of these means:
class Image(private val data: IntArray, val width: Int, val height: Int) {
...
companion object {
fun FromFile( val filePath: String) : Image {
....
}
}
}
From my perspective there are two main points to consider: Java compatibility and semantics. First semantics, is the constructor mainly a general purpose way of creating the object as if it was calling the constructor, then I would go for secondary if possible, or operator invoke() / toplevel otherwise (operator invoke allows for better Java compatibility). If it is fundamentally a factory function that may return different types then use a factory. Java compatibility is why I prefer a secondary constructor as it works as-designed. In case of inheritance secondary constructors can be invoked by the children, primary ones cannot.
Advantage of invoke is that before calling class constructor, you can calculate things, have local variables, call some functions, etc.
While in case of secondary constructor, the first thing you must do is calling primary constructor, so there’s little space for some code.
data class Demo(val x: Int) {
companion object {
operator fun invoke(x: Int, y: Int): Demo{
val v = someCalculation(x, y)
return Demo(v)
}
}
}
Sure, but for simple cases the secondary constructor looks better(IMO) and might be better for compatibility as @pdvrieze pointed out. So I think secondary constructors are perfectly fine, and not discouraged.
Definitely no rules here. It’s just that I found that I removed almost all secondary constructors after migrating java code to kotlin. Not intentionally, it just looked better.