From the first day we learn Java, we have Java’s class initialization order in mind. But in the actual development process, it seems difficult to access this part of the application. Before this, I also thought it was only the eight-part essay in the interview, until recently stepped on a pit, which found how important it is.

1.

As everyone knows, in the Java world, classes are initialized in the following order: static member variable or static code block of the parent class -> static member variable or normal code block of the parent class -> constructor of the parent class -> subclass member variable or normal code block -> constructor of the subclass.

None of us have a problem with understanding it, but it’s easy to lose sight of the order when you use it. Let me give you an example: Suppose you have two classes, Animal and Dog, of which Dog is descended from Animal. . Their code looks like this:

public class Animal { public Animal() { eat(); } protected void eat() { } } // ---------------------------- public class Dog extends Animal { private String foodName = "Meat". Public Dog() {} @override protected void eat() {system.out.println (" I eat "+ foodName); }}Copy the code

The code implementation is very simple, basically: the parent class has a non-final method callback in its constructor, and the subclass overrides that method, while accessing a member variable of its own in that method.

So what could go wrong here? If you run the above code, the output will be: I eat null, which means the foodName has not been initialized yet. Here, it is easy to understand why foodName is not initialized, because in the class initialization order, the member variables of the subclass are initialized after the constructor of the parent class. The Demo simple enough, so do you think there is no problem, in a formal development environment, the situation is very complicated, generally tens of thousands of code, in a way to access our member variables of the class, generally do not care about when this method is a callback, which leads to encounter unpredictable results in the development process.

Fortunately, this kind of problem is always present and can be encountered and solved in development. It will not be carried over to the line, but I will say this anyway: do not call methods in the constructor (code block) of the superclass that can be overridden by the subclass.

In addition, there is an implicit problem with this invocation:

public class Dog extends Animal { private String foodName1; private String foodName2 = null; Public Dog() {} @override protected void eat() {foodName1 = "meat "; FoodName2 = "bones "; } public void print() { System.out.println("foodName1 = " + foodName1 + " foodName2 = " + foodName2); }}Copy the code

I rewrote the Dog class code to define two member variables: foodName1 and foodName2, which are assigned in the eat method. Note that foodName2 has a default value of null, while foodName1 has no default value. So what happens if we create Dog’s object and call its print method? FoodName1 = meat foodName2 = null That is, a member variable with a default value remains the default value when it is actually used, even if it is reassigned inside the method. Why is that? To really understand how this works, you’d have to scratch the bytecode of the Dog class, but I’m too lazy to scratch the bytecode because if you define a member variable without setting a default value, when it’s initialized, it’s not initialized anymore. In other words, if a default value is set, it will be overwritten by the default value, even if it has been assigned previously.

Compared to the first pit, the second pit is more implicit and difficult to understand. The bottom line is the same: do not call methods in a superclass constructor (code block) that can be overridden by subclasses. Most importantly, the compiler doesn’t say anything during this process, which is one of the reasons why this kind of problem is so common.

2. Talk about Kotlin?

You may be used to this in Java, and the compiler does not report an error, indicating that this is the official default (I’m just saying that). However, when we apply Kotlin in the same way, the compiler reports a warning:

open class Test {
    private val string = getString()

    protected open fun getString(): String {
        return ""
    }
}
Copy the code

Overall, much friendlier than Java, at least with a warning.

From this, we can draw two conclusions:

  1. Do not ignore warnings from the compiler.
  2. Go use Kotlin!!