【细说Java】Java变量初始化顺序
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变量初始化顺序的更多相关文章
- Java静态方法,静态变量,初始化顺序
1. 静态方法: 成员变量分为实例变量和静态变量.其中实例变量属于某一个具体的实例,必须在类实例化后才真正存在,不同的对象拥有不同的实例变量.而静态变量被该类所有的对象公有(相当于全局变量),不需要实 ...
- 图示Java类的初始化顺序
Java类的初始化顺序 在开发中,知道Java类的初始化顺序才能让我们更加清楚地掌握程序的执行流程.先把结论贴出来,Java里,从图里的1~6,分别按顺序执行. 以下为代码验证阶段,一共三个类 ...
- Java实例变量初始化
由一道面试题所想到的--Java实例变量初始化 时间:2015-10-07 16:08:38 阅读:23 评论:0 收藏:0 [点我收藏+] 标签:java ...
- java创建对象 的初始化顺序
java创建对象 的初始化顺序 1.初始化块 初始化块通常写在类的构造方法之前,由花括号括起来,通常包含对成员属性进行初始化的语句: 初始化块分为instance初始化块和static初始化块,初始化 ...
- Java学习笔记二十三:Java的继承初始化顺序
Java的继承初始化顺序 当使用继承这个特性时,程序是如何执行的: 继承的初始化顺序 1.初始化父类再初始子类 2.先执行初始化对象中属性,再执行构造方法中的初始化 当使用继承这个特性时,程序是如何执 ...
- 调整static变量初始化顺序的一个办法
// wrap the LaunchDir variable in a function to work around static/global initialization order stati ...
- Java类的初始化顺序 (静态变量、静态初始化块、变量、初始...
很有意思的一篇文章 1.没有继承 静态变量->静态初始化块->变量->变量初始化块->构造方法 2.有继承的情况 父类静态变量->父类静态初始化块->子类静态变量- ...
- Java中类成员变量初始化顺序
一. 定义处默认初始化vs构造函数中初始化 java中类成员变量支持在声明处初始化,也可以在构造函数中初始化,那么这两者有什么区别呢?看下面例子 public class FieldsInit { p ...
- java静态类、静态方法、静态代码块,静态变量及实例方法,实例变量初始化顺序及内存管理,机制
1.当一个类被第一次使用时,它需要被类加载器加载,而加载过程涉及以下两点: (1)在加载一个类时,如果它的父类还未被加载,那么其父类必须先被加载: (2)当类加载到内存之后,按照在代码中的出现顺序执行 ...
随机推荐
- [Redux] Introduction
Single immutable state tree: Should be just one single javascript object. Describing the changes by ...
- ZOJ1524
题意:给定需要购买物品的顺序以及总物品对应的价格,求解按顺序购买物品时最小花费. 输入: m,n(m代表需要购买物品的清单,n代表总的物品数) Xi...(代表对应物品的序号以及价格) 输出: cos ...
- JAVA彩色图片变灰处理
File file = new File("F:/firefox.png"); File destFile = new File("F:/pic/" + Sys ...
- Examples_08_07
http://yarin.iteye.com/?page=4 Activity01.java package com.yarin.android.Examples_08_07; import andr ...
- Warning: World-writable config file '/etc/my.cnf' is ignored
1. 问题描述: 重启mysql服务时出现以下信息: Warning: World-writable config file '/etc/my.cnf' is ignored 出现这种情况的原因是:m ...
- .NET 编译器(”Roslyn“)介绍
介绍 一般来说,编译器是一个黑箱,源代码从一端进入,然后箱子中发生一些奇妙的变化,最后从另一端出来目标文件或程序集.编译器施展它们的魔法,它们必须对所处理的代码进行深入的理解,不过相关知识不是每个人都 ...
- Eclipse+maven发布ee项目jar包未发布
背景:在Eclipse中搭建好EE环境后,发布时,出现ClassNotFoundException: XX.XX.ConfigureListener,查看时,对应的jar包都引入了,项目没也没出错,但 ...
- zepto源码研究 - zepto.js-4(常用的工具)
$.each: /** * 以集合每一个元素作为上下文,来执行回调函数 * @param elements * @param callback * @returns {*} */ $.each = f ...
- DOM动态添加表格
var table=document.createElement("table"); table.border=1; var b=document.createElement(&q ...
- python记录
1. 序列的分片操作:需要提供两个索引作为边界,第1个索引的元素包含在分片内,第2个索引的元素不包含在分片内. 为了能让分片部分能够包含列表的最后一个元素,必需提供最后一个元素的下一个元素所对应的索引 ...