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. 搭建完整邮件系统(postfix+dovecot+clamAV+Spamassassin+amavisd-new)

    ============================ 相关软件: 1. 发送邮件 --- postfix 2. 身份认证 --- sasl2 3. 接收邮件 --- dovecot 4. 防病毒邮 ...

  2. UITabBarController 标签栏控制器

    接上篇导航控制器UINavigationController 接下来是UITabBarController 标签栏控制器 先来看一下UITabBarController的结构 从图上可以看出控制器分为 ...

  3. openssl生成自签名证书

    1.生成x509格式的CA自签名证书 openssl req -new -x509 -keyout ca.key -out ca.crt 2.生成服务端的私钥(key文件)及申请证书文件csr文件 o ...

  4. CTime,Systemtime的比较还有转换成日期格式。

    vc为我们提供了两种日期型的变量. 一种是CTime.他的缺点就是年份只支持到2038年,以后的日期就不支持啦,如果你的项目有20-30年的寿命,你就选择使用SYSTEMTIME.这个时间函数来进行比 ...

  5. 4 Java学习之 反射Reflection

    1. 反射概念  反射机制就是:动态地获取类的一切信息,并利用这些信息做一些你想做的事情. java反射机制能够知道类名而不实例化对象的状态下,获得对象的属性或调用方法. JAVA反射机制是在运行状态 ...

  6. CloudFoundry Service 使用

    Mysql服务在V2版本号中github上有独立的releaseproject(cf-mysql-release),该release提供了一个Mysql-broker和一个Mysql-server和( ...

  7. android ViewPager具体解释

    Viewpager 在android界面布局中属于经常使用类型 ,它能够做导航,页面菜单,进入软件是的欢迎界面 等等.比方今最流行的几款手机软件  ,QQ,微信,微博 等 ,其主界面 都用到了View ...

  8. [Redux] Avoiding Object Mutations with Object.assign() and ...spread

    Learn how to use Object.assign() and the spread operator proposed for ES7 to avoid mutating objects. ...

  9. C#总结项目《影院售票系统》编写总结三

    昨天总结了动态绘制控件.票类型的切换以及数据在窗体中的展现.今天继续总结,自己喜欢的就去做吧,让别人说去吧,省的自己再留下什么后悔遗憾,噢耶,加油! 今天总结项目中最核心的部分--购票.座位颜色状态的 ...

  10. 几种常用的Java数据源解决方案

    http://blog.163.com/qqabc20082006@126/blog/static/22928525201041944847653/