《Java编程思想》第五章 初始化与清理

来源:互联网 发布:python 粒子群算法库 编辑:程序博客网 时间:2024/06/06 07:13

一、初始化

1.1、构造器

构造器分为默认构造器(无参构造器)和带参构造器。写的类中如果没有构造器,编译器会自动帮你创建一个默认构造器。

1.2、static

给变量和方法加上“static”关键词,表示该变量和方法是静态变量和静态方法。静态变量和静态方法属于类。

1.3、初始化基本概念

方法中的局部变量不会被默认初始化,而如果不显式初始化编译会报错。
类中的非静态数据成员和静态数据成员都会被默认初始化。由《Java编程思想》第二章 一切都是对象可知,共有9种类型:基本类型(boolean,char,byte,short,int,long,float,double)和对象引用类型。
这9种类型的默认初始化值如下所示:

boolean falsechar 0值对应的字符byte 0short 0int 0long 0float 0.0double 0.0对象引用类型 null

非静态数据成员有4种初始化形式:默认初始化,定义初始化,非静态数据成员初始化语句,构造器初始化(初始化顺序按照从前到后的顺序)
静态数据成员有3种初始化形式:默认初始化,定义初始化,静态数据成员初始化语句(初始化顺序按照从前到后的顺序)
对于非静态数据成员来说,默认初始化,构造器初始化一定会进行,定义初始化,非静态数据成员初始化语句不一定存在;对于静态数据成员来说,默认初始化一定会进行,定义初始化,静态数据成员初始化语句不一定存在。

1.4、初始化时机和顺序

1.4.1、静态数据成员初始化时机和顺序

静态数据成员初始化中的“默认初始化”时机是:第一次加载类文件,在内存中创建类对象实例;静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”时机是:触发条件的满足。这两个阶段之间的间隔可能很“远”。从总体来看,静态数据成员初始化的顺序是:默认初始化,定义初始化和静态数据成员初始化语句。

1.4.1.1、第一次加载类文件,创建类对象实例

程序运行中,第一次遇到类的定义声明,就会去加载相应的类文件,并在内存中创建类对象实例。比如在如下代码中,会依次加载Main和Node的类文件,并在内存中创建相应的类对象实例。在类对象实例创建过程中,所有静态数据成员会被“默认初始化”。

public class Main {    public static void main(String[] args) {        Node node = null;    }}class Node {}
1.4.1.2、触发条件

静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”的触发条件是:第一次操作(读和写)类的静态数据成员,调用类的静态方法,调用类的构造方法(其实严格意义上来说,类的构造方法也是一个静态方法,这里作为独立的条件存在),由于“extends”关键词级联触发(在触发子类的静态数据成员初始化的过程中,由于“extends”关键词的存在,而去触发父类的静态数据成员初始化),“Class.forName()”语句执行等等动作触发。
注意:读取类的编译期常量并不会触发静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”,具体可见《编译期常量的一些事儿》
1、级联触发例子:

/***输出结果是:Super Two false*/class Super {    static { System.out.print("Super "); }}class One {    static { System.out.print("One "); }}/***extends的存在,引起级联触发*/class Two extends Super {    static { System.out.print("Two "); }}class Test {    public static void main(String[] args) {        //触发了静态数据成员的“默认初始化”        One o = null;        //触发了静态数据成员的“默认初始化,定义初始化和静态数据成员初始化语句”        Two t = new Two();        System.out.println((Object)o == (Object)t);    }}

2、触发“域真正所在类”的静态数据成员初始化

/***输出结果:1729*/class Super {    static int taxi = 1729;}class Sub extends Super {    static { System.out.print("Sub "); }}class Test {    public static void main(String[] args) {        //taxi域真正所在类是Super,因而不触发Sub类静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”        System.out.println(Sub.taxi);    }}

1.4.2、非静态数据成员初始化时机和顺序

创建类实例时触发,一个类的类实例可以被创建任意次(因而一个类的非静态数据成员初始化流程可以被触发任意次),创建在调用该类的构造方法时发生。
一旦类实例的内存被分配好,就会进行非静态数据成员的默认初始化,紧接着进行非静态数据成员的定义初始化,执行非静态数据成员初始化语句,最后执行构造器方法。

由于一个类的类对象实例创建一定先于该类的类实例创建,因而静态数据成员的初始化流程一定先于非静态数据成员初始化流程执行,但是静态数据成员初始化流程只会被触发一次,而非静态数据成员初始化流程可以被触发任意次。

1.5、代码例子

以下代码展示了上述7种初始化的顺序关系:

package com.dslztx.character;/** * 调用Main类的静态方法,使得“加载Main类文件,创建类对象实例(引发静态数据成员的默认初始化)”,接着触发静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”<br/> * 1)分配完类对象实例的内存之后,首先进行静态数据成员的默认初始化(静态数据成员a,b,c被默认初始化为0值)<br/> * 2)接下来由于触发,进行静态数据成员的“定义初始化,静态数据成员初始化语句”。按照静态数据成员的定义顺序,依次进行静态数据成员的定义初始化,有些静态数据成员也可能没有定义初始化(a没有定义初始化;b有定义初始化被初始化为10,这同时<br/> * 说明此时c还未被定义初始化,还只是默认初始化得到的0值;c有定义初始化,被初始化为20<br/> * 3)执行静态数据成员初始化语句,有可能没有静态数据成员初始化语句(a被赋值为10,c被赋值为30)<br/> * * <br/> * 创建Main类实例<br/> * 1)分配完类实例的内存之后,首先进行非静态数据成员的默认初始化(非静态数据成员d,f,g被默认初始化为0值)<br/> * 2)按照非静态数据成员的定义顺序,依次进行非静态数据成员的定义初始化,有些非静态数据成员也可能没有定义初始化(d没有定义初始化;f有定义初始化被初始化为10,这同时<br/> * 说明此时g还未被定义初始化,还只是默认初始化得到的0值;g有定义初始化,被初始化为20<br/> * 3)执行非静态数据成员初始化语句,有可能没有非静态数据成员初始化语句(d被赋值为10,g被赋值为40)<br/> * 4)执行构造方法语句(d被赋值为20,g被赋值为30)<br/> * * @author dsl */public class Main {    static int a;    static int b = f();    static int c = 20;    static int f() {        return c + 10;    }    static {        System.out.println("static initialize start");        System.out.println(a);        System.out.println(c);        a = 10;        c = 30;        System.out.println(a);        System.out.println(c);        System.out.println("static initialize end");    }    int d;    int f = h();    int g = 20;    int h() {        return g + 10;    }    {        System.out.println("initialize start");        System.out.println(d);        System.out.println(g);        d = 10;        g = 40;        System.out.println(d);        System.out.println(g);        System.out.println("initialize end");    }    public Main() {        d = 20;        g = 30;        System.out.println("constructor print start");        System.out.println(d);        System.out.println(g);        System.out.println("constructor print end");    }    /**     * 调用Main类的静态方法,使得“加载Main类文件,创建类对象实例(引发静态数据成员的默认初始化)”,接着触发静态数据成员初始化中的“定义初始化和静态数据成员初始化语句”        */    public static void main(String[] args) throws ClassNotFoundException {        System.out.println(Main.a);        System.out.println(Main.b);        System.out.println(Main.c);        System.out.println("分割线-------------");        // 创建Main类的类实例        Main main = new Main();        System.out.println(main.d);        System.out.println(main.f);        System.out.println(main.g);    }}

执行结果如图1所示:

图1
这里写图片描述

二、清理

“finalize”方法的调用时机:只有当运行垃圾回收过程时,才会调用“finalize”方法。
那么何时运行垃圾回收过程?垃圾回收过程由JVM控制,有可能执行,也有可能不执行:
1)JVM分配的内存快要耗尽,可以增加运行垃圾回收过程的可能性。
2)执行“System.gc()”语句,可以增加运行垃圾回收过程的可能性。
3)如果直接关闭JAVA程序,那么系统直接回收JAVA程序所占用的内存,而不会去运行垃圾回收过程。
由以上解释可见,“finalize”方法不一定会被调用,因为垃圾回收过程有可能不被运行,要执行清理,就得显式调用自己的清理方法,而不可依赖于“finalize”方法。

三、其他

3.1、this关键词

两个作用:
1)表示对当前对象的引用
2)添加参数列表表示对相应的构造器的调用。this语句调用构造器在且只能在构造器中进行,且必须为构造器的第一条程序语句

3.2、数组初始化的3种方式

举例说明:
int[] a=new int[10];
int[] a={10,20,30}
int[] a=new int[]{10,20,30}

3.3、可变参数列表

3.4、失效的“向前引用”

根据“《Java编程思想》第二章 一切都是对象”可知,Java解决了“向前引用”的问题,即在某处可以使用在其后定义的资源。
但是现在有如下一段代码:

public class Main {    int a = f(c);    int c = 10;    int f(int n) {        return n;    }}

int a = f(c);语句处,编译器报出Illegal forward reference(非法的向前引用)错误。这看起来是“向前引用”失效了。
其实这个“向前引用”失效跟初始化顺序相关,编译器认为你的本意是将“10”传给“f()”方法,如果上面这段代码编译通过的话,“f()”方法最后得到的传入值将是“0”(c的默认初始化值为0)。为了防止这种“surprise”的产生,编译器报出了如上错误。(如果将int a=f(c);语句和int c=10;语句互换位置,编译器就不会报错)
又有以下一段代码:

public class Main {    int a = g();    int c = 10;    int g() {        return c;    }    public static void main(String[] args) {        Main main = new Main();        System.out.println(main.a);    }}

该段代码编译能够通过,但是从严格意义上来,如果按照上述思路,那么int a = g();语句也是一个非法的向前引用。
综上,Java关于这个问题的语法糖真是不伦不类。



参考文献:
[1]http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4

0 0
原创粉丝点击