第5章:初始化与清理

用构造器确保初始化

  1. 构造器如何命名:有两个问题 ①:所取的任何名字都可能与类的某个成员冲突,②:调用构造器是编译器的责任,所以必须让编译器知道应该调用哪个方法.Java中采取方案为:即构造器与类相同的名字,考虑到初始化期间自动调用构造器,所以这种做法顺利成章.
  2. 由于构造器的名字必须与类名完全相同,所以每个方法首字母小写的编程风格并不适用于构造器.
  3. 系统默认是无参构造器,当自己实现了有参构造器的时候,无参构造器就会失去作用,也必须重新实现.
  4. 从概念上讲,初始化与创建是彼此独立的,然而java中初始化和创建捆绑在一起,两者不能分离.
  5. 构造器是一种特殊类型的方法,因为它没有返回值.这与返回值为空(void)明显不同.对于空返回值,尽管方法本身不会自动返回什么,但是仍可选择让它返回别的东西,构造器则不会返回任何东西,别无选择(new表达式确实返回了对新建对象的引用,但是构造器本身并没有任何返回值).假如构造器具有返回值,并且允许人们自行选择返回类型,那么势必得让编译器知道该如何处理此返回值.

方法重载

  1. 要是有几个方法有相同的名字,java如何才能知道你指的是哪一个呢?其实规则很简单:每个重载的方法都必须要有一个独一无二的参数类型列表.不建议通过参数顺序的不同和返回值区分两个方法.

默认构造器

  1. 默认构造器:默认构造器(又名无参构造器)是没有形式参数的–它的作用是创建一个默认对象.如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器,但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你创建默认的构造器

this关键字

  1. this关键字只能在方法内部使用,表示对调用方法的那个对象的引用.this的用法和其他对象引用并无不同
  2. 在构造器中调用构造器使用this.尽管可以用this调用一个构造器,但却不能调用2个构造器.此外必须将构造器置于最起始处,否则编译器会报错.

static的含义

  1. 了解了this关键字之后,就会更全面的理解static(静态)方法的含义.static方法就是没有this的方法.在static方法的内部不能调用非静态方法,反过来倒是可以的.而且可以再没有创建任何对象的前提下,仅仅通过类型本身来调用static方法.这实际上正是static方法的主要用途.

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

  1. java有垃圾回收器负责回收无用对象占据的内存资源,但是也有特殊情况:假定你的对象并非使用new获得了一块特殊的区域,由于垃圾回收器只知道释放那些经new分配的内存,所以它不知道该如何释放对象的这块特殊内存.为了应对这种情况,java允许在类中定义一个名为finalize()的方法.
  2. finalize()工作原理假定是这样:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存.所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作
  3. 只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放.因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了.
  4. finalize()的用途何在:不该将finalize()作为通用的清理方法.那么,finalize()的真正用途是什么呢?垃圾回收只与内存有关,也就是说,使用垃圾回收器的唯一原因是为了回收程序不在使用的内存,所以对于与垃圾回收相关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关.
  5. finalize()具体应用:之所以要有finalize(),是由于在内存分配的时候可能使用了类似C语言的做法,而并非java中的通常做法,这种情况主要发生在使用”本地方法”的情况下,本地方法就是在java中调用非java代码的方式.这个时候释放内存需要使用 finalize()
  6. 终结条件:通常,不能指望finalize()做清理.不过finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是 对象终结条件验证.当对某个对象不再感兴趣–也就是它可以被清理了,这个对象应该处于某种状态,使它占用的内存被安全释放.本例的终结条件是:所有的Book对象在被当做垃圾回收前都应该被嵌入(check in).但在main()方法中,由于程序猿的错误,有一本书未被签入.要是没有finalize()来验证终结条件,将会很难发现这种缺陷.注意:Systme.gc()用于强制进行终结动作,即使不这么做,通过重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误.
public class Book {
    boolean checkedOut = false;

    public Book(boolean checkedOut) {
        this.checkedOut = checkedOut;
    }

    void checkIn() {
        checkedOut = false;
    }

    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();
    }
}

垃圾回收器如何工作

  • 引用记数:引用记数是一种简单但速度很慢的垃圾回收技术,每个对象都含有一个引用的计数器,当有引用连接至对象的时,引用计数加1.当引用离开作用域或被置为null时,引用计数减1.虽然管理引用计数的开销不大,但是这项开销在整个程序生命周期中持续发生.垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用为0时,就释放其占用的空间(但是引用计数模式会在计数值变为0时立即释放对象).这种做法有个缺陷,如果对象之间存在循环引用,可能会出现对象应该被回收,但是计数器却不为0的情况.引用记数常用来说明垃圾收集的工作方式,但是似乎从未被应用于任何一种java虚拟机实现中.

成员初始化

java尽力保证:所有变量在使用前都能得到恰当的初始化.对于方法的局部变量,java以编译时错误的形式来贯彻这种保证

  • 构造器初始化:可以用构造器来进行初始化,在运行时刻,可以调用方法或执行某些动作来确定初值.但是无法阻止自动初始化,它将在构造器被调用之前发生.
  • 静态数据的初始化:静态数据都只占用一份存储区域.当对象被创建(或者第一次访问数据的时候),才会被初始化,此后静态类不会再被初始化.初始化的顺序是先静态对象,然后是非静态对象.
  • 数组初始化:数组只是相同类型的,用一个标识名称封装到一起的一个对象序列或基本类型数据序列.数组的创建是在运行时刻进行的.数组元素中的基本类,值会自动初始化成空值(对于数字和字符,就是0;对于布尔型,是false) 可变参数列表,由于所有的类都直接或者间接继承于Object类,所以可以创建以Object数组为参数的方法
  static void printArray(Object[] args){
      for(Object obj :args){
          System.out.println(obj);
      }
  }
    public static void main(String[] args) {
        printArray(new Object[]{"1","2"});
        printArray(new Object[]{new Float(3.14),new Double(11.11)});
    }

可变参数的在jdk5的时候将该特性添加进来了,因此你可以使用它们来定义可变参数列表了.可变参数列表中可以使用任何类型的参数,包括基本类型

  static void printArray(Object... args){
      for(Object obj :args){
          System.out.println(obj);
      }
  }
    public static void main(String[] args) {
        printArray("1","2");
        printArray(new Float(3.14),new Double(11.11));
    }

有了可变参数,当你指定参数时,编译器实际上会为你去填充数组.你仍旧获得是一个数组.当有多个参数的时候,可变参数列表需要放在最后一位.将0个参数传递给可变参数列表是可行的.
可变参数列表使重载变得复杂.

  • 枚举类型:关键字enum,由于枚举类型的实例是常量,因此按照命名惯例他们都用大写字母表示(如果在一个名字中有多个单词,用下划线将他们隔开),在你创建enum时,编译器会自动添加一些有用的特性.例如创建toString()方法,以便可以很方便地显示某个enum实例的名字,这正是下面打印语句如何产生其输出的答案.编译器还会创建ordinal()方法,用来表示某个特定enum常量的声明顺序,以及static values()方法,来按照enum常量的声明顺序,产生 由这些常量值构成的数组.
public class TestDemo {
    public enum Spiciness {
        NOT, MILE, MEDIUM, HOT, FLAMING
    }
    public static void main(String[] args) {
        System.out.println(Spiciness.NOT);
        for (Spiciness s : Spiciness.values()) {
            System.out.println(s + "  , ordinal:   " + s.ordinal());
            /** 输出结果
             NOT  , ordinal:   0
             MILE  , ordinal:   1
             MEDIUM  , ordinal:   2
             HOT  , ordinal:   3
             FLAMING  , ordinal:   4
             */
        }
    }
}

尽管enum看起来形式一种新的数据类型,但是这个关键字只是为enum生成对应的类型时,产生了某些编译器行为,因此在很大程度上,你可以将enum当做其它任何类来处理.事实上enum确实是个类,并且具有自己的方法.
枚举有个特别实用的特性,即它可以再switch语句内使用:

public class Burrito {
    Spiciness degree;

    public enum Spiciness {
        NOT, MILE, MEDIUM, HOT, FLAMING
    }

    public Burrito(Spiciness degree) {
        this.degree = degree;
    }

    public void describe() {
        switch (degree) {
            case NOT:
                System.out.println("NOT");
                break;
            case MILE:
                System.out.println("MILE");
                break;
            case MEDIUM:
                System.out.println("MEDIUM");
                break;
            default:
                System.out.println("默认");
        }
    }


    public static void main(String[] args) {
        Burrito plain = new Burrito(Spiciness.NOT);
        plain.describe();//NOT
    }
}

由于switch是要在有限的可能值集合中选择,因此它与enum正是绝佳组合,请注意enum的名字是如何能够倍加清楚的表明程序意欲何为.大体上,可以将enum用作另外一种创建数据类型的方式,然后直接将所得到的类型拿来使用.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值