Java编程思想笔记——初始化与清理

用构造器确保初始化

构造器(constructor),在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化,类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造器。
调用构造器是编译器的责任。Java中构造器采用和类相同的名称。

public class Rock {
    Rock(){
        System.out.println("Rock");
    }
    Rock(int i){
        System.out.println("Rock:i=" + i);
    }
}

public class Test {
    public static void main(String[] args){
        for(int i = 0;i < 10;i++){
            new Rock();
        }
        new Rock(1);
    }
}

在创建对象时:new Rock();将会为对象分配存储空间,并调用相应的构造器,确保在操作对象之前,它已经被恰当地初始化。
不接受任何参数的构造器叫做默认构造器或无参构造器。
如果Rock(int i)是Rock类中唯一的构造器,编译器将不会允许你以任何方式创建Rock对象。

构造器是一个特殊类型的方法,因为他没有返回值,这与返回值为空明显不同。

方法重载

构造器名由类名决定,就只能有一个构造器,那想用多种方式创建一个对象(方法名相同而形式参数不同),必须用到方法重载

public class Tree {
    int height;
    Tree(){
        System.out.println("Planting a seedling");
        height = 0;
    }
    Tree(int initialHeight){
        height = initialHeight;
        System.out.println("Creating new Tree that is " + height + " feet tall");
    }
    void info(){
        System.out.println("Tree is " + height + " feet tall");
    }
    void info(String s){
        System.out.println(s + "Tree is " + height + " feet tall");
    }
}

public class Test {
    public static void main(String[] args){
        for(int i = 0;i < 10;i++){
            Tree t = new Tree(i);
            t.info();
            t.info("overloaded method");
        }
        new Tree();
    }
}

重载方法都必须有一个独一无二的参数类型列表,甚至参数顺序的不同也足以区分(一般别这么做)。以返回值来区分重载方法是行不通的(又是我们并不关心返回值,直接调用方法)。

默认构造器

如果所写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。但是,如果已经定义了一个构造器,编译器就不会帮你自动创建默认构造器。

public class Rock {
    Rock(int i){
        System.out.println("Rock:i=" + i);
    }
}

public class Test {
    public static void main(String[] args){
        new Rock();
    }
}

此时,new Rock()就会报错。

this关键字

public class Banana {
    void peel(){
        System.out.println("peel...");
    }
}

public class Test {
    public static void main(String[] args){
        Banana a = new Banana(),
               b = new Banana();
        a.peel();
        b.peel();
    }
}

这样我们并不能知道是a还是b调用了peel()方法,可以把“所操作对象的引用”作为第一个参数传递给peel():

Banana.peel(a,1);
Banana.peel(b,2);

这是内部的表达形式,我们并不能这样写代码。
我们现在希望在方法内部获得对当前对象的引用,为此有了专门的关键字:this,this只能在方法内部使用,表示对“调用方法的那个对象”的引用。但要注意,如果在方法内部调用同一个类的另一个方法,就不必使用this(有些人执意将this放在每个方法调用和字段引用前,认为这样更清楚更明确,千万别这么做,我们使用高级语言的原因之一就是他们能帮我们做一些事情。我们期望只在必要的地方使用this,如下例:)。

public class Banana {
    void peel(){
        System.out.println("peel...");
    }
    void eat(){
        peel();
        System.out.println("eat...");
    }
}

在eat()内部,可以写this.pick(),但没必要。编译器能帮你自动添加。只有当需要明确指出对当前对象的引用时,才需要使用this关键字。

public class Leaf {
    int i = 0;
    Leaf increment(){
        i++;
        return this;
    }

    public static void main(String[] args){
        Leaf l = new Leaf();
        System.out.println(l.increment().increment().increment().i);
    }
}//结果:3

其他用法:

public class Banana {
    Banana getPeeled(){
        return Peeler.peel(this);
    }
}
public class Peeler {
    static Banana peel(Banana banana){
        System.out.println("peel...");
        return banana;
    }
}
public class Person {
    public void eat(Banana banana){
        Banana peeled = banana.getPeeled();
        System.out.println("eat...");
        System.out.println("Yummy!!!");
    }

    public static void main(String[] args){
        new Person().eat(new Banana());
    }
}
在构造器中调用构造器

尽管可以用this点用一个构造器,但却不能调用两个,此外,必须将构造器调用至于最初始处,否则编译会报错。

static的含义

static方法就没有this的方法,在static方法的内部不能调用非静态方法,反过来是可以的,而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。它很像全局方法。java中禁止使用全局方法,但你在类中置入static方法就可以访问其他static方法和static域。

清理:终结处理和垃圾回收

由于垃圾回收器只释放由new分配的内存,并不知道释放一些“特殊”内存。为了应对这种情况,java允许在类中定义一个名为finalize()的方法。假定的工作原理:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。

垃圾回收只与内存有关。
使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行动来说(尤其是finalize方法),他们也必须同内存及其回收有关。
java一切皆对象,对象都会有垃圾回收进行内存释放,之所以有finalize(),是由于在分配内存时可能采用了类似c语言的做法,主要发生在使用“本地方法”的情况下,本地方法是一种在java调用非java代码的方式。

必须实施清理

无论是垃圾回收还是终结,都不保证一定会发生,如果JVM并未面临内存耗尽的情形,它不会浪费时间去执行垃圾回收以回复内存的。

终结条件

finalize()一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是终结条件的验证。当对某个对象不在感兴趣——也就是他可以被清理,这个对象应该处于某种状态,使它占用的内存可以被安全地释放。

public class Book {
    boolean checkedOut = false;
    Book(boolean checkOut){
        checkedOut = checkOut;
    }
    //签入
    void checkIn(){
        checkedOut = false;
    }

    @Override
    protected void finalize(){
        if(checkedOut){
            System.out.println("Error:checked out");
        }
    }

    public static void main(String[] args){
        Book novel = new Book(true);
        novel.checkIn();
        new Book(true);
        System.gc();
    }
}/*Output:
Error:checked out
*/

本例终结条件:所有book对象在被当最垃圾回收前都应该被签入,但是出现程序错误,有一本未签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。System.gc()强制进行终结动作。

垃圾回收器如何工作

Java的垃圾回收器对于提高对象的创建速度,具有明显的效果,也就是说存储空间的释放会影响到存储空间的分配,这确实是某些java虚拟机的工作方式。Java从堆分配空间的速度,可以和其他语言从堆栈中分配相媲美。
堆;c++院子——地盘 , 销毁——地盘重新利用
java 传送带,分配新对象,向前移动一格,堆指针只是简单的移动到尚未分配的区域,比得上C++在堆栈上分配空间的效率,这样显得会需要拥有比实际更多的内存,在创建了足够多的对象之后,内存资源将耗尽。但是其中的秘密在于垃圾回收器的介入:一方面回收空间,一面使堆中的对象紧凑排列。
通过垃圾回收器对对象重新排列,实现了一种高效、有无限空间可供分配的堆模型。

引用计数,简单很慢,每个对象都含有一个计数器,当引用连接到对象时,引用计数加1,当引用离开作用域或者被置为空,引用计数减1。在整个程序的生命周期都在持续发生,垃圾回收器会在含有所有对象的列表上遍历,将计数为0的对象释放其占用的空间(经常会在变为0时直接释放)。循环引用时存在缺陷,对象应该被回收,但引用计数却不为0。定位这样的交互自引用组所需的工作量极大,Java并未采用这种垃圾回收机制。

一些更快的模式思想:对于任何活的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历所有引用,就能找到所有活的对象。对于发现的每个引用,必须追踪它所有引用的对象,然后是此对象包含的所有引用,如此反复进行,知道根源于堆栈和静态存储区的引用所形成的网络全部被访问为止。

在这种方式下,java虚拟机将采用一种自适应的垃圾回收技术。处理存活对象的Java虚拟机实现:
停止-复制(stop-and-copy),暂停程序(所以不属于后台回收模式),将所有活的对象复制到另一个堆,没有复制的全是垃圾。当对象被复制到新堆,他们一个挨着一个,所以新堆保持紧凑排列。当把对象从一处搬到另一处时,所有指向他的引用都必须修正。位于堆或静态存储区的引用可直接被修正,但可能还有其他指向这些对象的引用,它们在遍历过程中才能被找到。
这种复制式回收器,效率会降低,有两个原因:
1、两个堆,维护比实际需要多一倍的空间,某些java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。
2、复制,程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。但回收器仍会将所有内存复制,这很浪费。一些java虚拟机会进行检查:要是没有新垃圾产生,就会转换成另一种工作模式(标记-清扫(mark-and-sweep))。
一般而言,标记-清扫速度相当慢,但是当你知道只会产生少量垃圾甚至不产生垃圾时,速度就很快。标记清扫同样遍历所有引用,找到所有存活对象。每当找到一个存活对象,就会给对象设一个标记。当标记全部完成时候,清理动作才会 开始(没有标记的对象将被释放)。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续的空间的话,就要重新整理对象。

上述Java虚拟机中,内存分配以较大的块为单位。如果对象较大,它会用单独的块。停止复制要求在释放旧有对象之前,必须把所有存活对象从旧堆复制到新堆,导致大量内存复制行为。有了块之后,垃圾回收器在回收时候可以往废弃的块里拷贝对象,每个块都有相应的代数(generation count)来记录它是否还存活。通常,如果块在某处被引用,其代数会增加,垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会被复制(只是代数会增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器效率降低的话,就会切换到标记清扫方式。虚拟机会跟踪标记清扫效果,要是堆空间出现很多碎片就会换回停止复制方式。这就是自适应的垃圾回收技术
Java虚拟机中有许多附加技术用以提升速度。尤其是与加载器操作有关的,被称为“即时”(just-in-time,JIT)编译器的技术。可以把程序全部或部分翻译成本地机器码(本来就是Java虚拟机的工作),程序运行速度因此得以提升。当需要装载某个类(通常是在为该类创建第一个对象)时,编译器会先找到其.class文件,然后将该类的字节码装入内存。有两种方案:
1、即时编译器编译所有代码。这种加载动作散落在整个程序生命周期内,累加起来花更多的时间;并且会增加可执行代码长度(字节码要比即时编译器展开后的本地代码小很多),这将导致页面调度,从而降低程序速度。
2、惰性评估(lazy evaluation),意思是即时编译器只在必要时候才编译代码。这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就是采用类似方法,代码每次被执行的时候会做一些优化,所以执行的次数越多,他的速度就越快。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值