java中如何理解初始化,JAVA: 理解Java中的类初始化

在运行 Java 代码时,很多时候需要弄清楚程序执行的流程,而面向对象的 Java 程序并非像主要为面向过程而设计的 C 语言一样去顺序执行(简单按照代码的顺序),这使得对于类文件的加载以及执行流程的理解非常重要。本文简单介绍了 Java 类的初始化部分具体过程,包括成员变量、静态代码块、构造函数等的初始化时机及执行流程。

初始化时机

根据 javase 8 的文档说明

T 的一个实例被创建

T 的一个静态方法被调用;

T 声明的一个静态变量被赋值;

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 分配内存后被赋值。

子父类的初始化,一般先进行父类初始化,然后进行子类初始化,分层进行。

执行顺序

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!");

}

}

上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

3d056b7ed0243d76ee503fbc8ad0308a.pngJAVA: 理解Java中的类初始化

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();

}

}

上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

e63605704e97bd490e4b4f2ad00d0c9f.pngJAVA: 理解Java中的类初始化

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);

}

}

仔细分析上述综合示例结果,(然后点击图片放大核对):

659348fd963579d6fc58f161876e0be4.pngJAVA: 理解Java中的类初始化

实例理解

实例 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

先给出结果(点击图片放大):

5797f4363c56cf136edb4af2330b4d27.pngJAVA: 理解Java中的类初始化

分析过程如下(注意子父类的分层初始化):

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!

实例 2

还有一个比较典型的例子

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 调试演示图

4b74d68c4f8ebba21d3ad74b537a38dc.gif

后记

这次关于 Java 中类的初始化就介绍到这里了,关于 Java 类的生命周期可以参看

cbd5a61765aed016a56103b68dcc8d51.gif

相关

Related Posts

Java学习笔记:内部类/匿名内部类的全面介绍

编写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: Socket(套接字)讲解

什么是 Socket Socket(套接字):是在网络上运行两个程序之间的双向通信链路的一个端点。socket绑定到一个端口号,使得 TCP 层可以标识数据最终要被发送到哪个应用程序。 正常情况下,一台服务器在特定计算机上运行,​​并具有被绑定到特定端口号的 socket。服务器只是等待,并监听用于客户发起的连接请求的 socket 。…

Java 取得使用者输入 java.util.Scanner用法

Scanner是新增的一个简易文本扫描器,在 JDK 5.0之前,是没有的。查看最新在线文档: public final class Scanner extends Object…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值