this关键字
如果为this添加了参数列表,那么就有了不同的含义。这将产生对符合此参数列表的某个构造器的明确调用。
public class Flower {
Flower(String ss)
{
// ...
}
Flower(int petals)
{
// ...
}
Flower(String s, int petals)
{
this(petals); //Flower(int petals)
//! this(s); //只能调用一个
}
}
析构函数与finalize()方法
不要把finalize()方法当作析构函数来用
析构函数 | finalize() | |
---|---|---|
是否一定执行 | 对象销毁时一定执行 | 对象销毁不一定是通过GC,只有通过GC销毁对象时执行 |
执行哪些内容 | 包含内存回收及其它销毁前要做的清理工作 | 只用于回收内存,不包括其它(业务相关) |
什么时候执行 | 对象生成周期结束时 | GC要销毁对象时,GC不会在对象生命周期结束时马上销毁它 |
C++(析构函数) = JAVA(内存清理 + 其它清理)
| |
GC完成 明确调用某个JAVA方法,不能使用finalize()
既然finalize()只能用于清理内存,而清理内存又由GC自动完成,那么finalize()有什么用?
- 以非JAVA方式申请的内存,需要通过finalize()来释放
- 检测对象是否被释放,举例:
class Book {
boolean checkedOut = false;
Book(boolean checkOut)
{
checkedOut = checkOut;
}
void checkIn()
{
checkedOut = false;
}
protected void finalize()
{
if(checkedOut)
{
System.out.println( "Error: checked out" );
// Normally, you'll also do this:
// super.finalize();
}
}
}
public class TerminationCondition {
public static void main( String[] args )
{
Book novelBook = new Book( true );
// Proper cleanup:
novelBook.checkIn();
// Drop the reference, forget to clean up:
new Book( true );
// Force gargabe collection & finalization:
System.gc();
}
}
所有的Book对象在被当作垃圾回收前都应该被签入,但由于程序员的错误,有一本书未被签入,要是没有finalize()来验证终结条件,将很难发现这种缺陷。
JVM的GC
对于其它语言,堆上分配对象的代码十分高昂。
对于JAVA,由于GC的作用,使得堆分配的速度可以与其它语言栈分配的速度相媲美。
- 为什么空间释放会影响到空间分配的速度? 因为GC在回收空间的同时,还会使堆中的对象紧凑排列。
- 垃圾收集方式:引用计数法? 引用计数法由于效率较低,且不能处理循环引用的问题,因此并没有实际应用于JVM中。
GC的工作方法
主要流程:
- 根据算法1追踪到所有的对象,剩下的就是要回收的空间
- 根据算法2决定使算法3还是算法4回收空间
- 算法3/算法4
算法1:追踪“活”的对象
对于任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可以穿过数个对象层次。
1.从堆栈和静态存储区开始
2.遍历所有引用,找到所有“活”的对象
3.对于发现的每个引用,进一步追踪它所引用的对象,如此反复
算法2:自适应技术
JVM会监视系统状态,如果所有对象都稳定,垃圾回收器的效率降低的话,就切换到算法4。
同样,JVM会跟踪算法4的效果,要是堆空间出现很多碎片,就会切换到算法3.
算法3:停止-复制
1.暂停程序的运行
2.把所有活着的对象从当前堆复制到另一个堆
3.搬的同时,修正指向它的引用
优点:新堆保持紧凑排列
缺点:1.需要两个堆,空间2.稳定后垃圾少,大量的复制是浪费的
改进:内存分配以“块”为单位,小的对象被复制并整理,大的对象不会被复制
算法4:标记-清扫
找到活的对象时仅作标记
标志完成才开始清理
未标记的对象会被释放
优点:不需要复制
缺点:产生内存碎片
初始化
在不同情况下使用未初始化的变量会有不同的结果。
对于未初始化的局部变量,使用时会报错,因为JAVA认为未初始化的局部变量更有可能是程序员的疏忽,所以采用默认值反而会掩盖这种失误。
对于未初始化的类成员变量,系统会给个默认值0。
自动初始化
在定义类成员变量的地方为其赋值
public class initValue {
boolean bool = true;
char ch = 'x';
int i; //没有指定初值则初始化为0
}
各种初始化的顺序
静态成员的初始化
自动初始化(以定义为顺序)(即使散布于方法定义之间)。
构造函数初始化
初始化块
public class Spoon {
static int i;
//静态块
static {
i = 47;
}
//非静态块
int j;
{
j = 48;
print("j is inited");
}
}
初始化块也在构造函数之前执行
数组初始化
- 声明的两种方式
int[] a; //a只是个引用
int a2[];
编译器不允许指定数组的大小。
要给数组创建空间,必须写初始化表达式。
int [] a = new int[5];
int [] a = new int[](1, 3, 4, 5,); //最后一个,可写可不写
可变参数
声明带可变参数的函数
void func1(Object[] args) //以前的写法
void func2(Object[]... args)
void func3(int required, String... trailing) //当具有可选的尾随参数时,这一特性会很有用
//重载
void func(Character... args)
void func(Integer... args)
使用带可变参数的函数
//常规调用方法
func1(new Object[]{"1", "2", "3"});
func2(new Object[]{"1", "2", "3"});
//自动转型为数组
func3(1, "1");
func3(0);
//当没有参数时,使用重载的可变参函数会有歧义
func();
只在重载方法的一个版本上使用可变参数列表,或者压根不用它。
自动包装机制