I have started learning Kotlin. I would like to know the difference between init block and constructor.
What is the difference between this and how we can use this to improve?
class Person constructor(var name: String, var age: Int) {
var profession: String = "test"
init {
println("Test")
}
}
The init block will execute immediately after the primary constructor. Initializer blocks effectively become part of the primary constructor.
The constructor is the secondary constructor. Delegation to the primary constructor happens as the first statement of a secondary constructor, so the code in all initializer blocks is executed before the secondary constructor body.
Example
class Sample(private var s : String) {
init {
s += "B"
}
constructor(t: String, u: String) : this(t) {
this.s += u
}
}
Think you initialized the Sample class with
Sample("T","U")
You will get a string response at variable s as "TBU".
Value "T" is assigned to s from the primary constructor of Sample class.
Then immediately the init block starts to execute; it will add "B" to the s variable.
Next it is the secondary constructor turn; now "U" is added to the s variable to become "TBU".
Since,
The primary constructor cannot contain any code.
https://kotlinlang.org/docs/reference/classes.html
the init{..} blocks allow adding code to the primary constructor.
A class in Kotlin class a primary constructor (the one after a class name) which does not contain code, it is only able to initialize properties (e.g. class X(var prop: String)).
The init{..} block in the place, where you can put more code that will run after properties are initialized:
initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers
More about that is in https://kotlinlang.org/docs/reference/classes.html#constructors
Here is an example:
class X(var b: String) {
val a = print("a")
init {
print("b")
}
constructor() : this("aaa") {
print("c")
}
}
X()
When called it prints abc. Thus the init{..} in invoked after primary constructor but before a secondary one.
As stated in the Kotlin docs:
The primary constructor cannot contain any code. Initialization code can be placed in initializer blocks, which are prefixed with the init keyword.
During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers: ...
https://kotlinlang.org/docs/classes.html#constructors
Related
In Java you can declare a private final member variable and then initialize it from your constructor, which is very useful and is a very common thing to do:
class MyClass {
private final int widgetCount;
public MyClass(int widgetCount) {
this.widgetCount = widgetCount;
}
In Kotlin how do you initialize final member variables (val types) with values passed in to a constructor?
It is as simple as the following:
class MyClass(private val widgetCount: Int)
This will generate a constructor accepting an Int as its single parameter, which is then assigned to the widgetCount property.
This will also generate no setter (because it is val) or getter (because it is private) for the widgetCount property.
As well as defining the val in the constructor params, you can use an init block to do some general setup, in case you need to do some work before assigning a value:
class MyClass(widgetCount: Int) {
private val widgetCount: Int
init {
this.widgetCount = widgetCount * 100
}
}
init blocks can use the parameters in the primary constructor, and any secondary constructors you have need to call the primary one at some point, so those init blocks will always be called and the primary params will always be there.
Best to read this whole thing really!
Constructors
class MyClass(private val widgetCount: Int)
That's it. If in Java you also have a trivial getter public int getWidgetCount() { return widgetCount; }, remove private.
See the documentation for more details (in particular, under "Kotlin has a concise syntax for declaring properties and initializing them from the primary constructor").
open class Super {
open var name : String = "Name1"
init {
println("INIT block fired with : $name")
name = name.toUpperCase()
println(name)
}
}
class SubClass(newName : String) : Super() {
override var name : String = "Mr. $newName"
}
fun main(args: Array<String>) {
var obj = SubClass("John")
println(obj.name)
}
The above Kotlin code results in the following TypeCastException :
INIT block fired with : null
Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type java.lang.String
at Super.<init>(index.kt:7)
at SubClass.<init>(index.kt:13)
at IndexKt.main(index.kt:21)
As my understanding goes while inheriting from a class in Kotlin, first the primary constructors and init blocks and secondary constructors of super classes are called with passed parameters. After which the subclass can override such properties with its own version.
Then why does the above code results in the exception as described ... What am I doing wrong ... Why does the init block in super class is fired with null ...??? At first my speculation was that the init block might get fired before the actual property initialization as it is executed as a part of primary constructor but initializing the name property in the primary constructor as below gives the same error and the IDE would have warned me if so.
open class Super(open var name : String = "Name1") {
init {
println("INIT block fired with : $name")
name = name.toUpperCase()
println(name)
}
}
class SubClass(newName : String) : Super() {
override var name : String = "Mr. $newName"
}
fun main(args: Array<String>) {
var obj = SubClass("John")
println(obj.name)
}
Console :
INIT block fired with : null
Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type java.lang.String
at Super.<init>(index.kt:5)
at Super.<init>(index.kt:1)
at SubClass.<init>(index.kt:11)
at IndexKt.main(index.kt:19)
Am I doing something wrong here or is this a language bug...??? What can I do to avoid the error and to make the init blocks fire with the actual passed value and not null ... ??? Elaborate what is happening behind the scene. At this time I have several situations with classes like this in my actual codebase where I want to inherit from another classes, I want to maintain property names as they are...
Essentially, because you tell Kotlin that your subclass is going to be defining name now, it is not defined when the init block in Super is executed. You are deferring definition of that until the SubClass is initialized.
This behavior is documented on the Kotlin website under "Derived class initialization order":
During construction of a new instance of a derived class, the base class initialization is done as the first step (preceded only by evaluation of the arguments for the base class constructor) and thus happens before the initialization logic of the derived class is run.
...
It means that, by the time of the base class constructor execution, the properties declared or overridden in the derived class are not yet initialized. If any of those properties are used in the base class initialization logic (either directly or indirectly, through another overridden open member implementation), it may lead to incorrect behavior or a runtime failure. Designing a base class, you should therefore avoid using open members in the constructors, property initializers, and init blocks. [emphasis mine]
FWIW, this is similar to the reason that some Java code analysis tools will complain if you refer to non-final methods in a constructor. The way around this in Kotlin is to not refer to open properties in your init blocks in the superclass.
Have the same trouble, a disgusting issue with kotlin, when subclass constructor is ignored or initialized after super class calls internal method, this is not a safe thing, if not worst i found in kotlin.
I'm constructing a class and then trying to call a member method of that class as a default value for one of the constructor args.
Why isn't this valid Kotlin?
// unresolved reference: defaultText
class MyThing(val text: String = defaultText()) {
fun defaultText() = "hi"
}
It's possible using two separate constructors in both Java and Kotlin, but then I lose the conciseness of default args.
class MyThing {
private val text: String
constructor(text: String) {
this.text = text
}
constructor() {
this.text = defaultText()
}
private fun defaultText(): String {
return "hi"
}
}
The biggest problem of having a constructor's default parameter expression call a member function of the same instance is that the default arguments are evaluated before the constructor is called.
Given that, such a member function would have to run on a completely un-initialized instance of the class (because even the super constructors will work after that, see this answer about the execution order).
Usually, member functions perform some logic taking the instance state into account, and having a member function run on an empty instance might break some of that logic (e.g. all fields will hold nulls, even the backing fields of Kotlin not-null properties). Overall, even when such calls do not fail at runtime, they are likely introduce subtle bugs, so using a completely uninitialized instance is prohibited.
With regard to the secondary constructor, well, at least it runs after the super constructor initializes some part of the instance, which is thus not completely empty, but it's up to you to make sure you don't use the parts of the class that are not initialized (if you do, you may again encounter a runtime failure or introduce a bug).
I'd rather suggest using a function of a companion object (those are initialized before the class is first used) for this purpose:
class MyThing(val text: String = defaultText()) {
companion object {
fun defaultText() = "hi"
}
}
Or even a top-level function:
fun defaultText() = "hi"
class MyThing(val text: String = defaultText())
Recently started with Kotlin
According to Kotlin docs, there can be one primary constructor and one or more secondary constructor.
I don't understand why I see this error
Since class test has no primary constructor.
This works fine:
open class test {
}
class test2 : test() {
}
And here is another difficulty I have faced, when I define a secondary constructor the IDE shows another error saying
Supertype initialization is impossible without primary constructor
But in the previous working example, I did initialize it, yet it worked fine. What did I get wrong?
You get this error because, even if you don't define a primary or a secondary constructor in a base class, there is still a default no-argument constructor generated for that class. The constructor of a derived class should always call some of the super constructors, and in your case there is only the default one (this is the constructor that you can call like test() to create an object of the class). The compiler and IDE force you to do that.
The super constructor rules complicate the matter to some degree.
If you define a secondary constructor in the derived class without defining the primary constructor (no parentheses near the class declaration), then the secondary constructor itself should call the super constructor, and no super constructor arguments should be specified in the class declaration:
class test2 : test { // no arguments for `test` here
constructor(a: Int) : super() { /* ... */ }
}
Another option is define the primary constructor and call it from the secondary one:
class test2() : test() {
constructor(a: Int) : this() { /* ... */ }
}
I'm trying to understand why the following code throws:
open class Base(open val input: String) {
lateinit var derived: String
init {
derived = input.toUpperCase() // throws!
}
}
class Sub(override val input: String) : Base(input)
When invoking this code like this:
println(Sub("test").derived)
it throws an exception, because at the time toUpperCase is called, input resolves to null. I find this counter intuitive: I pass a non-null value to the primary constructor, yet in the init block of the super class it resolves to null?
I think I have a vague idea of what might be going on: since input serves both as a constructor argument as well as a property, the assignment internally calls this.input, but this isn't fully initialized yet. It's really odd: in the IntelliJ debugger, input resolves normally (to the value "test"), but as soon as I invoke the expression evaluation window and inspect input manually, it's suddenly null.
Assuming this is expected behavior, what do you recommend to do instead, i.e. when one needs to initialize fields derived from properties of the same class?
UPDATE:
I've posted two even more concise code snippets that illustrate where the confusion stems from:
https://gist.github.com/mttkay/9fbb0ddf72f471465afc
https://gist.github.com/mttkay/5dc9bde1006b70e1e8ba
The original example is equivalent to the following Java program:
class Base {
private String input;
private String derived;
Base(String input) {
this.input = input;
this.derived = getInput().toUpperCase(); // Initializes derived by calling an overridden method
}
public String getInput() {
return input;
}
}
class Derived extends Base {
private String input;
public Derived(String input) {
super(input); // Calls the superclass constructor, which tries to initialize derived
this.input = input; // Initializes the subclass field
}
#Override
public String getInput() {
return input; // Returns the value of the subclass field
}
}
The getInput() method is overridden in the Sub class, so the code calls Sub.getInput(). At this time, the constructor of the Sub class has not executed, so the backing field holding the value of Sub.input is still null. This is not a bug in Kotlin; you can easily run into the same problem in pure Java code.
The fix is to not override the property. (I've seen your comment, but this doesn't really explain why you think you need to override it.)
The confusion comes from the fact that you created two storages for the input value (fields in JVM). One is in base class, one in derived. When you are reading input value in base class, it calls virtual getInput method under the hood. getInput is overridden in derived class to return its own stored value, which is not initialised before base constructor is called. This is typical "virtual call in constructor" problem.
If you change derived class to actually use property of super type, everything is fine again.
class Sub(input: String) : Base(input) {
override val input : String
get() = super.input
}