Java notes 4: reuse classes

Posted by burnside on Sat, 15 Jan 2022 16:37:08 +0100

Java notes 4: reuse classes

Source: Java Switch statement (detailed usage) - java Tutorial - PHP Chinese network

Class is the code organization unit in OOP programming. Whether it is OOP class or process oriented function, its purpose is to realize code reuse.

There are two ways to realize code reuse through classes: Inheritance and composition.

combination

Composition simply means that an instance of a class exists in another class as an attribute.

package ch4.compose;

class Compose {
}

public class MyClass {
    private Compose compose;
    ...
}

Of course, such a composition is only a reference, and it must be initialized to make it useful. As with properties of ordinary types, there are many ways to initialize combined objects.

  1. Initialize while declaring:
package ch4.compose2;

class Compose {
}

public class MyClass {
    private Compose compose = new Compose();
	...
}

The advantage of this is that the initialization behavior is before all constructor calls. In other words, even if there are multiple overloaded constructors, the compose attribute can be effectively initialized.

  1. Initialize in constructor:
package ch4.compose3;

class Compose {
}

public class MyClass {
    private Compose compose;
    
    public MyClass() {
        compose = new Compose();
    }
	...
}

This is also a common way.

  1. Incoming from outside:
package ch4.compose4;

class Compose {
}

public class MyClass {
    private Compose compose;
    
    public MyClass(Compose compose) {
        this.compose = compose;
    }
	...
}

A reference to compose can be passed in through the constructor or other methods, which is very common in the related implementation of design patterns.

  1. Lazy initialization:
package ch4.compose5;

class Compose {
}

public class MyClass {
    private Compose compose;

    public MyClass() {
    }

    public Compose getCompose() {
        if (compose == null) {
            compose = new Compose();
        }
        return compose;
    }
	...
}

The advantage of this initialization method is that it can delay the initialization of objects that consume performance until they are really needed. This is also a common method in singleton mode. However, it should be noted that there is no problem in doing so in a single process. If you want to achieve this effect when writing concurrent programs, you need to make greater efforts (such as using resource locks).

inherit

The inheritance syntax of Java is similar to that of other mainstream languages. However, because Java adopts single root inheritance, all classes that do not specify inheritance are actually implicitly inherited from Object, that is, all classes are subclasses of Object class.

package ch4.inheritence;

class Parent{

}

public class Child extends Parent {
    public static void main(String[] args) {
        Child c = new Child();
        if (c instanceof Object){
            System.out.println("c is Object.");
        }
        if (c instanceof Parent){
            System.out.println("c is Parent.");
        }
        if (c instanceof Child){
            System.out.println("c is Child.");
        }
        // c is Object.
        // c is Parent.
        // c is Child.
    }
}

You can use the instanceof operator to determine whether an object is an instance of a class, which is similar in many languages.

As can be seen from the above example, c this instance can have multiple "identities" through inheritance, so it is usually said that inheritance is an "is" relationship.

super

If there is an inheritance relationship and you need to use the reference of the parent class in the current class, you can use the super keyword, which is usually used to call the parent class in the subclass:

package ch4.inheritence2;

class Parent{
    public void display(){
        System.out.println("Parent is displayed.");
    }

    public void desdroy(){
        System.out.println("parent is desdroyed.");
    }
}

public class Child extends Parent {
    @Override
    public void display() {
        super.display();
        System.out.println("Child is desplayed.");
    }

    @Override
    public void desdroy() {
        System.out.println("Child is desdroyed.");
        super.desdroy();
    }
    public static void main(String[] args) {
        Child c = new Child();
        c.display();
        c.desdroy();
        // Parent is displayed.
        // Child is desplayed.
        // Child is desdroyed.
        // parent is desdroyed.
    }
}

When overriding (overriding) the parent class method, you really need to consider whether to use super to call the method with the same name as the parent class, but it should be noted that the call order is very important. For example, as in the above example, display represents a creation behavior, so the display action of the child class may not be performed until the display action of the parent class is completed, so call super display(). The destroy method represents a destroy action, which is the opposite of display. Therefore, the destroy action of the child class needs to be performed before the destroy action of the parent class, so as to ensure that some association relationships are correctly destroyed before the destruction of the parent class.

Similar to this, super can also be used to call the constructor of the parent class:

package ch4.inheritence3;

import util.Fmt;

class Parent {
    private int number;

    public Parent(int number) {
        this.number = number;
        Fmt.printf("Parent(%d) is called.\n", number);
    }

}

public class Child extends Parent {
    

    public Child(int number) {
        super(number);
        Fmt.printf("Child(%d) is called.\n", number);
    }

    public static void main(String[] args) {
        Child c = new Child(1);
        // Parent(1) is called.
        // Child(1) is called.
    }
}

In this case, super also has a limitation similar to this, that is, it must be used in the first line of the subclass constructor, and can only be used once in a single constructor.

Initialize base class

For classes with inheritance relationship, the construction process of their instances is from inside to outside, which is a bit like "onion structure".

Here, we use Shape, Rectangle and Square, which are often used to learn OOP.

Shape is the base class of all shapes and represents an abstract shape. Rectangle is a rectangle, which means that there are two sides that may or may not be equal, and a point on two-dimensional coordinates (which can be a center point or a corner). Square is a square and a "special Rectangle", and its four sides are equal.

package ch4.inheritence4;

class Pointer {
    private int x;
    private int y;

    public Pointer(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

class Shape {

    public Shape() {
        System.out.println("Shape is build");
    }
}

class Rectangle extends Shape {
    private int xEdge;
    private int yEdge;
    private Pointer center;

    public Rectangle(int xEdge, int yEdge, Pointer center) {
        super();
        this.xEdge = xEdge;
        this.yEdge = yEdge;
        this.center = center;
        System.out.println("Rectangle is build");
    }
}

public class Square extends Rectangle {
    private int edge;
    private Pointer center;

    public Square(int edge, Pointer center) {
        super(edge, edge, center);
        this.edge = edge;
        this.center = center;
        System.out.println("Square is build");
    }

    public static void main(String[] args) {
        Pointer center = new Pointer(0, 0);
        int edge = 10;
        Square s = new Square(edge, center);
        // Shape is build
        // Rectangle is build
        // Square is build
    }
}

You can see that to create a subclass instance, you need to "from inside out". First create an instance of the base class, then create an instance of the parent class, and finally create an instance of the subclass.

It should be noted that in fact, the Rectangle constructor does not need to explicitly call super(), because its parent constructor is a default constructor without parameters. In this case, even if super() is not explicitly called, the Java compiler will call it automatically. However, it is still a good habit to explicitly call the parent class constructor, which can reduce some ambiguity when reading the code.

agent

In fact, agent is a concept, a design pattern. For a detailed description of it, see Design pattern with Python 11: Proxy Pattern - konjac black tea's blog (icexmoon.xyz).

If the proxy is not defined so rigorously, in fact, when one object holds another object, to some extent, the current object can act as a proxy for the object it holds. In other words, we can implement a simple agency relationship through combination:

package ch4.proxy;

class MyClass{
    public void print(String str){
        System.out.println("print() is called.");
        System.out.println(str);
    }
}

class MyClassProxy{
    MyClass mc = new MyClass();
    public void print(String str){
        mc.print(str);
    }
}

public class Main {
    public static void main(String[] args) {
        MyClassProxy mcp = new MyClassProxy();
        mcp.print("hello");
        // print() is called.
        // hello
    }
}

In fact, the core implementation of the complete proxy is also through composition, but it may need to use interfaces or abstract base classes.

Composition and inheritance

Ensure proper cleaning

Since the creation of an instance is "from inside to outside", it is dependent and natural. When destroying an instance, it should also be "from outside to inside". Only in this way can we ensure that there will be no reference relationship between external instances and internal instances when destroying an internal instance.

The "graphic family" is still used here:

package ch4.inheritence5;

import util.Fmt;

class Pointer {
    private int x;
    private int y;

    public Pointer(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return Fmt.sprintf("Pointer(%d,%d)", this.x, this.y);
    }

    public void destory() {
        String thisStr = this.toString();
        this.x = 0;
        this.y = 0;
        Fmt.printf("%s is destroy.\n", thisStr);
    }
}

class Shape {

    public Shape() {
        System.out.println("Shape is build");
    }

    public void desdroy() {
        System.out.println("Shape is destroy.");
    }
}

class Rectangle extends Shape {
    private int xEdge;
    private int yEdge;
    private Pointer center;

    public Rectangle(int xEdge, int yEdge, Pointer center) {
        super();
        this.xEdge = xEdge;
        this.yEdge = yEdge;
        this.center = center;
        Fmt.printf("%s is build.\n", this.getName());
    }

    private String getName() {
        return Fmt.sprintf("Rectangle(xEdge:%s,yEdge:%s,center:%s)", this.xEdge, this.yEdge, this.center);
    }

    @Override
    public void desdroy() {
        String thisStr = this.getName();
        this.xEdge = 0;
        this.yEdge = 0;
        this.center.destory();
        Fmt.printf("%s is destory.\n", thisStr);
        super.desdroy();
    }
}

public class Square extends Rectangle {
    private int edge;
    private Pointer center;

    public Square(int edge, Pointer center) {
        super(edge, edge, center);
        this.edge = edge;
        this.center = center;
        Fmt.printf("%s is build.\n", this.getName());
    }

    @Override
    public void desdroy() {
        String thisStr = this.getName();
        this.edge = 0;
        this.center.destory();
        Fmt.printf("%s is destory.\n", thisStr);
        super.desdroy();
    }

    private String getName() {
        return Fmt.sprintf("Squre(edge:%d,center:%s)", this.edge, this.center);
    }

    public static void main(String[] args) {
        Pointer center = new Pointer(5, 5);
        int edge = 10;
        Square s = new Square(edge, center);
        s.desdroy();
        // Shape is build
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.
        // Squre(edge:10,center:Pointer(5,5)) is build.
        // Pointer(5,5) is destroy.
        // Squre(edge:10,center:Pointer(5,5)) is destory.
        // Pointer(0,0) is destroy.
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(0,0)) is destory.
        // Shape is destroy.
    }
}

This example shows how to "log out" an instance through the destroy method "from the outside in".

The main thing to note is that in the Square and Rectangle classes, I use a private getName() method as a method to obtain the string form of the current instance, rather than a general rewriting of the toString method. The reason is that the latter is public, which means that it will be called "polymorphically" after writing, that is, Rectangle (...) will not appear in the output result Is build, only Square(...) is build.

In addition, in the output result, Pointer(0,0) is destroy This is also strange because although the center properties of Sqaure and Rectangle are private and have no inheritance relationship, they are actually references to the same Pointer object. Therefore, when the destroy method is actually called, the center property of Square is destroyed first, and the Pointer object becomes a Pointer(0,0), This is obviously a bug in the Rectangle center.

For objects that may be referenced by multiple objects at the same time, you should check whether all references have become invalid when destroying them. You can really destroy them only when all references have become invalid:

package ch4.inheritence6;

import util.Fmt;

class Pointer {
    private int x;
    private int y;
    private int referenceCounter = 0;

    public Pointer(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void addReference() {
        this.referenceCounter++;
    }

    @Override
    public String toString() {
        return Fmt.sprintf("Pointer(%d,%d)", this.x, this.y);
    }

    public void destory() {
        referenceCounter--;
        if (referenceCounter == 0) {
            String thisStr = this.toString();
            this.x = 0;
            this.y = 0;
            Fmt.printf("%s is destroy.\n", thisStr);
        }
    }
}
...
class Rectangle extends Shape {
    private int xEdge;
    private int yEdge;
    private Pointer center;

    public Rectangle(int xEdge, int yEdge, Pointer center) {
        super();
        this.xEdge = xEdge;
        this.yEdge = yEdge;
        this.center = center;
        center.addReference();
        Fmt.printf("%s is build.\n", this.getName());
    }
	...
}

public class Square extends Rectangle {
    private int edge;
    private Pointer center;

    public Square(int edge, Pointer center) {
        super(edge, edge, center);
        this.edge = edge;
        this.center = center;
        center.addReference();
        Fmt.printf("%s is build.\n", this.getName());
    }
    ...
    public static void main(String[] args) {
        Pointer center = new Pointer(5, 5);
        int edge = 10;
        Square s = new Square(edge, center);
        s.desdroy();
        // Shape is build
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.
        // Squre(edge:10,center:Pointer(5,5)) is build.
        // Squre(edge:10,center:Pointer(5,5)) is destory.
        // Pointer(5,5) is destroy.
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is destory.
        // Shape is destroy.
    }
}

Of course, this is just a description of how to correctly clean up objects with "multiple references". In actual coding, simpler methods are often used:

package ch4.inheritence7;

import util.Fmt;

...
class Rectangle extends Shape {
	...
    @Override
    public void desdroy() {
        String thisStr = this.getName();
        this.xEdge = 0;
        this.yEdge = 0;
        this.center = new Pointer(0, 0);
        Fmt.printf("%s is destory.\n", thisStr);
        super.desdroy();
    }
}

public class Square extends Rectangle {
	...
    @Override
    public void desdroy() {
        String thisStr = this.getName();
        this.edge = 0;
        this.center = new Pointer(0, 0);
        Fmt.printf("%s is destory.\n", thisStr);
        super.desdroy();
    }

    private String getName() {
        return Fmt.sprintf("Squre(edge:%d,center:%s)", this.edge, this.center);
    }

    public static void main(String[] args) {
        Pointer center = new Pointer(5, 5);
        int edge = 10;
        Square s = new Square(edge, center);
        s.desdroy();
        // Shape is build
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.
        // Squre(edge:10,center:Pointer(5,5)) is build.
        // Squre(edge:10,center:Pointer(5,5)) is destory.
        // Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is destory.
        // Shape is destroy.
    }
}

As long as the center reference points to new Pointer(0,0) or null, it is equivalent to releasing the reference to the original object, and the original Pointer object will be collected and cleaned up by the garbage collector after all references are invalid. If the destroy of Pointer must be executed during cleanup, you can Java programming note 3: access control - konjac black tea's blog (icexmoon.xyz) The Cleaner class mentioned in.

Name mask

It should be noted that when overriding a parent method, the function signature must be completely consistent, otherwise it will not be considered as overriding:

package ch4.override1;

import util.Fmt;

class Parent{
    public void func(int i){
        Fmt.printf("func(int %d) is called.\n", i);
    }

    public void func(char c){
        Fmt.printf("func(char %s) is called.\n", c);
    }
}

class Child extends Parent{
    public void func(String str) {
        Fmt.printf("func(String %s) is called.\n", str);
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.func("123");
        c.func(123);
        c.func('a');
        // func(String 123) is called.
        // func(int 123) is called.
        // func(char a) is called.
    }
}

In the above example, the func method in Child is not actually an override of the parent func method, but a new overload is added.

This sometimes leads to some potential bug s. For example, you intend to rewrite a method, but in fact, it is overloaded because of different parameter types.

For this, Java provides an @ override tag:

package ch4.override2;

import util.Fmt;

...

class Child extends Parent{
    @Override
    public void func(String str) {
        Fmt.printf("func(String %s) is called.\n", str);
    }
}
...

The method marked by the @ override tag must be an override of the parent method, otherwise it will not pass the compilation check. In this case, the compiler will report an error.

It should be noted that this tag is not necessary and does not affect the normal execution of the program, but it can avoid possible bug s when rewriting methods.

Composite or inheritance

The choice between composition and inheritance, that is, when to use composition and when to use inheritance, is a very in-depth OOP problem, which often requires developers to have some experience.

The development model has a rule for this: use more composition and less inheritance. The reason is that although they all have good scalability, in contrast, composition is more flexible, and inheritance is often affected by the dilemma that "all subclasses have to make corresponding modifications after the base class modifies the interface". Moreover, this risk will become more and more troublesome with the increase of inheritance levels. Therefore, for inheritance, the suggestion of development mode is not to use more than three levels of inheritance.

For more information on design patterns, I recommend reading the book Head First design patterns or my blog series Design pattern with Python - konjac black tea's blog (icexmoon.xyz).

protected

In order to modify the design of the base class, the properties of the base class should be set to private as much as possible. If the subclass needs to use the private attribute of the parent class, you can add an accessor and modifier with protected permission:

package ch4.protected1;

import util.Fmt;

class Parent{
    private int num;

    protected int getNum() {
        return num;
    }

    protected void setNum(int num) {
        this.num = num;
    }
}

class Child extends Parent{

    public Child(int num) {
        super();
        super.setNum(num);
    }

    public void display(){
        Fmt.printf("num:%d\n", super.getNum());
    }

    public void clean(){
        super.setNum(0);
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child(10);
        c.display();
        c.clean();
        c.display();
        // num:10
        // num:0
    }
}

Of course, there is no need for inheritance in this example. It is only an illustration of the use of protected.

Upward transformation

Upward transformation describes that in some cases, the object of an exported class can be treated as the object of the base class.

The reason why it is called "upward transformation" is that in the UML diagram, the base class is usually placed above and the exported class is located below. For example, the inheritance relationship of the shape related class family mentioned earlier can be represented by the following UML diagram:

Because the inheritance relationship is is, the upward transformation is safe. A base class object can be used as a parent class object when appropriate:

package ch4.cast;

public class Main {
    private static void testShape(Shape shape) {
        shape.display();
        shape.desdroy();
    }

    public static void main(String[] args) {
        Pointer center = new Pointer(5, 5);
        int edge = 10;
        Square s = new Square(edge, center);
        Shape[] shapes = new Shape[3];
        shapes[0] = s;
        shapes[1] = center;
        shapes[2] = new Line(new Pointer(5, 5), new Pointer(1, 1));
        for (Shape shape : shapes) {
            testShape(shape);
        }
    }
}

Here, I also regard Pointer and the newly added Line class as subclasses of Shape.

For the sake of space, only part of the code is shown here. For the complete code, see java-notebook/xyz/icexmoon/java_notes/ch4/cast at main ยท icexmoon/java-notebook (github.com).

In addition to this upward transformation when transferring parameters in function calls, it can also be transformed "manually" at any position:

package ch4.cast;

public class Main2 {
    private static void testShape(Shape shape) {
        shape.display();
        shape.desdroy();
    }

    public static void main(String[] args) {
        Pointer center = new Pointer(5, 5);
        int edge = 10;
        Shape s = new Square(edge, center);
        testShape(s);
    }
}

Here, the Shape handle is directly used to accept an object of subtype Square, which is also possible.

final

The final keyword is similar to the const keyword in C/C + +, but slightly different.

final data

If the data declared as final is basic data, its content cannot be changed. If it is an object, it cannot point to a new reference:

package ch4.final1;

class MyInteger{
    private int num;

    public MyInteger(int num) {
        this.setNum(num);
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

public class Main {
    public static void main(String[] args) {
        final int num = 123;
        // num = 222;
        final String msg = "hello";
        // msg = "world";
        final MyInteger mi = new MyInteger(10);
        mi.setNum(20);
        System.out.println(mi.getNum());
        // 20
    }
}

It should be noted that although final String and final int look very similar, in fact, the former is an object, but String objects are special and cannot be modified after creation.

Whether the content of the object declared as final can be modified depends on the specific design of the object (such as String). Final only restricts the reference and cannot be re pointed.

The more common use of final is to create "class constants" in combination with static:

package ch4.final2;

class Colors {
    public static final int RED = 1;
    public static final int BLUE = 2;
    public static final int YELLOW = 3;
    public static final int BLACK = 4;
    public static final int GREEN = 5;

    public static boolean isLight(int code) {
        switch (code) {
            case BLUE:
            case YELLOW:
            case RED:
            case GREEN:
                return true;
            default:
                return false;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Colors.isLight(Colors.GREEN));
    }
}

This was useful before Java se 5, but it was not so useful after enum.

Some languages require that class attributes declared by const or final must be initialized at the same time, but Java does not have this restriction. Final attributes declared but not initialized can be called "blank final". Such "blank final" can be initialized at any time later, but it must be initialized before use, Otherwise, a compilation error will be generated.

package ch4.final3;

class MyClass {
    private final int num;

    public MyClass(int num) {
        this.num = num;
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass mc = new MyClass(10);
    }
}

Similarly, you can also specify the parameter of the method as final, so it cannot be changed in the method:

package ch4.final4;

class MyClass {
	...
    public void passNum(final int num){
        // num = 123;
    }
}
...

The annotated part of the instance cannot be compiled.

final method

Methods declared final cannot be overridden:

package ch4.final5;

class Parent {
    public final void test() {
        System.out.println("test() is called.");
    }
}

class Child extends Parent {
    // public void test() {

    // }
}

public class Main {
    public static void main(String[] args) {
    }
}

Both final and private methods cannot be overridden by the parent class, but they have some differences. From the definition point of view, the method defined by private is completely invisible to the subclass. It seems that it does not exist to the subclass, so the subclass can define a method with the same signature, which is allowed:

package ch4.final6;

class Parent {
    private void test() {
        System.out.println("Parent.test() is called.");
    }
}

class Child extends Parent {
    private void test(){
        System.out.println("Child.test() is called.");
    }

    public void callTest(){
        this.test();
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.callTest();
        // Child.test() is called.
    }
}

Final is different. It is only used to declare that the method itself cannot be overridden by subclasses. Of course, final is meaningful only when it is used in combination with public or protected. If a method is declared as private final, it has no meaning.

The main purpose of final is to set the methods of some "skeleton" codes to be non rewritable, such as:

package ch4.final7;

class Controller {
    public Controller() {

    }

    public final void request() {
        this.preRequest();
        this.doRequest();
        this.afterRequest();
    }

    protected void preRequest() {

    }

    protected void afterRequest() {

    }

    protected void doRequest() {

    }
}

class IndexController extends Controller {
    @Override
    protected void preRequest() {
        super.preRequest();
        //Check login status
    }

    @Override
    protected void doRequest() {
        super.doRequest();
        //Load home page html
    }
}

public class Main {
    public static void main(String[] args) {
        IndexController ic = new IndexController();
        ic.request();
    }
}

Here is a simple Control layer code in the MVC framework commonly used in Web development. The request of the Controller base class is a method that contains the skeleton code. In this method, three methods that can be inherited by subclasses are called preRequest, doRequest and afterRequest. In this way, the subclass can inherit the corresponding methods as needed to perform some necessary operations before and after executing the HTTP request, such as reading and writing cookies or performing login check.

Of course, the example here is quite simple. Usually, these methods for processing HTTP requests also pass some parameters containing HTTP request information.

In fact, the above practice is called "template method pattern" in the design pattern. For more description of this design pattern, see Design pattern with Python 8: Template Method Pattern - konjac black tea's blog (icexmoon.xyz).

final class

final has only one purpose for a class -- to prevent the class from being inherited.

This may not be common, because inheritance itself is to make programming more "flexible". In other words, making a class final is undoubtedly a waste of martial arts. So if you do intend to do so, think carefully first.

Object initialization order

The initialization order of objects was briefly mentioned earlier:

  1. Load the class and initialize the static class variable.
  2. Perform initialization of common properties.
  3. Constructor execution.

You can verify with the following example:

package ch4.init;

import java.util.Random;

class MyClass{
    private int num;
    private static int snum;
    private static Random random;
    static{
        random = new Random();
        snum = random.nextInt(100);
        System.out.println("static members inilize is executed.");
    }
    {
        num = random.nextInt(100);
        System.out.println("normal members inilize is executed.");
    }
    public MyClass(){
        System.out.println("constructor is called.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass mc = new MyClass();
        // static members inilize is executed.
        // normal members inilize is executed.
        // constructor is called.
    }
}

If inheritance is involved, the initialization process will be more complex:

package ch4.init2;

import java.util.Random;

...
class Child extends MyClass {
    private int num;
    private static int snum;
    private static Random random;
    static {
        random = new Random();
        snum = random.nextInt(100);
        System.out.println("Child's static members inilize is executed.");
    }
    {
        num = random.nextInt(100);
        System.out.println("Child's normal members inilize is executed.");
    }

    public Child() {
        super();
        System.out.println("Child's constructor is called.");
    }
}

public class Main {
    public static void main(String[] args) {
        Child mc = new Child();
        // static members inilize is executed.
        // Child's static members inilize is executed.
        // normal members inilize is executed.
        // constructor is called.
        // Child's normal members inilize is executed.
        // Child's constructor is called.
    }
}

You can see that class definitions are also loaded first. The difference is that if there is an inheritance relationship, all parent class definitions need to be loaded. After loading the class definition, you need to initialize the class variables. The initialization process is "from inside to outside", because the class variables of the child class can be defined based on the class variables of the parent class. Therefore, the class variable of the parent class must be initialized first. Then initialize the normal properties of the parent class and call the constructor of the parent class. In this way, the instance of the parent class is initialized, and then the ordinary properties of the child class are initialized and the constructor of the child class is called.

Thank you for reading.

Topics: Java Back-end