【Java编程思想】5.初始化与清理
构造器就是在创建对象的时候被自动调用的特殊方法。
Java 在提供构造器的基础上,额外提供“垃圾回收器”。控制对象从生命周期开始到结束。
5.1 用构造器确保初始化
创建对象时,Java 会在用户有能力操作对象之前自动调用构造器,保证初始化进行。
5.2 方法重载
方法重载的存在,一方面是方法上的需要,另一方面也是为了可以定义多种构造器而存在。
重载的方法必须拥有独一无二的参数类型列表。
当方法重载中的参数牵扯到基本类型时,
- 常数5会被当做 int 处理,同理其他常量也是一致的。
- 如果传入数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。
- 如果传入的实际参数大于重载方法声明的形式参数,就得通过类型转换执行窄化转换。
不要以返回值区分重载方法—>因为在调用的时候不能指定类型信息,编译器不知道要调用哪个函数。虽然结合上下文有可能去判断,但是在忽略返回值的调用中还是不能判断的。
5.3 默认构造器
当一个类没有定义任何构造器时,在创建这个类的对象时,会调用其默认构造器。
但是当定义了有参构造器时,除非重新声明无参构造器,否则不能调用默认构造器去创建对象。
5.4 this 关键字
this 只能在方法内部使用,标识对“调用方法的那个对象”的引用。
return this // 当需要返回对当前对象的引用时,可以这样使用
同一个类中,在一个构造器中调用另一个构造器也可以使用 this,不过不能调用两个。同时必须将构造器置于起始处,否则编译器会报错。
static 方法就是没有 this 的方法。在 static 方法内部不能调用非静态方法。
static 的主要用途就是在没有创建任何对象的前提下,仅仅通过类本身来调用 static 方法。从语义上讲,这是“全局方法”的一种实现(Java 中禁止使用全局方法)。
5.5 清理:终结处理和垃圾回收
GC 三大原则:
1.对象可能不被垃圾回收
2.垃圾回收并不等于“析构”
3.垃圾回收只与内存有关
创建了足够多的对象之后,内存耗尽,其中的秘密在于垃圾回收期的介入,Java 虚拟机采用一种自适应的垃圾回收技术
前提:
对任何活的对象,一定能最终追述到其存货在堆栈或静态存储区中的引用。从堆栈和静态存储区开始遍历所有引用,并追踪其所引用的对象,反复进行直到根源于堆栈和静态存储区的引用所形成的网络全部被访问位置----交互自引用的对象组不会被发现,就被自动回收了
自适应的垃圾回收技术-处理找到的存活对象:
- 停止-复制:暂停程序运行,将所有存活对象从当前堆复制到另一个堆,没有被复制的全部是垃圾。对象在新堆中一个爱着一个,保持紧凑排列
- 标记-清扫:没有新垃圾或很少有垃圾产生时,从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存货的对象。每找到一个就为其设置一个标记,在全部标记工作完成后开始清理。清理过程中没有标记的对象会被释放,不会发生任何复制操作。--如此剩下不连续的堆需要在此进行“停止-复制”垃圾回收
内存分配以较大的“块”为单位,对象较大的时候,会占用单独的块。每个块都用相应的“代数(genenration count)
”来记录其是否存活----如果块在某处被引用,其代数会增加;GC 将对上次回收动作之后新分配的块进行整理。如此这般,GC 就会定期进行完整的清理动作,大型对象仍然不会被复制,只是其代数会增加(严格来说,这是区别于“停止-复制”的要求的,节省了大量复制大型对象到新堆的时间),内含小型对象的块会被复制并整理。
总的来说,JVM 会进行监视,对象都很稳定,GC 效率降低,会切换到“标记-清扫”模式;JVM 会跟踪“标记-清扫”的效果,在堆空间出现很多碎片后会切换回“停止-复制”模式。这就是“自适应技术”
5.7 构造器初始化
类的没个基本类型数据成员保证都会有一个初始值
初始化的顺序是先静态对象(如果他们尚未因前面的对象创建过程而初始化),而后是非静态对象
总结一下对象的创建过程,以名为 Dog 的类举例:
- 即使没有显式的使用 static 关键字,构造器实际上也是静态方法。因此,当首次创建类型为 Dog 的对象时,或者 Dog 类的静态方法或静态域首次被访问时,java 解释器都会查找类路径,定位 Dog.class 文件
- 载入 Dog.class,有关静态初始化的所有动作都会执行---静态初始化只在 class 对象首次加载的时候进行一次
- 当用 new Dog() 创建对象的时候,首先将在堆上为 Dog 对象分配足够的存储空间
- 这块存储空间会被清零,这就自动的将 Dog 对象中的所有基本类型数据都设置成了默认值,而引用被设置成了 null
- 执行所有出现于字段定义处的初始化动作
- 执行构造器
5.8 数组初始化
编译器不允许指定数组的大小——就是说,在初始化数组后,我们拥有的只是对数组的一个引用,并没有给数组对象本身分配任何空间。
为了给数据分配存储空间,必须写初始化表达式。
int[] a1 = {} //{}->存储空间的分配(等价于使用 new)将由编译器负责
可以直接使用 new 在数组里创建元素(即使是基本类型数组,new 依然可以工作;但是不能用 new 创建单个的基本类型数据)
int[] a = new int[20] //数组元素中的基本数据类型会自动初始化成空值(数字和字符是0,布尔是 false)
如果创建了一个非基本类型的数组(即对象数组),那么就创建了一个引用数组
Integer[] a = new Integer[20] // 此时 a 还只是引用数组,直到通过创建新的 Integer 对象,并把对象赋值给引用,初始化进程才算结束,如果忘记创建对象并试图使用数组中的空引用,会在运行时产生异常
可以使用数组作为参数,获得可变参数列表
的效果。
由于所有的类都直接或者间接继承于 Object 类,所以可以创建以 Object 数组为参数的方法。
//Java SE5以前:
function(Object[] args){} // 方法定义
function(new Integer(13), new Integer(1)); //调用方法
//Java SE5以后:
function(Object... args){} // 方法定义
function(13, 1); //调用方法
有了可变参数,就不需要再显式的编写数组语法了,当指定参数时,编译器会主动填充数组。同时此种方式也支持传入空数组作为参数传递给可变参数列表。
可变参数列表中,可以将不同类型混合在一起,自动包装机制会有选择的将 int 参数提升为 Integer。
在可变参数列表参与重载的过程中,编译器会使用自动包装机制来匹配重载的方法,最后调用最明配匹配的方法。但是在不使用参数调用 f()
时,编译器就无法知道应该调用哪一个方法了(针对使用可变参数列表的方法而言)。
实际可以在方法中增加一个非可变参数来解决改问题。但是原则上,应该只在重载方法的一个版本上使用可变参数列表,或者根本不使用这种方式。
5.9 枚举类型
枚举 enum 也是类的一种,可以和 switch 一起使用。
使用 print 可以打印出枚举值。