Java的类/实例初始化过程

栏目: Java · 发布时间: 6年前

内容简介:昨天看到群里面有人分享了一道题目,我答错了,于是趁机了解了下Java的类/对象初始化过程:程序A主要考察的是程序B考察的则是

昨天看到群里面有人分享了一道题目,我答错了,于是趁机了解了下 Java 的类/对象初始化过程:

Java的类/实例初始化过程
程序的输出见文章最后

程序A主要考察的是 类实例初始化 。简单验证了下,类实例初始化过程如下:

  • 父类实例初始化
  • 构造块/变量初始化(按照文本顺序执行)
  • 构造函数

程序B考察的则是 类初始化 。类初始化的过程如下:

  • 父类初始化
  • static变量初始化/static块(按照文本顺序执行)

但是我们必须做到 面向接口编程,而不是面向实现编程(Program to an ‘interface’, not an ‘implementation’)

于是就得看看 Java Language Specification 了。其中 类初始化过程 如下:

  1. 每个类都有一个初始化锁LC,进程获取LC(如果没有获取到,就一直等待)
  2. 如果C正在被其他线程初始化,释放LC并等待C初始化完成
  3. 如果C正在被本线程初始化,即 递归初始化 ,释放LC
  4. 如果C已经被初始化了,释放LC
  5. 如果C处于erroneous状态,释放LC并抛出异常NoClassDefFoundError
  6. 否则,将C标记为正在被本线程初始化,释放LC;然后, 初始化那些final且为基础类型的类成员变量
  7. 初始化C的父类SC和各个接口SI_n (按照implements子句中的顺序来) ;如果SC或SIn初始化过程中抛出异常,则获取LC,将C标记为erroneous,并通知所有线程,然后释放LC,然后再抛出同样的异常。
  8. 从classloader处获取assertion是否被打开
  9. 接下来, 按照文本顺序执行类变量初始化和静态代码块,或接口的字段初始化,把它们当作是一个个单独的代码块。
  10. 如果执行正常,获取LC,标记C为已初始化,并通知所有线程,然后释放LC
  11. 否则,如果抛出了异常E。若E不是Error,则以E为参数创建新的异常ExceptionInInitializerError作为E。如果因为OutOfMemoryError导致无法创建ExceptionInInitializerError,则将OutOfMemoryError作为E。
  12. 获取LC,将C标记为erroneous,通知所有等待的线程,释放LC,并抛出异常E。

可以看到JLS确实规定了父类先初始化(7)、static块和类变量赋值按照文本顺序来(9)。

然后看看 类实例的初始化

  1. 开始调用构造函数(给参数赋值)
  2. 如果这个构造函数在开始就调用了其他构造函数,那么调用新的构造函数,并按照本规则处理。如果执行过程中抛出异常,则整个过程也抛出同样的异常。如果正常,继续。
  3. 如果构造函数没有在开始就调用其他构造函数。如果本类不是Object,那么构造函数会隐式或者显式的 调用父类的构造方法 。父类构造方法也依本规则处理。如果执行过程中抛出异常,则整个过程也抛出同样的异常。如果正常,继续。
  4. 执行实例初始化和实例变量初始化。顺序 按照文本顺序 来处理——从左到右、从上到下。如果执行过程中抛出异常,则整个过程也抛出同样的异常。如果正常,继续。
  5. 执行剩下的构造函数。如果执行过程中抛出异常,则整个过程也抛出同样的异常。

JLS特意提到,如果子类覆盖了父类的方法,则在构造函数中, 调用的方法也是子类的

接下来一个一个看代码:

// 程序A
// 父类
class Parent {
    int i = 1;
    Parent() {
        System.out.println(i);
        int x = getValue();
        System.out.println(x);
    }
    {i = 2;}
    protected int getValue() {return i;}
}
// 子类
class Son extends Parent {
    int j = 1;
    Son() {j = 2;}
    protected int getValue() {return j;}
}
class Test {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.getValue());
    }
}
  1. 21行,开始调用Son的构造函数
  2. 16行,Son的构造函数开始之前,初始化Parent
  3. 4行,开始执行i的变量初始化
  4. 10行,开始执行构造代码块
  5. 6-8行,开始执行Parent的构造函数。注意,调用的getValue方法是子类的,而此时Son.j还没有被构造函数、变量赋值语句初始化,此时Son.j是0。(输出2,0)
  6. 回到16行,继续执行Son的构造函数
  7. 22行,打印此时Son.j的值。(输出2)

所以,程序A的输出是:

接下来看程序B:

// 程序B
public class MagimaTest {
    public static void main(String[] args) {
        magimaFunction();
    }
    static MagimaTest st = new MagimaTest();
    static {
        System.out.println("1");
    }
    {
        System.out.println("2");
    }
    MagimaTest() {
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }
    public static void magimaFunction() {
        System.out.println("4");
    }
    int a = 110;
    static int b = 112;
}
  1. 3行,在执行main之前,需要初始化MagimaTest类。
  2. 6行,初始化st成员变量,开始初始化st实例。
  3. 13开始调用构造函数,但是开始前,需要处理成员变量初始化
  4. 10行,执行构造代码块(输出2)
  5. 20行,初始化a变量
  6. 14行,继续执行构造函数。此时a为110,b尚未初始化,所以是0(输出3,a=110,b=0)
  7. 7行,st成员变量初始化结束,执行下一个static代码块(输出1)
  8. 21行,继续初始化下一个static成员变量b
  9. 4行,调用magimaFunction(输出4)

所以输出是:

2
3
a=110,b=0
1
4

好了,看完了解析。那么我再出一个题目吧: 如果将程序B中的MagimaTest.b改为final的,输出会变化吗?


以上所述就是小编给大家介绍的《Java的类/实例初始化过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First jQuery

Head First jQuery

Ryan Benedetti , Ronan Cranley / O'Reilly Media / 2011-9 / USD 39.99

Want to add more interactivity and polish to your websites? Discover how jQuery can help you build complex scripting functionality in just a few lines of code. With Head First jQuery, you'll quickly g......一起来看看 《Head First jQuery》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具