11 December 2013

构造器

Java尽管没有像C++引入主动回收机制,但其仍然提供了类似的构造器机制。Java在创建对象时,如果其类具有构造器,就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。构造器采用了与类相同的名称。比如:

class Apple {
    Apple() {
        System.out.println("Apple()");     
    }
}

上面提供的例子叫做默认构造器,其不提供任何的参数。构造器比较特殊,不提供任何的返回值,而且不同于返回值为void的情况,对于空返回值,尽管方法本身不会返回什么,但仍可以选择让它返回别的东西。在许多面向对象的编程语言中,都为构造器提供了重载功能:

class Apple {
    Apple() {
        System.out.println("Apple()");     
    }
    Apple(int i) {
        System.out.println("Apple()");     
    }
    Apple(Srting s) {
        System.out.println("Apple()");     
    }
}

当创建该类时,根据不同的参数,Java可以调用对应的构造器。所以,每个重载的方法必须具有独一无二的参数类型列表。当然,顺序不同可以区分两个方法,但这无疑使代码难以维护(参数可以顺序不同,但代码完全一样)。对于没有找到对应的构造器(重载方法)时,实际参数若小于重载方法的形式参数,则采取提升类型。如果小于,则进行窄化转换,这无疑会使数据丢失一部分,要谨慎对待。如果类中没有提供构造器,编译器会创建一个默认构造器,如果有,默认编译器则不会创建。

这个例子也很有趣:

class Apple {
    Apple(Object obj) {
        System.out.println("Apple()" + obj);     
    }   
}

即向上转型的用法,无论是int还是double或String,都会转型到Object,并进行构造。

this关键字

我觉得书中对this关键字的解释很好,指暗自将“所操作对象的引用”作为第一个参数传递给方法。比如:

Obj a = new Obj();
Obj b = new Obj();

a.func(1);
b.func(2);

Obj.func(a, 1);
Obj.func(b, 2);

最后两行代码即前面两行代码的内部实现,当然不能这么写,但却是很好理解。this有很多用法,比如返回当前对象的引用:

public class Apple {
    int i;
    Apple func() {
        i++;
        return this;
    }
}

也可以将当前对象传递给其他方法:

class Peeler {
    static Apple peel(Apple apple) {
        return apple;
    }
}
class Apple {
    Apple getPeeled() {
        return Peeler.peel(this);
    }
}

由于Peeler的peel方法是静态的,故可以直接调用。

this也可以使用在,一个构造器调用另一个构造器等等。我们再回过头来看看static关键字,它实际上就定义了一个没有this关键字的静态方法,正因为没有所操作对象的引用,所以可以直接使用,而不用操作引用。

清理

Java尽管提供了垃圾回收器来自动回收垃圾,但其还提供了finalize()方法,在垃圾回收期准备释放内存时,会先调用finalize()。这里需要注意:

  • 对象可能不被垃圾回收。
  • 垃圾回收并不等于“析构”。
  • 垃圾回收只与内存有关。

比如书中举的这个例子,假设某个对象创建过程中会将自己绘制到屏幕上,如果不是明确的从屏幕上擦除,则其可能永远得不到擦除,故在finalize()中加入了擦除功能,当垃圾回收器回收该对象的内存时,调用finalize(),擦除图像。也就是说,finalize()方法只是处理一些其他的清除工作(非Java分配内存的部分),而不是该对象分配的内存(这部分由垃圾回收器清除,除非引用类似C中动态分配内存的方法,这时可以使用finalize)。通常,不能指望finalize(),必须创建其他的“清理”方法,并且明确的调用它们。之所以这么说,因为finalize()只能在垃圾回收器准备释放内存时才会被调用,但垃圾回收器什么时候才会释放内存呢?它不一定会发生。所以,如果我们只是等待其准备回收时再清除屏幕(上例)显然是不够的,而应该创建其他的清除的方法,并在必要时显示调用。对于Java虚拟机,如果内存还没有消耗尽,它是不会浪费时间去执行垃圾回收以恢复内存的。

我们可以调用:

System.gc()

来强制进行终结动作(也不是每次都能管用),即使不这么做,通过重复地执行程序(内存耗尽),最终也能实现垃圾回收。

尽管如此,垃圾回收器对于提高对象创建的速度,有明显的效果。Java垃圾回收器采用一种自适应的方法,有两种模式,其一是“停止-复制”。其先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以保持了紧凑排列,节省了空间。当然,这种模型下,效率会降低。原因有二,一方面,来回复制比较低效。另一方面,当进入稳定状态后,可能只会产生很少的垃圾,停止-复制很浪费。

所以,Java垃圾回收器还有两外一种模式:标记-清扫。其先遍历所有的引用,进而找出所有存活的对象。每当找到一个存活的对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成时,清理动作才会开始。清理时,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,如希望得到连续空间,则需要重新整理剩下的对象。

Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫”模式。如果堆空间出现很多碎片,就切换到“停止-复制”模式,这就是它的自适应技术。



blog comments powered by Disqus