借用一个小例子来分析Java程序的初始化过程,其中涉及类的加载,初始化顺序
class Insect {
private int i = 9;
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 = printInt("static Insect.x1 initialized");
static int printInt(String s) {
System.out.print(s);
System.out.println(". Insect.x1 = " + x1 );
return 47;
}
}
public class Beetle extends Insect {
private int k = printInt("Beetle.k initialized");
public Beetle() {
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = printInt("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
以下对程序的分析,如有错误,不吝指教。
(1)如果要运行这段程序,启动虚拟机时,指定要Beetle(包含main()方法)作为启动的类,虚拟机会先初始化Beetle类。
(2)当初始化一个类时,发现其父类尚未初始化,则需要先触发其父类的初始化,所以需要先初始化Insect(注意:这里的初始化指的是类加载过程中的初始化阶段,不是new 类的对象);
(3)在初始化的过程中,会对static的属性进行赋值,也就是Insect.x1。在类的加载过程中分为以下几个阶段:加载-->验证-->准备-->解析-->初始化。在准备阶段会对类变量(注意是类变量,static修饰的变量)进行内存分配,静态变量的初始值是零值。此时Insect.x1 = 0;
(4)现在Insect进行到了加载过程的初始化阶段,会对Insect.x1赋值,
x1 = printInt("static Inject.x1 initialized");
此时,Insect.x1 = 47。Insect类加载完毕
(5)加载Beetle类,同样是在准备阶段将Beetle.x2置零,初始化阶段对
Beetle.x2 = printInt("static Beetle.x2 initilaize");此时,Beetle.x2 = 47。Beetle类加载完毕。
(6)开始执行main()方法,第一条语句是打印;
(7)执行第二条语句,创建Beetle对象b。子类对象会包含一个父类的子对象,这个对象与你用父类直接创建的对象是一样的,区别就是一个被包装在子类的内部,一个在外部。所以首先会调用父类相应的构造方法,创建父类的对象,由于Beetle没有显式的调用父类的构造方法,虚拟机会使用父类的无参构造方法。
(8)这个时候相当于执行:new Insect()。会执行一下动作:
a .首先会在堆上分配一块内存;
b.然后这块存储空间会被置零,这就自动的将Insect类里面的所有属性变成零值(注意,Java要求:在调用任何方法之前,所有属性都至少有一个初值。正是通过置零存储空间来保证这条规则的)。由于x1是类变量,并不存储在这块在堆上分配内存,x1的值仍然为47,i=0,j=0;
c.执行所有出现字段定义处的初始化动作,也就是private int i=9。此时,x1=47,i=9;j=0;
d.执行构造函数;
创建父类对象完成,Insect.x1 = 47,i=9,j=39。
(9)创建子类Beetle的对象,如第八步一般,k的值被置零,k=0;接着执行k定义处的初始化,k=47;执行构造函数;
至此,程序运行完毕,程序的输出结果是:
/********
//验证第三步中所说,在准备阶段给x1赋零值。
static Insect.x1 initialized. Insect.x1 = 0
static Beetle.x2 initialized. Insect.x1 = 47
Beetle constructor
i = 9, j = 0
Beetle.k initialized. Insect.x1 = 47
k = 47
j = 39
********/
============================================================================================
看了上面的内容,觉得还不够完整,再补充一点关于静态语句块和静态变量、子类初始化的内容。
class Print {
//为了演示静态语句块和静态属性的初始化是按照定义的先后顺序而设的
public Print() {
System.out.println("class Print is created.");
}
//为了演示构造子类之前,先调用父类的构造方法
public Print(String className) {
System.out.println("class Print is create by " + className);
}
}
class Father {
private Print p1 = new Print("Father");
static {
System.out.println("Before static field init." +
"\n----------------" );
//在类变量定义之前的静态语句块不可以使用,编译无法通过
//System.out.println(p);
//但是可以在静态语句块中对其进行赋值,这样是没问题的
//p = new Print();
//即使已经定义了,也不能使用
//Cannot reference a field before it is defined
//System.out.println(p);
}
static Print p = new Print();
static {
System.out.println("----------------\n" +
"After static field init.");
}
public Father(int i) {
System.out.println("class Father is created ");
}
}
class Son extends Father {
private Print p = new Print("Son");
public Son() {
//父类的构造方法要放在有效代码的第一行
super(0);
System.out.println("class Son is created");
}
public static void main(String[] args) {
new Son();
}
}
/*output*/
Before static field init.
----------------
class Print is created.
----------------
After static field init.
class Print is create by Father
class Father is created
//以上两行的输出说明先调用父类的构造方法
class Print is create by Son
class Son is created
从上面的输出中,我们可以得到以下结论:
1)静态语句块和静态属性的初始化顺序是按照定义的先后顺序来进行的,但是都早于非静态属性的初始化(其实这是句废话,看了上面一段的就明白了)。在静态属性定义之前的静态语句块可以对其赋值,但是不能是使用(即使已经初始化了,仍然不能使用,记住:只有在定义处之后才能使用)。
2)我们知道,当子类继承父类时,子类已经知道了父类的一切,并且可以访问父类中任何声明为public和protected的成员。这意味着,必须假定父类的所有成员都是有效的。为了确保这一目的,唯一的办法就是:首先调用父类的构造器。在调用父类构造器之前,自然会执行父类属性定义处的初始化(又说了一句废话)。
3)关于构造方法:
a.一个类如果没有构造方法,编译器认为:这个类需要构造方法,给它提供一个默认无参构造方法,这个构造方法什么也不做;
b.这个类如果有了构造方法,无论有无参数,编译器都会认为:这个类有构造方法,它知道自己要做什么,所以不需要我操心了,不要给它提供任何额外的构造方法了。
关于子类构造方法:子类构造方法必须在第一行(不包括注释)使用关键字super调用父类的构造方法,否则编译器默认调用父类的无参构造方法。关于这句话的解释是这样的:如果父类有无参构造方法,无论是自己写的,还是编译器默认创建的,那么可以不不使用super(),当然也可以使用;如果父类没有无参构造方法,那就必须使用super(参数)来显式调用,否则编译无法通过。
以上内容均为原创,转载请注明:http://blog.csdn.net/yuhongye111/article/details/25502057
分享到:
相关推荐
通过查看和分析这个文件的源代码,我们可以更深入地理解这两种初始化块的工作原理和应用场景。 总之,理解并有效地使用静态和对象初始化块是Java开发中的关键技能,它们可以帮助我们更好地控制类和对象的初始化过程...
当我们谈论“Java类继承初始化顺序”时,涉及到的关键知识点包括构造器、成员变量的初始化、super关键字以及方法的覆盖。 首先,了解类初始化的顺序至关重要。当创建一个子类实例时,初始化过程遵循以下步骤: 1. ...
`<clinit>`和`<init>`方法的存在揭示了Java初始化的底层机制,它们确保了类和对象的正确初始化,同时也提供了异常处理的能力,使得在初始化过程中出现的问题能够被准确捕获和定位。 总结: Java初始化涉及类的静态...
举个例子:我在国内某公司曾经负责维护公司内部的全球通信系统的服务端,公司员工在10W人以上,需要初始化的内容就是将这10W多的用户信息,在系统启动时就初始化到缓存中,用户通过客户端登陆,像服务端发出请求后,...
在这个例子中,`b` 先于 `a` 被初始化,而静态成员 `c` 在类实例化之前已经初始化。 在Java中,初始化顺序则有所不同: 1. 基本类型的静态字段和引用类型的静态字段(如果它们是常量,即final且已初始化):这些...
本文将深入探讨Java中的初始化过程,包括对象的构造器初始化以及类成员的初始化顺序,同时也会涉及Java的垃圾回收机制,即内存的自动清理。 首先,让我们详细了解一下Java中的构造器初始化。构造器是Java中用于初始...
Java多线程中的延迟初始化是一种优化策略,旨在推迟对象的创建直到它们真正被需要时,以减少不必要的资源消耗。在多线程环境下,确保延迟初始化的线程安全性至关重要,否则可能导致数据不一致或运行时异常。 非线程...
Java 对象初始化的多维度分析 Java 对象初始化是一个基础概念,但是很多人不知道它的内涵,特别是在类继承时。下面,我们从三个维度来分析 Java 对象的初始化过程。 一、从程序运行维度分析 在 Java 中,对象初始...
在这个例子中,当我们首次主动使用`Singleton`类时,`counter1`和`counter2`会被初始化。由于`singleton`对象的创建,`counter1`会增加1,而`counter2`保持其默认值0。 了解类的初始化对于优化代码性能、避免并发...
Java 在 HashMap 初始化时赋初值过程解析 Java 中的 HashMap 是一种常用的数据结构,一般用来做数据字典或者 Hash 查找的容器。在初始化并赋初值时,我们通常使用 `HashMap, Object> map = new HashMap();` 的方式...
在Java编程语言中,初始化数据域(字段或成员变量)是程序设计的重要组成部分。它确保在对象被创建或类被加载时,变量拥有一个合理的初始值。本篇文章将详细探讨四种常见的初始化数据域的方法,结合给出的代码示例...
根据不同的初始化方式,Java 提供了三种主要的数组初始化方法:动态初始化、静态初始化以及隐式初始化。 #### 二、动态初始化 动态初始化是在创建数组时由系统自动为数组中的元素赋以默认值。默认值取决于元素的...
通过分析这些代码,可以更深入地理解如何在实际项目中应用初始化参数。 总的来说,Servlet初始化参数是配置Servlet行为和应用配置的关键机制,理解和掌握其用法对于Java Web开发者来说至关重要。通过阅读和实践这个...
mq连接时初始化的动作,用于java开发的简单例子。。。。
在面向对象语言中(如Java、C#等),当创建一个继承自某个基类的子类对象时,会有一个特定的初始化顺序。这个顺序通常遵循以下步骤: 1. **基类静态成员初始化**:如果基类中有任何静态成员,则会在程序启动时按照...
### Java学习之神奇初始化 #### 知识点详解 在Java编程语言中,类的初始化顺序是一个非常重要的概念。特别是当涉及到静态成员(`static`)的初始化时,这一顺序对于理解程序的行为至关重要。根据提供的文件信息,...
`@PostConstruct`是Java EE规范的一部分,它用于标记一个方法,这个方法将在对象被Spring容器初始化完成后、所有依赖注入完成并且在该对象的`init()`方法之前调用。使用`@PostConstruct`注解的方法只会被调用一次,...
Java 语言中数组数据操作包括数组的初始化、数组的基本操作和数组的遍历等。数组是一种重要的数据结构,在 Java 语言中广泛应用。 Java 语言是一种功能强大且灵活的编程语言,它提供了强大的功能和灵活的编程模型。...
例子将帮助你理解如何声明、初始化和操作数组。 6. **异常处理**:Java有完善的异常处理机制,可以捕获并处理运行时错误。通过示例,你将学习try-catch-finally结构,以及如何自定义异常。 7. **输入/输出流**:...