本文在“针对类(Class)和抽象类(Abstract Class)(即不包括接口(Interface),枚举(Enum),注解(Annotation)),且不存在继承关系”的背景下进行介绍。
一、初始化 1.1、实例对象和类对象初始化 1.1.1、实例对象初始化 实例对象的字段(即普通字段)按照如下顺序进行初始化:
1 2 3 4 1、默认初始化 2、定义初始化 3、实例初始化语句初始化 4、构造器初始化
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package chapter5;public class InitialOrder { int i = f(10 ); { System.out.println("定义初始化后 i:" + i); i = 20 ; System.out.println("实例初始化语句初始化后 i:" + i); } public InitialOrder () { i = 30 ; System.out.println("构造器初始化后 i:" + i); } public static void main (String[] args) { new InitialOrder (); } public int f (int a) { System.out.println("默认初始化后 i:" + i); return 10 ; } }
触发条件:实例对象的创建。需要注意的是:1)实例对象初始化中“默认初始化”和“构造器初始化”初始化阶段必存在,“定义初始化”和“实例初始化语句初始化”初始化阶段可选存在;2)实例对象初始化中初始化阶段之间连续不间断。
1.1.2、类对象初始化 类对象的字段(即静态字段)按照如下顺序进行初始化:
1 2 3 1、默认初始化 2、定义初始化 3、静态初始化语句初始化
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package chapter5;public class InitialOrder2 { public static void main (String[] args) throws ClassNotFoundException { ExperimentObject object = null ; Class.forName("chapter5.ExperimentObject" ); } } class ExperimentObject { static int i = f(10 ); static { System.out.println("定义初始化后 i:" + i); i = 20 ; System.out.println("静态初始化语句初始化后 i:" + i); } public static int f (int a) { System.out.println("默认初始化后 i:" + i); return 10 ; } }
类对象初始化中“默认初始化”初始化阶段触发条件:第一次遇见类的声明,从而引发加载类的Class文件和创建类对象。 类对象初始化中“定义初始化和静态初始化语句初始化”初始化阶段触发条件:第一次访问类的“静态字段,静态方法,构造方法”,第一次子类触发级联触发父类,Class.forName(相应类类路径)
语句第一次执行。 综合来看:1)类对象初始化中“默认初始化”初始化阶段必存在,“定义初始化”和“静态初始化语句初始化”初始化阶段可选存在;2)类对象初始化中“默认初始化”和“定义初始化,静态初始化语句初始化”初始化阶段之间可间断不连续(因为“默认初始化”初始化阶段和“定义初始化,静态初始化语句初始化”初始化阶段有相互独立的触发条件),“定义初始化”和“静态初始化语句初始化”初始化阶段之间连续不间断。
1.2、其他 关于实例对象和类对象初始化有以下几点说明:
实例对象和类对象初始化中“默认初始化”初始化阶段的默认初始化值遵照Java编程思想#一切都是对象 中的表2
类对象初始化先于实例对象初始化
类对象初始化至多进行1次,实例对象初始化可进行任意多次
最后是一个关于实例对象和类对象初始化的综合示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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" ); } 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 = new Main (); System.out.println(main.d); System.out.println(main.f); System.out.println(main.g); } }
二、清理 实例对象和类对象由垃圾回收器负责进行回收。
三、其他 3.1、方法重载 1、重载方法之间以参数类型列表进行区分,不能以返回值进行区分 2、调用重载方法时,最终会绑定到最契合传入实参的重载方法,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package chapter5;public class Overloading { public static void main (String[] args) { Overloading o = new Overloading (); byte a = 10 ; o.f(a); o.g(o); o.h(a); o.j(o); } public void f (short a) { System.out.println("f(short)" ); } public void g (Object a) { System.out.println("g(Object)" ); } public void h (short a) { System.out.println("h(short)" ); } public void h (byte a) { System.out.println("h(byte)" ); } public void j (Object a) { System.out.println("j(Object)" ); } public void j (Overloading a) { System.out.println("j(Overloading)" ); } }
3.2、构造器 当且只当不显式指定构造器时,编译器才会自动创建一个默认构造器。 允许进行构造器方法重载。
3.3、this关键词 this关键词有两个用途:“指代本实例对象”和“在构造器中调用构造器”。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package chapter5;public class ThisKeyword { int a; int b; public ThisKeyword (int a, int b) { this .a = a; this .b = b; } public ThisKeyword (int a) { this (a, 10 ); } public void setA (int A) { this .a = A; } public ThisKeyword getThisInstance () { return this ; } }
使用this调用构造器有以下限制:
“this调用构造器”只能在构造器中使用
“this调用构造器”只能置于构造器最起始处
“this调用构造器”在同一个构造器中至多使用1次
3.4、finalize()方法 垃圾回收过程中,在回收对象之前至多会调用1次对象的finalize()
方法,因此,该方法机制的设计初衷是我们可以覆盖实现自己的finalize()
方法,在覆盖实现后的finalize()
方法中做垃圾回收前的清理工作,但是基于以下两点原因,我们不应该使用finalize()
方法机制来做垃圾回收前的清理工作:
有可能不进入垃圾回收过程,比如通过System.exit()
命令直接退出Java进程
在垃圾回收过程中,回收对象之前至多会调用1次对象的finalize()
方法,但也有可能不调用
3.5、内存泄漏 在Java中,也有可能出现内存泄漏的情形,比如在本地方法中分配的内存(例如C/C++语言实现的本地方法中使用malloc
方法分配内存)。
3.6、数组初始化 数组初始化有3种形式,需要注意的是,第1种只能用在数组定义处。分别以“int[]”和“String[]”数组为例进行说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package chapter5;public class ArrayInitialize { public static void main (String[] args) { int [] a = {1 , 2 , 3 }; int [] aa = new int []{1 , 2 , 3 }; int [] aaa = new int [3 ]; aaa[0 ] = 1 ; aaa[1 ] = 2 ; aaa[2 ] = 3 ; String[] b = {"hello" , "world" }; String[] bb = new String []{"hello" , "world" }; String[] bbb = new String [2 ]; bbb[0 ] = "hello" ; bbb[1 ] = "world" ; } }
备注: 1、数组初始化后,数组元素的默认值遵从Java编程思想#一切都是对象 中的表2
3.7、可变参数列表 自从JDK 1.5开始,Java引入了对可变参数列表的支持,可变参数列表本质上还是一个数组,因此适用于“foreach语法”,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package chapter5;public class VarArg { public void f (int ... a) { for (int aa : a) { System.out.println(aa); } } public void g (Object... b) { for (Object bb : b) { System.out.println(bb); } } }
3.8、方法实现体中的变量不会进行默认初始化 方法实现体中的变量不会像类中的字段(包括普通和静态字段)一样进行默认初始化。编译过程会因为存在未被初始化的变量而失败。示例代码如下:
1 2 3 4 5 6 7 8 9 10 package chapter5;public class MethodVariableInitialization { void f () { int b; System.out.println(b); } }
3.9、失效的“向前引用” ###根据Java编程思想#一切都是对象 可知,Java解决了“向前引用”的问题,即在某处可使用在其后定义的资源。 但是现在有如下一段代码:
1 2 3 4 5 6 7 8 9 10 11 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;
语句互换位置,编译器就不会报错) 又有以下一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Main { int a = g(); int g () { return c; } public static void main (String[] args) { Main main = new Main (); System.out.println(main.a); } int c = 10 ; }
该段代码编译能够通过,但是从严格意义上来,如果按照上述思路,那么int a = g();
语句也是一个非法的向前引用。 综上,Java关于这个问题的语法糖真是不伦不类。