编写java程序时,一般一个类(或者接口)都是放在一个独立的java文件中,并且类名同文件名(如果类是public的,类名必须与文件名一致;非public得,无强制要求)。如果想把多个java类放在一个java文件中,则只能有一个public类。如下面的两个类放在同一个文件中就会报错,无法编译通过。 可以看出,因为TestOne.java文件中已经有一个public类TestOne,这时再加了一个public类TestTwo就报错了。如果将类TestTwo前面的public修饰符去掉就没有问题了。 我们下面介绍内部类的概念和使用,所谓内部类,简单的说,就是一个类定义在另一个类的内部。与上面的两个类在同一个文件中不同(TestOne和TestTwo虽然在一个文件中,但相互没有嵌套,是并列的)。 采用内部类,有时会对代码的可读性带来一点问题,但很多场景下还是很有必要的。尤其在使用一些框架(如java的swing,集合框架中的排序操作)等,使用内部类(尤其是匿名内部类)会带来很多便利;再比如我们在开发Android app时,就会大量的使用内部类,如果不了解内部类的含义和使用规则,几乎无法顺利的进行Android app的开发。 内部类有其特别的地方,其中核心之一是,内部类实例可以访问包含它的外部类的所有成员,包括private成员。另外非常关键的一点是,内部类的使用必须与一个外部类的实例绑定,注意是实例,后面的例子中会说明这点。 内部类也分好几种情况,下面一一来解释。 一、一般内部类…
JAVA: 理解Java中的类初始化
在运行 Java 代码时,很多时候需要弄清楚程序执行的流程,而面向对象的 Java 程序并非像主要为面向过程而设计的 C 语言一样去顺序执行(简单按照代码的顺序),这使得对于类文件的加载以及执行流程的理解非常重要。本文简单介绍了 Java 类的初始化部分具体过程,包括成员变量、静态代码块、构造函数等的初始化时机及执行流程。
初始化时机
根据 javase 8 的文档说明[1],一个类(本文暂不考虑接口)T 将在下列情况第一次出现前立即被初始化:
另外要注意:当一个类初始化时,它的父类如果没有初始化则会先被初始化。
初始化步骤
首先要弄清楚几个基本概念:静态代码块、构造代码块、构造方法、成员变量、子父类的初始化。
概念分析
- 静态代码块, 一般用于类的数据初始化,形式为:
class StaticDemo{ static { int a=1; System.out.println("I am a static block!"); } }
- 构造代码块,与静态代码块的区别在于少了 static 关键字:
class ConstructorBlockDemo{ { int a=1; System.out.println("I am a constructor block!"); } }
- 构造方法
public class ConstructorDemo{ public ConstructorDemo(){ int a=1; System.out.println("I am a constructor!"); } }
- 成员变量,一般就是类所定义的变量(描述类的属性),这个容易理解。分为默认初始化和显示初始化:
class FieldDemo{ int b;//initialized implicitly int a=1;//initialized explicitly }
需要注意的是,a=1;
在有些情况下(比如 FieldDemo 还有父类)并不一定立即在变量 a 分配内存后被赋值。
- 子父类的初始化,一般先进行父类初始化,然后进行子类初始化,分层进行。
执行顺序
【update】本文提到的程序可以使用在线可视化 IDE 帮助理解,Java Tutor – Visualize Java code execution to learn Java online
1. 首先程序运行时,从 main 方法所在的主类开始,但并 不意味着就是从 main 方法开始。而是 JVM 开始加载类:
public class LoadClassDemo{ static { int a=1; System.out.println("I am a static block!"); } public static void main(String[] args) { System.out.println("I am the main method!"); } }
上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

JVM 在加载 LoadClassDemo 时,静态代码块就开始执行了,从而对类的信息初始化。并且静态代码块只在类加载时执行,因此只执行一次。
2. 构造代码块。静态代码块执行完毕后,构造代码块就开始执行了,而且每次生成类的实例都会执行,区别于静态代码块只执行一次。比如:
class ObjectDemo { static { System.out.println("ObjectDemo static block!"); } { System.out.println("ObjectDemo constructor block"); } public ObjectDemo() { System.out.println("ObjectDemo constructor"); } } public class ConstructorBlockTest { static { System.out.println("I am a static block!"); } public static void main(String[] args) { System.out.println("I am the main method!"); ObjectDemo od1 = new ObjectDemo(); ObjectDemo od2 = new ObjectDemo(); } }
上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

3. 成员变量,在创建实例时被分配内存,之后进行默认初始化和显示初始化(如有)。
4. 构造方法,上述过程之后构造方法才被调用生成对象。
class ObjectDemo { static { System.out.println("ObjectDemo static block!"); } { System.out.println("ObjectDemo constructor block"); } String f="field value"; public ObjectDemo() { System.out.println("ObjectDemo constructor"); } } class ObjectDemo2 { static { System.out.println("ObjectDemo static block!"); } { System.out.println("ObjectDemo constructor block"); } String f2="field value"; public ObjectDemo2() { System.out.println("ObjectDemo constructor"); f2="field value changed!"; } } public class InitializationDemo { static { System.out.println("I am a static block!"); } public static void main(String[] args) { System.out.println("I am the main method!"); ObjectDemo od1 = new ObjectDemo(); ObjectDemo od2 = new ObjectDemo(); System.out.println(od1.f); ObjectDemo2 od3 = new ObjectDemo2(); System.out.println(od3.f2); } }
仔细分析上述综合示例结果,(然后点击图片放大核对):

实例理解
实例 1
请分析一下代码执行结果,然后思考注释掉的 super 与结果又何关系
class X { static String xVar="x value"; Y b = new Y(); static{ System.out.println(xVar); System.out.println("X static block"); xVar="static value"; } X() { System.out.println("X"); System.out.println(xVar); xVar="x value changed!"; } } class Y { String yVar="Y value"; Y() { System.out.println("Y"); System.out.println(yVar); yVar="y value changed!"; } void show(){ System.out.println(yVar); } } public class Z extends X { Y y ; static{ System.out.println("Z static block"); } { y = new Y(); y.show(); } Z() { //super System.out.println("Z"); } public static void main(String[] args) { System.out.println(new Z().xVar); } }
分析 1
先给出结果(点击图片放大):

分析过程如下(注意子父类的分层初始化):
1. 首先找到 main 方法所在类为 Z,所以最先开始加载 Z 类,由于 Z 类还有父类 X,因此又加载 X 类,先对 X 类初始化;
2.X 类加载并为静态变量 xVar 初始化,接着执行静态代码块,输出 x value
,接着输出X static block
并改变 xVar 值;
3.X 类加载完之后,Z 类开始执行静态代码块输出 Z static block
;
4. 接着初始化 X 类,初始化成员变量 y,因此输出Y
和y value
,由于没有构造代码块,所以继续执行构造方法输出 X
和static value
;
5. 到此 X 类初始化完成,再接着执行 Z 类的构造代码块输出 Y
和Y value
并改变 yVar 指,执行 show()方法后输出y value changed!
,最后执行 Z 的构造方法,输出Z
;
6. 到此 Z 类初始化完成,最终输出x value changed!
[4]。
实例 2
还有一个比较典型的例子[5],请分析程序执行结果:
class Base { Base() { preProcess(); } void preProcess() { } } class Derived extends Base { public String whenAmISet = "set when declared"; public Derived() { whenAmISet = "set in constructor"; } void preProcess() { whenAmISet = "set in preProcess()"; } } public class TestInitialization { public static void main(String[] args) { Derived d = new Derived(); System.out.println(d.whenAmISet); } }
分析 2
详见脚注 3,要注意本文中的例子在 Derived 类中添加了构造方法,所以最终结果为 set in constructor
更新:eclipse 调试演示图
后记
这次关于 Java 中类的初始化就介绍到这里了,关于 Java 类的生命周期可以参看 [6] 其实还有关于接口并没有提到,有兴趣可以参考 JLS 相关内容,即脚注 1 所引。另外,还是谈谈如何去深入学习一些知识的方法,比如这次类的初始化,首先当然是 search 关键字,然后自己动手写代码观察思考(还可以使用 Eclipse 断点观察程序执行流程),接着很有必要参考 JLS 中关于 Java Execution 的部分(Chapter 12),最后要想深入理解就得自己多改写代码,思考如何才能理解为何是这样执行的、这样设计有何好处。
原文:https://www.caoqq.net/java-class-initialization.html