理解初始化块
Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个java对象的状态初始化,然后将java对象返回给程序,从而让整个java对象的信息更加完整。
与构造器作用非常类似的是初始化块,它也可以对Java对象进行初始化操作。
初始化块是Java类里可出现的第四种成员,除了初始化块还有属性,方法,构造器。
一个类可以有多个初始化块,相同类型的初始化之间有顺序,前面定义的初始化块先执行,后面定义的初始化块后执行。
初始化块的语法格式如下:
[修饰符] {
// 初始化块的可执行代码
}
初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。
初始化里的代码可以包含任何可执行性语句,包括定义局部变量,调用其他对象的方法,以及使用分支,循环语句。
初始化虽然也是Java类的一种成员,但它没有名字,也没有标示,因此无法通过类,对象来调用初始化块。
初始化块只能在创建Java对象时隐式执行,而且在执行构造器之前执行。
public class Linkin
{
{
String name = "LinkinPark";
System.out.println("第一个初始化块,定义一个局部变量。");
if ("LinkinPark".equals(name))
{
System.out.println("第一个初始化块,使用一个if语句。");
}
}
{
System.out.println("第二个初始化块,建议将多个初始化合并到一个初始化块中。");
}
public Linkin()
{
System.out.println("默认的构造器,初始化块执行结束后构造器开始执行。");
}
public static void main(String[] args)
{
new Linkin();
// 第一个初始化块,定义一个局部变量。
// 第一个初始化块,使用一个if语句。
// 第二个初始化块,建议将多个初始化合并到一个初始化块中。
// 默认的构造器,初始化块执行结束后构造器开始执行。
}
}
初始化块是构造器的补充,初始化块总是在构造器执行之前执行。
如果有一段初始化处理代码对所有的对象完全相同,且无需接受任何参数,就可以把这段代码提取到初始化块中。
当Java创建一个对象时,系统先为该对象的所有实例变量分配内存,前提是该类已经被加载过了,接着程序开始对这些实例变量执行初始化,其初始化顺序是,先执行初始化块或
申明实例变量时指定的初始值,这两个地方指定初始化值的执行允许与他们在源代码中的排列顺序相同,再执行构造器里指定的初始值。
public class Linkin
{
String name = "NightWish";
{
age = 18;
name = "LinkinPark";
}
// 申明一个实例变量,同时赋初始值为1。
int age = 25;
public static void main(String[] args)
{
Linkin linkin = new Linkin();
// java中允许先初始化块中使用初始化代码块后面申明的变量,age开始的时候是18,后来变为了25。
System.out.println("linkin对象的age:" + linkin.age);
// name刚开始的时候是NightWish,后来变成了LinkinPark。
System.out.println("linkin对象的name:" + linkin.name);
// linkin对象的age:25
// linkin对象的name:LinkinPark
}
}
初始化块和构造器
从某种程度上来看,初始化块就是构造器的补充,初始化块总是在构造器执行之前执行。系统同样可使用初始化块来进行对象的初始化操作。
与构造器不同的是,初始化块是一段固定执行的代码,并不能接受任何的参数。
初始化对同一个类的所有对象所进行的初始化处理完全相同,基于这个原因,不难发现初始化的基本用法,如果有一段初始化处理代码对所有的对象完全相同,且无须接受任何参
数,就可以把这段初始化处理代码提取到初始化块中。
通过把多个构造器中的相同代码提取到初始化块中定义,能更好的提高初始化代码的复用,提高整个应用的可维护性。
实际上初始化块是一个假象,使用Javac命令编译Java类之后,该Java类中的初始化块会消失,初始化块中的代码会被还原到每个构造器中,且位于构造器所有代码的前面。
1),定义一个Linkin类,类中写一个初始化块,写两个构造器。
public class Linkin
{
{
System.out.println("初始化块。");
}
public Linkin()
{
System.out.println("第一个构造器。");
}
public Linkin(String name)
{
System.out.println("第二个构造器。");
}
}
2),使用Javac命令来编译下Linkin.java。
➜ ~ vi Linkin.java
➜ ~ javac -d . Linkin.java
➜ ~ ls -tl
total 24
-rw------- 1 LinkinPark staff 6 5 19 10:38 JD-GUI
-rw-r--r-- 1 LinkinPark staff 491 5 19 10:37 Linkin.class
-rw-r--r-- 1 LinkinPark staff 226 5 19 10:37 Linkin.java
3),使用Java反编译工具来看下生成的class文件,初始化块中的代码确实移到了多个构造器中,然后初始化块就消失了。
这里有一个问题就是,为毛我使用Mac的反编译工具JD反编译Linkin.class之后,莫名其妙的多import了一个PrintSteam,我晕。
import java.io.PrintStream;
public class Linkin
{
public Linkin()
{
System.out.println("初始化块。");
System.out.println("第一个构造器。");
}
public Linkin(String paramString)
{
System.out.println("初始化块。");
System.out.println("第二个构造器。");
}
}
与构造器类似,创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直上溯到Java.lang.Object类,并执行java.lang.Object类的初始化块,开始执行
java.lang.Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器。。。最后才执行该类的初始化块和构造器,返回该类的对象。
静态初始化块
如果定义初始化块时使用了static修饰符,则这个初始化块就变成了静态初始化块,也称为类初始化块。
普通初始化块负责对对象执行初始化,类初始化块则负责对该类进行初始化。
静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行,通常用于对类变量执行初始化处理。
类初始化块总是比普通初始化先执行,并且只执行一遍。值得注意的是静态初始化块也属于类的静态成员,同样遵循静态成员不能访问非静态成员的规则。
与普通初始化块类似:
1),系统在类初始化阶段执行静态初始化块,不仅会执行本类的静态初始化块,而且还会上溯到java.lang.Object类,先执行java.lang.Obejct类的静态初始化块,然后在执行其父
类的静态初始化块,,,最后才执行该类的静态初始化块,经过这个过程,才完成了该类的初始化过程。
只有当类初始化完成后,才可以在系统中使用这个类,包括访问这个类的类方法,类变量或者用这个类来创建实例。
2),静态初始化块和申明静态成员变量时所指定的初始值都是该类的初始化代码,他们的执行顺序与源程序中的排列顺序相同。
public class SubClass extends SuperClass
{
static
{
System.out.println("子类的静态初始化块。");
}
{
System.out.println("子类的初始化块。");
}
public SubClass()
{
System.out.println("子类的构造器。");
}
public static void main(String[] args)
{
new SubClass();
System.out.println("=====华丽丽的分割线======");
new SubClass();
// 父类的静态初始化块。
// 子类的静态初始化块。
// 父类的初始化块。
// 父类的构造器。
// 子类的初始化块。
// 子类的构造器。
// =====华丽丽的分割线======
// 父类的初始化块。
// 父类的构造器。
// 子类的初始化块。
// 子类的构造器。
}
}
class SuperClass
{
static
{
System.out.println("父类的静态初始化块。");
}
{
System.out.println("父类的初始化块。");
}
public SuperClass()
{
System.out.println("父类的构造器。");
}
}
最后这里再补充几点关于类加载的内容
Java中一个对象的创建过程:
1. 当构造一个对象的时候,系统先构造父类对象,再构造子类对象。
2. 构造一个对象的顺序:
1),递归地创建父类的 static 成员(即使只调用其子类静态成员,也会先创建父类静态成员);
2),顺序地创建本类的 static 成员(只要调用这个类的属性或方法或使用该类来创建一个实例对象都需创建,而且只需要创建一次);
3),递归地创建父类对象(先创建父类非静态成员,按照源码位置依次执行初始化块和申明变量时赋的初始值,再调用父类构造方法);
4),顺序地创建本类非静态成员(包括属性和方法,按照源码位置依次执行初始化块和申明变量时赋的初始值);
5),调用本类的构造方法(它可调用本类或父类的成员,也可调用本类的其他构造方法);
6),创建完成了,一个Java对象诞生啦。
什么时候类加载:
第一次需要使用类信息时加载,只需要加载一次。
类加载的原则:延迟加载,能不加载就不加载。
触发类加载的几种情况:
1),调用静态成员时,会加载静态成员真正所在的类及其父类。通过子类调用父类的静态成员时,只会加载父类而不会加载子类。
2),第一次 new 对象的时候加载,第二次再 new 同一个类时,不需再加载。
3),加载子类会先加载父类。
如果静态属性有 final 修饰时,则不会加载,当成常量使用。
如果访问的是类的公开静态常量,那么如果编译器在编译的时候能确定这个常量的值,就不会被加载;如果编译时不能确定其值的话,则运行时加载。
比如,public static final int a =123,编译时a变量就已经确定值了,所以不会再加载类。
类加载的顺序:
1),加载静态成员或者代码块:
先递归地加载父类的静态成员或者代码块(Object的最先);再依次加载到本类的静态成员;
同一个类里的静态成员或者代码块,按源代码的顺序加载;
如果其间调用静态方法,则调用时会先运行静态方法,再继续加载。同一个类里调用静态方法时,可以不理会源代码的顺序;
调用父类的静态成员,可以像调用自己的一样;但调用其子类的静态成员,必须使用“子类名.成员名”来调用。
2),加载非静态成员或者代码块:
先递归地加载父类的非静态成员或者代码块(Object的最先);再依次加载到本类的非静态成员;
同一个类里的非静态成员或者代码块,按写代码的顺序加载。同一个类里调用方法时,可以不理会写代码的顺序;
调用属性时,必须注意加载顺序。如果能在加载前调用,值为默认初始值(如:null或者0或者false);
调用父类的非静态成员(private 除外),也可以像调用自己的一样。
3),调用构造方法:
先递归地调用父类的构造方法(Object的最先);默认调用父类空参的,也可在第一行super写明调用父类某个带参的构造器;
再依次到本类的构造方法;构造方法内,也可在第一行this写明调用某个本类其它的构造方法;
由于构造子类时会先构造父类;而构造父类时,其所用的静态成员和非静态属性是父类的,但非静态方法却是子类的;
由于构造父类时,子类并未加载;如果此时所调用的非静态方法里有成员,则这个成员是子类的,且非静态属性是默认初始值的。