JAVA中子类和父类各种属性和代码块的加载顺序

探讨了在Java中,当子类继承父类时,父类与子类的静态和非静态代码块、属性及构造函数的加载顺序。通过实例代码演示了静态先于非静态、父类先于子类的加载原则。

这是面试或笔试中经常出现的一个问题:就是当子类继承父类之后,初始化子类,那么子类和父类各部分的加载顺序是怎样的?

答案是,顺序如下:

1. 父类的静态代码块 & 静态属性。(存在多个静态代码块 / 静态属性的话按照编写顺序由上至下依次执行)

2. 子类的静态代码块 & 静态属性。(存在多个静态代码块 / 静态属性的话按照编写顺序由上至下依次执行)

3. 父类的非静态代码块 & 非静态属性。(存在多个非静态代码块 / 非静态属性的话按照编写顺序由上至下依次执行)

4. 父类的构造函数。

5. 子类的非静态代码块 & 非静态属性。(存在多个非静态代码块 / 非静态属性的话按照编写顺序由上至下依次执行)

6. 子类的构造函数。

一共6项,看起来好像很多,但是其实都是遵循这么一条规则而已:

静态早于非静态的同时父类早于子类。

正是因为静态早于非静态。所以我们在写程序时,静态不能调用非静态,因为静态加载完成后,并不能保证非静态此时也加载完成了。但是非静态可以调用静态,因为你调用了非静态的时候非静态肯定先加载完成之后才能执行,而静态会早于非静态加载完成,此时静态也一定加载完成了。

而父类早于子类也很好理解,道理如同你不可能比你爹先出生,因为子类的很多行为和属性都要从父类身上获取下来才行。


那么我们通过代码来证实以上结论,为了验证,我们特地让静态及非静态的代码块和属性交叉排列

父类,SuperClass:

public class SuperClass {

    public String property1 = initProperty(1);
    public static String staticProperty1 = initStaticProperty(1);

    {
        System.out.println("这是父类的非静态代码块1");
    }

    static {
        System.out.println("这是父类的静态代码块1");
    }

    {
        System.out.println("这是父类的非静态代码块2");
    }

    static {
        System.out.println("这是父类的静态代码块2");
    }

    {
        System.out.println("这是父类的非静态代码块3");
    }

    static {
        System.out.println("这是父类的静态代码块3");
    }

    SuperClass() {
        System.out.println("这是父类的构造函数");
    }
    public String property2 = initProperty(2);
    public static String staticProperty2 = initStaticProperty(2);
    public String property3 = initProperty(3);
    public static String staticProperty3 = initStaticProperty(3);

    public String initProperty(int order) {
        System.out.println("这是父类的非静态属性" + order);
        return "这是父类的非静态属性" + order;
    }

    public static String initStaticProperty(int order) {
        System.out.println("这是父类的静态属性" + order);
        return "这是父类的静态属性" + order;
    }

}

子类,SubClass:

public class SubClass extends SuperClass{


    {
        System.out.println("这是子类的非静态代码块1");
    }

    static {
        System.out.println("这是子类的静态代码块1");
    }

    {
        System.out.println("这是子类的非静态代码块2");
    }

    static {
        System.out.println("这是子类的静态代码块2");
    }

    {
        System.out.println("这是子类的非静态代码块3");
    }

    static {
        System.out.println("这是子类的静态代码块3");
    }

    public String property1 = initProperty(1);
    public static String staticProperty1 = initStaticProperty(1);
    public String property2 = initProperty(2);
    public static String staticProperty2 = initStaticProperty(2);
    public String property3 = initProperty(3);
    public static String staticProperty3 = initStaticProperty(3);

    public String initProperty(int order) {
        System.out.println("这是子类的非静态属性" + order);
        return "这是子类的非静态属性" + order;
    }

    public static String initStaticProperty(int order) {
        System.out.println("这是子类的静态属性" + order);
        return "这是子类的静态属性" + order;
    }

    SubClass() {
        System.out.println("这是子类的构造函数");
    }

}

随后我们初始化子类:

public static void main(String[] args) {
    SubClass subClass = new SubClass();
}

可以看到控制台输出顺序如下:

通过观察下面几行打印以及代码可知:

(非)静态属性与(非)静态代码块的加载优先级一样高, 加载顺序取决于谁写在前面。

但是我们会通过下图发现一个问题:

哎,这里不是应该输出父类的非静态属性1,2,3吗,为什么是子类的?那是因为,父类和子类的非静态属性的初始化方法名都是 initProperty(),所以,子类是重写了父类的方法。这点一定要注意哦。

那么,接下来,我们修改子类的非静态属性的初始化方法名,以此来避免重写造成的多态现象,使我们更加直观的看到父类和子类的加载顺序:

子类代码修改后如下(就是将initProperty方法更名为subInitProperty):

package initorderstudy;

public class SubClass extends SuperClass{


    {
        System.out.println("这是子类的非静态代码块1");
    }

    static {
        System.out.println("这是子类的静态代码块1");
    }

    {
        System.out.println("这是子类的非静态代码块2");
    }

    static {
        System.out.println("这是子类的静态代码块2");
    }

    {
        System.out.println("这是子类的非静态代码块3");
    }

    static {
        System.out.println("这是子类的静态代码块3");
    }

    public String property1 = subInitProperty(1);
    public static String staticProperty1 = initStaticProperty(1);
    public String property2 = subInitProperty(2);
    public static String staticProperty2 = initStaticProperty(2);
    public String property3 = subInitProperty(3);
    public static String staticProperty3 = initStaticProperty(3);

    // 将重写方法更名,以便不重写父类重名方法
    public String subInitProperty(int order) {
        System.out.println("这是子类的非静态属性" + order);
        return "这是子类的非静态属性" + order;
    }

    public static String initStaticProperty(int order) {
        System.out.println("这是子类的静态属性" + order);
        return "这是子类的静态属性" + order;
    }

    SubClass() {
        System.out.println("这是子类的构造函数");
    }

}

执行main方法后结果如图:

由此,子类和父类个属性和代码块的加载顺序如上所述:静态早于非静态的同时父类早于子类。

Java 中,子继承父类属性(字段或变量)时,通常不会直接访问它们,而是通过`super`关键字或者`this`关键字来间接访问。原因有以下几点: 1. 封装性:Java 遵循封装原则,即数据成员应该是私有的(默认为private),外部无法直接访问。为了保护数据的安全性完整性,防止意外修改,我们需要提供公共的方法来操作这些属性。 2. 继承的目的:子继承父类通常是为了解决特定的问题并添加新的功能,而不是简单地复制父类的所有内容。通过访问修饰符如 `protected` 或 `public` 提供的访问控制,可以限制属性的可见范围,使得子可以根据需要选择性地使用。 3. 防止名称冲突:如果直接使用父类属性名,可能会导致命名冲突,特别是在大型项目中。通过`super`或`this`,可以明确指代是父类的还是本属性。 4. 代码清晰维护性:使用`super`或`this`可以使代码更易于理解维护,特别是当父类都有同名的属性时,可以避免混淆。 要使用父类属性,子通常会在构造函数中初始化、设置方法中调用父类的相应属性,或者是通过gettersetter方法进行访问。例如: ```java class Parent { private String name; } class Child extends Parent { public void initialize() { super.name = "Inherited Name"; // 使用super访问父类属性 } public void display() { System.out.println(this.name); // 使用this访问本类属性,如果未覆盖则显示父类的name } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值