zoukankan      html  css  js  c++  java
  • Java编程思想---第五章 初始化与清理(上)

    第五章 初始化与清理(上)

      随着计算机革命的发展,不安全的编程方式已逐渐成为编程代价高昂的主因之一。初始化和清理正是设计安全的两个问题。许多程序的错误都源于程序员忘记初始化变量,特别是在使用程序库时。当使用完一个元素时,很容易把对你不再有影响的元素忘记,这样一来这个元素占用的资源就会一直得不到释放,结果是资源的耗尽,特别是内存资源。

      Java采用了构造器(constructor),并额外提供了垃圾回收器,对于不再使用的内存资源,垃圾回收器能自动将其释放。

     

    5.1 用构造器确保初始化

     

      可以假象为编写的每个类都定义一个initialize()方法,在你使用其对象之前应该首先调用initialize()方法。这同时也意味着用户必须记得自己去调用这个方法。在Java中,通过停供构造器,类的设计者可以确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,Java就会在用有能力操作对象之前自动调用相应的构造器,从而保证初始化的进行。在Java中采用了和C++类似的命名方法,也就是构造器与类采用相同的名称。

      以下是一个带有构造器的简单类:

    class Rock {
        Rock(){
            System.out.print("Rock ");
        }
    }
    
    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++){
                new Rock();
            }
        }
    }

      输出结果如下:

     

      现在创建对象时将会为对象分配存储空间,并调用相应的构造器,这就确保你在操作对象之前它就已经被初始化了。

      不接受任何参数的构造器叫做默认构造器,Java文档中通常使用术语无参构造器,但和其他方法一样,构造器也能带有形式参数以便制定如何创建对象,对上面的例子稍加修改:

    class Rock2 {
        Rock2(int i){
            System.out.print("Rock " + i + " ");
        }
    }
    
    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 8; i++){
                new Rock2(i);
            }
        }
    }

      输出结果如下:

      有了构造器形式参数,就可以在初始化对象时提供实际参数。如果上述例子中,Rock2(int)Rock类中唯一的构造器,那么编译器将不会允许你以任何其他方式创建Rock对象。

      构造器有助于减少错误,使代码更易于阅读。在Java中,初始化和创建两者捆绑在一起,不能分离。构造器是一种特殊类型的方法,它没有返回值,这不同于voidvoid是空返回值。

     

    5.2 方法重载

     

      任何程序设计语言都具备的一项重要特性就是对名字的运用,当创建一个对象时,也就给这个对象分配到的存储空间取了一个名字,通过使用名字,你可以引用所有的对象和方法。

      在Java中,构造器是强制重载方法名的另一个原因,既然构造器的名字已经由类名决定,就只能有一个构造器名,如果想要用多种方式创建一个对象,我们就需要有两个甚至多个构造器。由于都是构造器,他们必须有相同的名字,为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。

      示例使用重载的构造器和重载方法:

      

    class Tree {
        int height;
        Tree(){
            System.out.println("Planting a seeding");
            height = 0;
        }
    
        Tree(int initialHeight){
            height = initialHeight;
            System.out.println("Creating new Tree that is " + height + " feet tail");
        }
    
        void info(){
            System.out.println("Tree is " + height + " feet tail");
        }
    
        void info(String s){
            System.out.println(s + ": Tree is " + height + " feet tail");
        }
    }
    
    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++){
                Tree t = new Tree(i);
                t.info();
                t.info("overloaded method");
            }
            new Tree();
        }
    }

      输出结果如下:

    Creating new Tree that is 0 feet tail

    Tree is 0 feet tail

    overloaded method: Tree is 0 feet tail

    Creating new Tree that is 1 feet tail

    Tree is 1 feet tail

    overloaded method: Tree is 1 feet tail

    Creating new Tree that is 2 feet tail

    Tree is 2 feet tail

    overloaded method: Tree is 2 feet tail

    Creating new Tree that is 3 feet tail

    Tree is 3 feet tail

    overloaded method: Tree is 3 feet tail

    Creating new Tree that is 4 feet tail

    Tree is 4 feet tail

    overloaded method: Tree is 4 feet tail

    Planting a seeding

     

    5.2.1 区分重载方法

     

      如果几个方法有相同的名字,Java该如何进行区分?规则就是,每个重载的方法都必须有独一无二的参数列表。例如void f(String s, int i)void f(int i, String s)就是两个不同的方法。

     

      5.2.2 设计基本类型的重载

     

        如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型有所不同,如果无法找到恰好接收char参数的方法,就会把char直接提升至int

        如果传入的实际参数大于重载方法声明的形式参数,就得通过类型转换来执行窄化转换,不这样做的话编译器就会报错。

     

      5.2.3 以返回值区分重载方法

     

        例如这两个方法:void f() {} int f() {},这两个方法虽然有相同的名字和形式参数,但是很容易就能区分它们。

     

    5.3 默认构造器

     

      默认构造器又称无参构造器,是没有形式参数的,它的作用是创建一个默认对象,如果你的类中没有构造器,编译器会自动帮你创建一个默认构造器。例如:

    class Bird {}
    
    public class DefaultConstructor {
      public static void main(String[] args) {
        Bird b = new Bird();
      }
    }

      表达式new Bird()创建了一个新对象,并调用其默认构造器,没有这个默认构造器的话就无法创建对象。但如果已经定义了一个构造器,编译器就不会帮你自动创建构造器。

     

    5.4 this关键字

     

      this关键词只能在方法内部使用,表示对“调用方法的那个对象”的引用,如果在方法内部调用同一个类的不同方法就不必使用this,直接调用即可。例如:

      

    public class test() {
      void func1() { /* ... */ }
      void func2() { pick(); /* ... */ }
    }

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

     

    5.4.1 在构造器中调用构造器

     

      可能会为了一个类写多个构造器,还有时可能在一个构造器中调用另一个构造器,这时可以用this关键字做到这一点。

      通常用this时,都是指“这个对象”、“当前对象”,表示对当前对象的引用。在构造器中,如果this添加了参数列表那么就有了不同的含义,这将产生对符合此参数列表的某个构造器的明确使用,这样,调用其他构造器就有了直接的途径。

      尽管可以使用this调用一个构造器,但是不能调用两个,此外必须将构造器调用置于最起始处,否则编译器会报错。如果参数名称和数据成员名字相同会产生歧义,使用this也能解决这个问题。

     

    5.4.2 static的含义

     

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

     

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

     

      程序员都了解初始化的重要性,但常常会忘记同样重要的清理工作,把一个对象用完后就弃之不顾的做法并非总是安全的,Java有垃圾回收器负责回收无用对象占据的内存资源,但也有特殊情况,假定你的对象(并非使用new)获得了一块特殊的内存区域,由于垃圾回收器只知道释放哪些经由new分配的内存,所以它不知道怎么释放该对象的内存。为应对这种情况,Java允许在类中定义一个名为finalize()的方法,它的工作原理是:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

      这里的finalize()C++中的析构函数并不一样,在C++中,对象一定会被销毁,而Java里的对象却并非总是被垃圾回收,在Java中,对象可能不被垃圾回收,垃圾回收并不等于析构。Java并未提供析构函数或者相似的概念,要做类似的工作必须自己动手创建一个执行清理工作的普通方法。

     

    5.5.1 finalize()的用途何在

     

      通过上述论述,我们应该知道不应该将finalize()作为通用的清理方法,那么finalize()真正用途是什么?

      首先我们要知道,垃圾回收只与内存有关,也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存,所以对于与垃圾回收有关的任何行为来说,尤其是finalize()方法,它们也必须同内存及其回收有关。但这不意味着当对象中含有其他对象时,finalize()方法就要明确释放那些对象,不管对象时如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将finalize()的需求限制到了一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。

      之所以要有finalize(),是由于在分配内存时可能采用了类似C语言中的做法,所以实际上在非Java代码中,也许会调用Cmalloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放从而造成内存泄露。

     

    5.5.2 你必须实施清理

     

      要清理一个对象,用户必须在需要清理的时刻调用执行清理的方法。Java不允许创建局部对象,必须使用new创建对象。在Java中,可以肤浅地认为正是由于垃圾收集机制的存在,使得Java没有析构函数,但是随着学习的深入,我们应该知道垃圾回收器的存在并不能完全代替析构函数。如果希望进行除释放存储空间之外的清理工作,还是得明确调用某个恰当的Java方法。

     

    5.5.3 终结条件

     

      通常,Java不能指望finalize(),必须创建其他的清理方法,并且明确调用他们。finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是对象终结条件的验证。

      当对某个对象不再感兴趣,也就是它可以被清理时,这个对象应该处于某种状态,使它占用的内存可以安全地被释放。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的缺陷,finalize()可以用来最终发现这种情况,尽管它并不是总会被调用。

     

    5.5.4 垃圾回收器如何工作

     

      有一种做法名叫停止-复制(stop-and-copy),显然这意味着,先暂停程序的运行,然后将所有的存活对象从当前堆复制到另一个堆,没有被复制的全是垃圾。当对象被复制到新堆时,他们是一个挨着一个的,所以新堆保持紧凑队列,然后就可以直接分配新空间了。

      但是对于这种所谓的复制式回收器而言,效率会降低。首先,得有两个堆,然后得在这两个分离的堆之间来回倒腾,从而得维护比实际需要多一倍的空间。第二个问题在于复制,程序进入稳定状态之后可能只会产生少量垃圾,甚至没有垃圾,尽管如此,复制式回收器仍然会将所有内存从一处复制到另一处,这很浪费。为了避免这种情形,一些Java虚拟机会进行检查:要是没有垃圾产生,就会转换到另一种工作模式(自适应),这种模式成为标记-清扫(mark-and-sweep)。对于一般用途而言,这种方式线速度相当慢,但是当你知道只会有少量垃圾甚至没有垃圾时它的速度就很快了。

      标记-清扫所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象,每找到一个就给对象设一个标记,这个过程不回收任何对象,只有当全部的标记工作完成的时候才会开始清理。在清理过程中,没有标记的对象将被释放,所以剩下的堆空间是不连续的,要是想得到连续空间的话就得重新整理剩下的对象。

     

    5.6 成员初始化

     

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

      

    void f() {
      int i;
      i++;
    }

      就会得到一条出错的消息,告诉你i尚未初始化。强制程序员提供一个初始值,往往能够帮助找出程序里的缺陷。

     

    5.6.1 指定初始化

     

      如果想为某个变量赋初值,最直接的办法就是在定义类成员变量的地方为其赋值。

      

    public class Init {
      int i = 999;
      boolean bool = true;
      float f = 3.14f;
      //...
    }

      也可以采用同样的方法初始化非基本类型的对象,如果Depth是一个类,那么可以像下面这样创建一个对象并初始化它:

    public class Measurement {
      Depth d = new Depth();
      //...
    }

      

      如果没有为d指定初始值就尝试使用它就会出现运行错误,告诉你一个异常。

      还可以通过调用某个方法来提供初值:

    public class MethodInit {
      int i = f();
      int f() {
        return 11;
      }
    }
  • 相关阅读:
     sublime text3快速生成html头部信息(转)
    电脑同时安装Python2和Python3以及virtualenvwrapper(转)
    在windows下使用多版本Python安装相应的虚拟开发环境
    win10+wget 收藏
    关于OS_PRIO_SELF的说明
    select菜单实现二级联动
    HeadFirst设计模式笔记:(六)命令模式 —— 封装调用
    rnqoj-57-找啊找啊找GF-二维背包
    UILable:显示多种颜色的方法
    动态规划晋级——POJ 3254 Corn Fields【状压DP】
  • 原文地址:https://www.cnblogs.com/parable/p/11429509.html
Copyright © 2011-2022 走看看