5. 初始化与清理
5.1 使用构造器确保初始化
-
命名规范:使用与类相同的名称
- 原因解析:第一,名称可能与类中的成员名称冲突;第二,调用构造器是编译器任务,需要让编译器明确识别
- 注意:由于构造器名称需要和类名相同,因此“每个方法首字母小写”的规则不适用于构造器
-
分类
- 默认构造器(无参构造器)
- 有参构造器
-
注意点
- 只有在创建对象时,会为对象分配存储空间,同时调用相应的构造器
- 无参构造器是特殊类型的方法,没有返回值,与返回值为 void 不同;void 类型方法可以修改返回类型,比如将 void 修改为 int,但是构造器没有办法修改
5.2 方法重载
-
构造器重载
-
规则
- 名称相同
- 参数不同,类型或者数量
-
<!– 比如,树作为类,有树苗和百年古树,百年古树则使用有参的构造方法,参数为树高;树苗则使用无参的构造方法,因为一般没有高度属性 –>
5.2.1 区分重载的方法
- 方法:每个重载的方法都有独有的参数类型列表
-
注意点
- 参数相同但是顺序不同虽然能区分,但是易读性差
5.2.2 基本类型的重载
-
规则
-
如果传入的参数类型大于重载方法声明的类型,则窄化转换
public class TestOverride {void f1(int f){System.out.println(getType(f)); System.out.println(f); } public static String getType(Object object){String typeName=object.getClass().getName(); int length= typeName.lastIndexOf("."); String type =typeName.substring(length+1); return type; } public static void main(String[] args) {TestOverride testOverride = new TestOverride(); testOverride.f1(100); // 窄化转换 testOverride.f1((int) 3000.08); } }/* output Integer 100 Integer 3000 */
-
如果传入的参数类型小于重载方法声明的类型,则提升数据类型
public class TestOverride {void f1(double f){System.out.println(getType(f)); System.out.println(f); } public static String getType(Object object){String typeName=object.getClass().getName(); int length= typeName.lastIndexOf("."); String type =typeName.substring(length+1); return type; } public static void main(String[] args) {TestOverride testOverride = new TestOverride(); testOverride.f1(100); testOverride.f1(3000.08); } }/* output Double 100.0 Double 3000.08 */
-
5.2.3 以返回值区分重载方法
无法使用,原因是如果不获取返回值,仅仅使用 f() 方法,是无法区分的
5.3 默认构造器
-
规则
- 类中缺少构造器,则编译器将自动创建默认构造器,且为无参构造器
- 类中已经定义构造器,则编译器不会创建 <!– 比如只定义了一个有参构造,则使用无参构造就会报错 –>
5.4 This 关键字
-
用途
- 表示对“调用方法的那个对象”的引用,就是当前类的对象的引用
- 能够将当前对象传递给其他方法
-
规则
- 只能在方法内部使用
- 如果在方法内调用一个类中的方法,则不需要 this 关键字
5.4.1 在构造器中调用构造器
-
规则
- 能够在构造器中调用构造器
- 不能调用两个
- 构造器调用需要置于最起始位置
-
Test
class TestThis { String s = "initial value"; int i; // 有参构造器 TestThis(String s,int i){System.out.println("Build s and i"); } // 无参构造器使用 this 调用 TestThis(){this("This",32); } public static void main(String[] args) {TestThis testThis = new TestThis(); } }
5.4.2 static 含义
- 用途
实现不创建对象的前提下,通过类本身来调用 static 方法
-
规则
- static 中不能使用 this 方法,原因是 Static 方法中不能调用非静态方法
5.5 清理:终结处理和垃圾回收
-
Finalize 工作原理
- 当垃圾回收器准备释放对象占用的存储空间,首先调用 finalize 方法
- 在垃圾回收动作发生时,真正回收对象占用的内存
-
注意点
- 对象可能不被垃圾回收
- 垃圾回收不等于析构 <!–C++ 中销毁对象必须用到的函数 –>
-
回收流程
- 可达状态:对象创建以后为可达状态
- 可恢复状态:当失去引用则为可恢复状态,在这个状态下,垃圾回收器会调用 finalize 方法,如果重新获得引用将成为可达状态
- 不可达状态:当彻底失去引用,同时已经执行了 finalize 方法且没有进入可达状态,则永久失去引用
5.5.1 finalize 的用途
垃圾回收只与内存有关,java 虚拟机未出现内存耗尽的情况时,不会浪费内存来执行垃圾回收
finalize 不是进行清理的合理方法,finalize 调用存在不确定性,可能还未执行就已经被回收
在 JDK9 中,finalize 已经标记为过时
5.5.4 垃圾回收器工作原理
1. 引用计数回收
- 原理:在对象头部存储引用的数量,当引用数为零时,就释放空间
- 优点
- 实时性,无需等待内存不足后处理,引用数为零即释放
-
缺点
-
当循环引用时,始终无法释放内存
<!–ab 对象皆为 null,但是由于循环引用,无法实现垃圾回收 –>
Class TestA{public TestB b;} Class TestB{public TestA a;} public static void main(String[] args){TestA a = new TestA(); TestB b = new TestB(); a.b = b; b.a = a; a = null; b = null; }
-
2. 自适应垃圾回收
- 流程
停止 - 复制——标记 - 清扫
-
缺点
- 需要占用内容,复制对象
5.6 成员初始化
-
规则
- 基本类型的数据,有初始值
- 类中的对象引用,初始值为 null
5.6.1 指定初始化
-
方法
- 基本类型直接赋值
- 新建类对象
- 调用方法提供初值
5.7 构造器初始化
自动初始化将在构造器被调用之前发生,i 先置为 0,然后赋值为 7
Class Counter{
int i;
Counter(){i = 7;}
}
5.7.2 对象创建过程
- 构造器是类的静态方法,当创建对象时,定位.class 文件
- 载入.class 文件,静态类型动作都将执行
- 使用 new 创建对象时,首先在堆上分配存储空间
- 对象中的所有基本类型设置为默认值,引用为 null
- 执行所有出现于字段定义处的初始化动作
- 执行构造器
5.7.3 显式的静态初始化
-
静态初始化时间
- 首次生成这个类的对象
- 首次访问这个类的静态数据成员
public class Car { // 两个静态字符串域,一个在定义处初始化,另一个在静态块中初始化 static String string1 = "defInBegin"; static String string2; static { string2 = "defInStatic"; print("显式的静态初始化"); } static void printMed(){print(string1); print(string2); } Car(){print("Car()"); } public static void main(String[] args) { // 首次生成对象 new Car();} }/* output 显式的静态初始化 Car() */
5.7.4 非静态实例初始化
-
与静态初始化区别
- 静态初始化仅执行一次,非静态实例初始化可执行多次
- 静态初始化执行早于非静态实例初始化,在构造方法之前
5.8 数组初始化
- 定义:相同类型的、用一个标识符名称封装到一起的一个对象序列或者是基本类型数据序列
-
使用方法
// 定义 int[] i; // 固定成员 i.length; // 数组大小 // 初始化时定义 int[] a = new int[10]; // 由随机数决定 Random rand = new Random(57); int[] b = new int[rand.nextInt(20)]; // 初始化 // 方法一 a[i] = rand.nextInt(20); // 方法二 Integer[] a = {new Integer(1), new Integer(2), 3, }; // 方法三 Integer[] b = new Integer[]{new Integer(1), new Integer(2), 3, }
-
注意
创建类的对象引用数组,并不会调用类的构造方法,原因是仅实例化了数组,并没有实例化类
public class Book {Book(String s){print("Initial"); print(s); } public static void main(String[] args) {Book[] books = new Book[10]; } }/* output 为空,因为并未实例化类,仅仅实例化了数组 */
5.8.1 可变参数列表
-
方法
- 方法一:创建以 Object 数组为参数的方法,可以应用于参数个数或者类型未知的情况
- 方法二:使用三个点来创建可变参数列表,可以应用于类型已知、数量未知
-
实现
public class TestObjectArray {static void printArray(Object[] args){for (Object s : args){System.out.println(s); } } // 方法二 static void f(int... args){System.out.println(args.length); } public static void main(String[] args) { // 方法一 printArray(new Object[]{new Integer(35),new Double(23.34),new String("dsaf") }); f(2,4,6,4); } }/* output 35 23.34 dsaf 4 */
5.9 枚举类型
- enum 关键字:声明一种整型常量的集合 <!– 一个星期有七天,则可定义为枚举类型 –>
-
使用方法
public enum MoneyValue{ONE, FIVE, TEN, TWENTY, FIFTY, HUNDRED;}
- 一个名为 MoneyValue 的枚举类型,其中有六个具名值,都是常量
- 通过.ordinal() 方法输出声明顺序
- 通过.value() 方法,以数组形式返回枚举类型的所有成员
-
enum 和 switch 搭配使用
package TwentyTwo; public class Money { public enum MoneyValue{ONE, FIVE, TEN, TWENTY, FIFTY, HUNDRED;} public static void main(String[] args) {for (MoneyValue mv: MoneyValue.values() ) {switch (mv){ case ONE: System.out.println("This is smallest"); System.out.println(mv.ordinal()); break; case FIVE: System.out.println("This is second"); break; case TEN: System.out.println("This is third"); break; case TWENTY: System.out.println("This is forth"); break; case FIFTY: System.out.println("This is fifth"); break; case HUNDRED: System.out.println("This is largest"); break; } } } }
-
枚举和数组的区别
- 数组是相同类型的多个数据项组成的集合;枚举是新类型,允许用常量来表示特定的数据片断
-
在未加入枚举之前,常量合集创建方式;加入后的创建方式,枚举更加简单安全
public class Test { public static final int A = 1; public static final int B = 2; public static final int C = 3; public static final int D = 4; public static final int E = 5; }
public class Test { public enum Grade{A,B,C,D,E;}; }