Chapter 5 Initialize and Cleanup

Chapter 5 Initialize and Cleanup

不安全的编程方式已逐渐成为编程代价高昂的主因之一,初始化(initialize)和清理(cleanup)正是
涉及编程安全的两个关键问题。未初始化的元素会引起各种问题,用完元素一直不清理则会引起资源用尽
的问题。

5.1 Use Constructors to ensure intialization

每个类都应该有一个初始化方法initilize(),提醒调用者在使用之前先调用该方法进行初始化,但这依赖于调用者的主动调用。

在Java中,通过构造器,确保用户有能力操作对象之前自动调用完毕构造器,保证了初始化。

为了确保构造方法不重名,且能够被编译器识别调用,采用c++语言的解决方法:构造器与类具有有相同的名称。

new 操作在创建对象,为对象分配空间时,将调用相应的构造器,
“初始化”和“创建”捆绑在一起,两者不能分离。

Java通过定义与类同名的构造函数,在new对象时自动调用,以确保对象的初始化。

5.2 Method Overloading

人类语言具有很强的"冗余"性,同样的动词会因为动词对象的不同而表达不同的动作。
Java中也一样,参数和方法名构成了方法标签,同样的方法名不同的参数可以视为不同的方法。

基本类型作为参数列表时,传入的参数首先选择完全符合的方法,若找不到则进行类型提升,只有主动进行类型转换才能降低。

5.3 Default Constructor

若类没有显示定义构造器,则编译器会自动创建一个无参默认构造器;若定义了构造器,编译器就不会创建了。

5.4 This

编译器把所操作对象的引用作为第一个参数this传给类的方法,所以在方法内部能够通过this调用其他属性或方法,
并且this可以省略,编译器会首先认为变量或方法是类的。

this也能用来表示构造器,但只能处于构造器的第一行。

总结:

  1. 在对象方法内部使用代表对象的引用。
  2. 在构造器中表示对象的另一个构造方法(不能循环引用)。

static方法就是没有this的方法,可以在没有创建对象的前提下调用方法,类似于全局函数。

5.5 finalize

finalize()会在对象被GC回收的时候执行,不能作为清理对象的方法。

因为:

  • Java中对象可能不被垃圾回收(内存充足的话)
  • 垃圾回收不等于"折构"
  • 垃圾回收应该只与内存有关

finalize主要用来提供给本地方法(Java调用其他语言)使用来回收内存。

Java中对象的清理一般是不可预料的,由GC来

5.6 GC

Java使用GC(垃圾回收器)来进行内存(主要是堆)的释放。

Java的GC一边回收内存,一边使堆中的对象紧凑排列,已获得更高的分配内存的速度。

GC的任务:

  • 快速地回收内存
  • 重排对象,提高分配内存的速度
  • 减少GC操作的消耗

引用计数法

每个对象都含有一个引用计数器,当被引用时,计数+1;当离开作用域或被置为null时,计数减1。
垃圾回收器会在整个程序生命周期中,对含有全部对象的列表进行遍历,当发现对象引用计数为0就释放空间。

  • 简单,但速度慢。
  • 定位循环引用的情况消耗大。
  • 用来说明垃圾回收,没有被应用于任何一种虚拟机中。

引用网络

思想:还处于工作中的对象的引用构成了"活"的对象引用链,遍历全部引用构成了"活"的对象的网络。
没有处于在网络中的对象就应该被回收,这就解决了循环引用的问题。

停止-复制

将堆分成两块,GC时先暂停程序的运行,从一个活的对象开始,遍历整个堆栈或静态存储区的引用作为应该存活的对象网络。然后将这些对象复制到另一个堆上,重新排列保持紧凑,修正所有对象的引用(映射为新地址)。余下的对象就是垃圾。

  • 基于引用网络,解决了循环引用问题。
  • 复制时会重新排列对象,使得分配内存的效率提高。
  • 维护两个堆的消耗增加,且就算没有垃圾,也会进行复制的操作。

标记-清扫

GC时先暂停程序的运行,从一个活的对象开始,遍历整个堆栈或静态存储区的引用,并标记每个对象。
遍历标记完成后,清理没有被标记的对象。

  • 基于引用网络,解决了循环引用问题。
  • 没有复制操作。
  • 标记清扫后剩下的堆空间是不连续的,需要重新整理。

分代回收

停止-复制方法导致了大量内存复制,特别是大对象的复制降低了程序的效率。
在Java中,对象生命周期处于不均匀的状态,一部分对象存活时间很短,一部分对象将一直存活,这就引入了分代的思想。
分代回收将内存分为不同的代,不同的代GC的频率不同。每次停止-复制时,存活下来的对象的代数将+1,代数越大的对象越被认为是持久对象,进行复制的频率将会降低。而代数低的对象将会被更快的清理,释放空间。

5.7 成员初始化

Java 尽力保证所有变量在使用前都能得到其恰当的初始化。

  • 对于局部变量,编译器要求变量使用前必须进行初始化。
  • 对于基本数据类型的类成员变量,会被自动赋于一个初始值。
  • 对于对象引用的类成员变量,会获得特殊值null
类型
char '\u0000'
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0
boolean false
Object null

自动初始化:

类型
char '\u0000'
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0
boolean false
Object null

初始化顺序

类的初始化顺序(同级按从上到下的顺序):

  1. 父类静态成员自动初始化
  2. 父类静态初始块、父类静态成员指定初始化
  3. 子类静态成员自动初始化
  4. 子类静态初始块、类静态成员指定初始化
  5. 父类初始块、父类成员变量指定初始化
  6. 父类构造函数(若没有在构造器里super指定,调用无参构造函数)
  7. 子类初始块、子类成员变量指定初始化
  8. 子类构造函数

静态初始化

静态成员的指定初始化和静态初始块会在类第一次使用时执行一次,包括new和调用静态成员或方法。

对象创建过程

创建过程:

  1. 创建类的对象或访问类的静态方法/静态域时,解释器查找类路径,定位class文件。
  2. 载入class文件,创建对应的Class对象,执行静态初始化动作。
  3. 在堆上为对象分配空间,并将空间数值置为0,即自动初始化。、
  4. 执行指定初始化动作。
  5. 执行构造函数

5.8 数组初始化

数组只是一个相同类型、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。

// 初始化表达式
int[] a = {1,2,3};

// 进行自动初始化
int[] b = new int[3];

// 另一种初始化写法
Integer[] c = new Integer[]{
  new Integer(1),
  new Integer(2),
  3
}

可变参数列表

通过数组能够传递可变数量的方法参数

void printArray(Object[] args){
  for(Object obj:args){
    System.out.println(obj+"");
  }
}
// 需要传递数组
printArray(new Object[]{"one",1});

在JAVA5之后,可变参数列表特性添加了进来。

  • 调用时会先寻找固定参数的方法,然后才是可变参数方法。
  • 可变参数只能位于一般参数后
  • 方法寻找顺序:恰好>自动提升>包装>可变参数
void printArray(Object... args){
  for(Object obj:args){
    System.out.println(obj+"");
  }
}
// 编译器会创建数组传递
printArray("one",1);

5.9 枚举

  • enum关键字在Java5中添加。
  • enmu实际是实现了Enum的类,包含了name和ordinal两个实例域和一些方法。
  • 枚举类型具有有限的实例(具名值),即实例都为常量。
  • 枚举可以在switch语句中使用。
public abstract class Enum>
        implements Comparable, Serializable {
          private final String name;
          private final int ordinal;
}

5.10 总结

  • 再总结了C++由于初始化问题中产生的居多问题后,Java使用一种精巧的初始化机制————构造器来确保正确的初始化。
  • 在Java中,由垃圾回收器来自动为对象释放内存,极大地简化了编程工作。
  • 垃圾回收器增加了运行时的开销,使得垃圾回收器的实现一直在优化,以提升它的效率和速度。

你可能感兴趣的:(Chapter 5 Initialize and Cleanup)