Java的变量初始化顺序,对这里一直似懂非懂,面试的时候也经常被问到,但答的一直不好,现在整理记录一下,以后忘记了可以来看看。

  程序分为两个部分,第一个部分不考虑继承,第二个部分考虑继承;

(1)不考虑继承的情况

代码如下:

public class JavaTest {
    public JavaTest() {
        System.out.println("执行JavaTest构造方法1");
    }

    public JavaTest(String param) {
        System.out.println("执行JavaTest构造方法2");
    }

    static {
        System.out.println("JavaTest静态代码块1");
    }

    {
        System.out.println("JavaTest代码块1");
    }

    private static int max1 = getMax1();
    private int min1 = getMin1();

    public int getMin1() {
        System.out.println("初始化成员变量min1");
        return 0;
    }

    public static int getMax1() {
        System.out.println("初始化静态成员变量max1");
        return 0;
    }

    static {
        System.out.println("JavaTest静态代码块2");
    }

    {
        System.out.println("JavaTest代码块2");
    }

    private static int max2 = getMax2();
    private int min2 = getMin2();

    public int getMin2() {
        System.out.println("初始化成员变量min2");
        return 0;
    }

    public static int getMax2() {
        System.out.println("初始化静态成员变量max2");
        return 0;
    }

    public static void main(String[] args) {
        System.out.println("==============================");
        new JavaTest();
        System.out.println("==============================");
        new JavaTest("param");
    }
}

简单说一下:本实例中,共声明了两个静态代码块,两个初始化块,两个构造器,两个静态成员变量,两个非静态成员变量,并分散的声明开了,这个程序仅仅是为了做测试,来证明各种初始化方式的顺序,实际编写时切勿这样写,否则会使程序非常难于维护。

执行结果:

JavaTest静态代码块1
初始化静态成员变量max1
JavaTest静态代码块2
初始化静态成员变量max2
==============================
JavaTest代码块1
初始化成员变量min1
JavaTest代码块2
初始化成员变量min2
执行JavaTest构造方法1
==============================
JavaTest代码块1
初始化成员变量min1
JavaTest代码块2
初始化成员变量min2
执行JavaTest构造方法2

通过以上结果我们可以得出一些结论结论:

静态的初始化要先于实例的初始化,并且只执行一次。静态的初始化后,才开始为实例变量分配空间,执行初始化,最后执行构造器。

具体执行情况可以总结如下:

(1)在类加载时,为类中的静态成员变量分配内存空间,并初始化默认值;

(2)执行静态成员变量的初始化操作。而静态成员的初始化有两种方式:在声明时直接初始化与静态代码块。两种初始化方式会按照在类中出现的顺序(声明的顺序)来执行。

(3)上面两步只会在类加载时执行一次;

(4)如果创建了类的对象,便在堆中为类的实例分配内存空间,并初始化默认值;

(5)执行实例变量的初始化操作。同样有两种方式:声明时直接初始化与初始化块。这两种方式也是按照在类中出现的顺序来执行;

(6)执行类的构造器方法。

  注意:虽然类的成员变量可以在声明时为其变量直接初始化,但声明与初始化并不是同时执行的。对于静态成员变量,会先为所有类中声明的静态成员变量分配空间,每个变量存在默认值后,才会执行变量声明处的初始化,而这种初始化方式是静态初始化块按照在类中出现的先后顺序来执行的。对于实例变量,与静态变量是类似的。

  既然类的变量空间分配是先于初始化执行的,那么就存在这样一种情况,在变量创建之后,而在变量的初始化之前。如果在此期间使用此变量,就可能得不到我们想要的结果。

用一个小例子说明一下:

public class Test2 {
    {
        print();
    }

    private int max = 9;
    private String maxValue;

    public String getMax() {
        return maxValue;
    }

    public void print() {
        maxValue = "max: " + max;
    }

    public static void main(String[] args) {
        Test2 test2 = new Test2();
        System.out.println(test2.getMax());
    }
}

大家看一下打印结果就知道了:

max: 0

  正常情况下,应该能打印出9的,但却打印出了0。问题就在于对print的调用,因为该方法是在初始化块中调用的,而初始化块与实例变量在声明处的初始化是同等级的,会按照类中出现的顺序执行;

  程序中初始化块出现在max变量的前面,所以会在max变量初始化前执行,在调用print方法时,max变量虽然已经声明,但却尚未执行初始化,其默认值为0,所以打印的结果就是0;

(2) 继承情况下的初始化顺序

继承的情况我们也都熟悉,当初始化的时候,首先会初始化父类,这是一个递归的过程。

代码如下:

public class Test {
    public static void main(String[] args) {
        System.out.println("==========================");
        new ThisClass();
        System.out.println("==========================");
        new ThisClass();
    }
}

class ThisClass extends SuperClass {
    public ThisClass() {
        System.out.println("执行ThisClass构造方法");
    }

    static {
        System.out.println("ThisClass静态代码块");
    }

    {
        System.out.println("ThisClass代码块");
    }

    private static int max = getMax();
    private int min = getMin();

    public static int getMin() {
        System.out.println("ThisClass初始化成员变量min");
        return 0;
    }

    public static int getMax() {
        System.out.println("ThisClass初始化静态成员变量max");
        return 0;
    }

}

class SuperClass {
    public SuperClass() {
        System.out.println("执行SuperClass构造方法");
    }

    static {
        System.out.println("SuperClass静态代码块");
    }
    {
        System.out.println("SuperClass代码块");
    }

    private static int max = getMax();
    private int min = getMin();

    public static int getMin() {
        System.out.println("SuperClass初始化成员变量min");
        return 0;
    }

    public static int getMax() {
        System.out.println("SuperClass初始化静态成员变量max");
        return 0;
    }
}

执行结果如下:

==========================
SuperClass静态代码块
SuperClass初始化静态成员变量max
ThisClass静态代码块
ThisClass初始化静态成员变量max
SuperClass代码块
SuperClass初始化成员变量min
执行SuperClass构造方法
ThisClass代码块
ThisClass初始化成员变量min
执行ThisClass构造方法
==========================
SuperClass代码块
SuperClass初始化成员变量min
执行SuperClass构造方法
ThisClass代码块
ThisClass初始化成员变量min
执行ThisClass构造方法

根据结果我们可以分析出:

(1)继承情况下,JVM会首先加载父类,这是一个递归的过程,直到Object类为止。在类加载中,首先为类中的静态成员变量分配内存空间,并初始化默认值,然后按照在类中的顺序执行静态代码块与静态成员变量的初始化,这个过程是从父类到子类,并且只执行一次;

(2)如果创建了类的对象,在初始化子类之前,会首先对父类的实例变量初始化默认值,然后按照在类中的顺序进行初始化,然后调用父类的构造器。如果没有创建任何对象,本环节就不会执行。

注意:我们知道,静态成员变量的初始化会在类首次加载时执行,并且只会执行一次,那么类在什么情况下会被JVM载入执行呢?

 public class JavaTest {
     public static void main(String[] args) {
 //        Super superTest1;
 //        Super superTest2 = new Super();
 //        int height = Super.height;
 //        Super.getHeight();
 /*
         try {
             Class.forName("Super");
         } catch (Exception e) {
             System.out.println("异常");
         }
 */
     }
 }

 class Super {
     static {
         System.out.println("静态代码块");
     }
     public static int height = 30;

     public static int getHeight() {
         return height;
     }
 }

完全注释掉后,运行程序,很显然,什么都没有输出;

然后我们将第3行注释取消,运行,还是什么都没有输出;

然后我们随意取消第4,5,6行任意一行,就可以打印出 "静态代码块";

由此说明,如果只是引用了类的引用,JVM是不会载入类的。JVM只是在需要某个类时才载入该类,这种需要可能是使用了该类的静态成员变量,或是调用了该类的静态方法,或是生成了该类的实例,但这并非是加载一个类的全部可能,如当加载子类时,那么父类自然也就被加载了。

同理,如果我们保留上面的注释,而取消第8行到13行的注释,那程序还是会打印出 "静态代码块"。因为JVM在加载类的时候会生成一个Class类的对象,而Class类的forName方法就是取得该类的Class对象,所以JVM会载入该类。

总结:Java变量的加载顺序,是从父类到子类,静态到非静态的过程。

等有时间,试着从JVM的角度和字节码的执行过程来研究一下Java变量的初始化顺序这个问题。

参考自:《细说Java》

【细说Java】Java变量初始化顺序的更多相关文章

  1. Java静态方法,静态变量,初始化顺序

    1. 静态方法: 成员变量分为实例变量和静态变量.其中实例变量属于某一个具体的实例,必须在类实例化后才真正存在,不同的对象拥有不同的实例变量.而静态变量被该类所有的对象公有(相当于全局变量),不需要实 ...

  2. 图示Java类的初始化顺序

    Java类的初始化顺序   在开发中,知道Java类的初始化顺序才能让我们更加清楚地掌握程序的执行流程.先把结论贴出来,Java里,从图里的1~6,分别按顺序执行.   以下为代码验证阶段,一共三个类 ...

  3. Java实例变量初始化

    由一道面试题所想到的--Java实例变量初始化 时间:2015-10-07 16:08:38      阅读:23      评论:0      收藏:0      [点我收藏+] 标签:java   ...

  4. java创建对象 的初始化顺序

    java创建对象 的初始化顺序 1.初始化块 初始化块通常写在类的构造方法之前,由花括号括起来,通常包含对成员属性进行初始化的语句: 初始化块分为instance初始化块和static初始化块,初始化 ...

  5. Java学习笔记二十三:Java的继承初始化顺序

    Java的继承初始化顺序 当使用继承这个特性时,程序是如何执行的: 继承的初始化顺序 1.初始化父类再初始子类 2.先执行初始化对象中属性,再执行构造方法中的初始化 当使用继承这个特性时,程序是如何执 ...

  6. 调整static变量初始化顺序的一个办法

    // wrap the LaunchDir variable in a function to work around static/global initialization order stati ...

  7. Java类的初始化顺序 (静态变量、静态初始化块、变量、初始...

    很有意思的一篇文章 1.没有继承 静态变量->静态初始化块->变量->变量初始化块->构造方法 2.有继承的情况 父类静态变量->父类静态初始化块->子类静态变量- ...

  8. Java中类成员变量初始化顺序

    一. 定义处默认初始化vs构造函数中初始化 java中类成员变量支持在声明处初始化,也可以在构造函数中初始化,那么这两者有什么区别呢?看下面例子 public class FieldsInit { p ...

  9. java静态类、静态方法、静态代码块,静态变量及实例方法,实例变量初始化顺序及内存管理,机制

    1.当一个类被第一次使用时,它需要被类加载器加载,而加载过程涉及以下两点: (1)在加载一个类时,如果它的父类还未被加载,那么其父类必须先被加载: (2)当类加载到内存之后,按照在代码中的出现顺序执行 ...

随机推荐

  1. UVa 10617 Again Palindromes / 记忆化搜索

    删除若干个字母后 剩下的是回文串 求有多少个 记忆化搜索 dp[i][j]表示i j 之间有多少个 其实递推也可以的 long long #include <stdio.h> #inclu ...

  2. Atitit.Gui控件and面板----数据库区-mssql 2008 权限 配置 报表查看成员

    Atitit.Gui控件and面板----数据库区-mssql 2008 权限 配置 报表查看成员 1. 配置server连接权限 1 2. 配置数据库权限 1 3. 设置表格/视图安全性 2 1.  ...

  3. java web mvc思想介绍

    1.首先简介一下什么是MVC思想. 在百度百科里面对MVC的说明,MVC全名是Model View Controller.是模型(model)-视图(view)-控制器(controller)的缩写. ...

  4. VC++中操作XMLWin32实例

    摘要:VC++中操作XML XML在Win32程序方面应该没有在Web方面应用得多,很多Win32程序也只是用XML来存存配置信息而已,而且没有足够的好处的话还不如用ini.VC++里操作XML有两个 ...

  5. [转] PostgreSQL的时间/日期函数使用

    PS:http://blog.csdn.net/love_rongrong/article/details/6712883 字符串模糊比较 日期类型的模糊查询是不能直接进行的,要先转换成字符串然后再查 ...

  6. 超好用文件对比工具 – Beyond Compare

    超好用文件对比工具 – Beyond Compare,开发中文件.目录对比神器,有了它,再也不用为找不到修改的内容而发愁了. 具备的丰富实用功能: 并列比较文件夹.FTP 网站或 Zip 文件: 为以 ...

  7. Topcoder SRM 639 (Div.2)

    A.ElectronicPetEasy [题意]一个数st1开始,每次加p1,一共加t1次,另外一个数st2开始,每次加p2,一共加t2次,输入的数均小于1000,问这两个数有没有可能相等,有可能输出 ...

  8. css07家用电器分类

    1.创建一个html页面 <!DOCTYPE html> <html> <head lang="en"> <meta charset=&q ...

  9. Android 聊天气泡

    网上搜到的只有一篇是自定义的TextView,其使用比较麻烦,所以采用大众化的方法--使用9.png来实现. 这里主要介绍sdk tool的draw9patch.bat的使用. 这个bat执行文件打开 ...

  10. spring 配置触发器 (类似于定时任务)

    为什么会看这个? 发现项目中有的service中的方法没有地方调用.经查,发现在web.xml中加载的spring的配置文件中配置了这个方法, 经查这种方式是触发器,会定时执行,只需要配置一下.可以设 ...