zoukankan      html  css  js  c++  java
  • Java核心技术 第六章 接口和内部类

    Java核心技术  第六章  接口与内部类

    接口:

    任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整数数值。

    Java SE 5.0中,Comparable接口已经改进为泛型类型。

    接口中所有的方法自动的属于public。因此,在接口中声明方法时,不必提供关键字public

    接口中决不能含有实例域,也不能在接口中实现方法。

    要让一个类使用排序服务,必须让它实现compareTo方法,因此必须实现Comparable接口。

    接口的特性:

    不能使用new运算符实例化一个接口。

    可以声明接口的变量:

    Comparable x ;

    X = new Employee() ;

    可以使用instanceof检查一个对象是否属于某个特定接口。

    If(anObject instanceof Comparable)

    与可以建立类的继承关系一样,接口也可以被扩展。

    public interface Moveable {

      void move(double x, double y) ;

    }

    public interface Powered extends Moveable {

      double milePerGallon() ;

    }

    虽然接口中不能包含实例域或静态方法,但却可以包含常量。

    public interface Powered extends Moveable {

      double milePerGallon() ;

      double SPEED_LIMIT = 95 ;

    }

    与接口中的方法都自动的被设置为public一样,接口中的域将被自动设为public static final .

    尽管每个类只能拥有一个超类,但却可以实现多个接口。使用逗号将实现的各个接口隔开。

    对象克隆:

    Employee original = new Employee(“John Public”, 5000) ;

    Employee copy = original ;

    copy.raiseSalary(10) ;

    copy original引用的是一个变量,改变copy将改变original

    如果创建一个对象的新的copy, 它的最初状态与original一样,但以后将可以各自改变各自的状态,那就需要使用clone方法。

    Employee copy = original.clone() ;

    copy.raiseSalary(10) ;

    clone 方法是Object类的一个protected方法。所以只有Employee类才能克隆Employee对象。克隆是对对象中的所有的数据域进行克隆。若果所有的数据域属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。

    进行浅拷贝,如果原始对象与克隆对象共享的子对象是不可变的,将不会产生任何问题。然而更常见的情况是子对象可变,因此这种情况下需要重新定义clone方法,以便实现克隆子对象的深拷贝。

    对每一个类,都需要作出下列判断:

    1. 默认的克隆方法是否满足要求
    2. 默认的clone方法是否能够通过调用可变子对象的clone得到修补
    3. 是否不应该使用clone

    实际上,选项3是默认的。如果要选择12,类必须:

    1.实现Cloneable接口

    2.使用public访问修饰符重新定义clone方法

    在这里,Cloneable接口的出现于接口的正常使用没有任何关系。尤其是,它并没有指定clone这个方法,这个方法是从Object类继承而来的。接口在这里只是作为一个标记,表明类设计者知道要进行克隆处理。如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检查异常。

    即使clone的默认实现(浅拷贝能够满足要求),也应该实现Cloneable接口,将clone重新定义为public,并调用super.clone()。 例:

    class Employee implements Cloneable {

      public Employee clone() throws CloneSupportedException {

        return (Employee) super.clone() ;

      }

    }

    上面实现的是浅拷贝,若想实现深拷贝,必须克隆所有可变的实例域。

    下面是一个建立深拷贝clone方法的一个示例:

    class  Employee implements Cloneable {

    ...

      public Employee clone() throws CloneNotSupportedException {

        Employee cloned = (Employee) super.clone() ;

        cloned.hireDay = (Date) hireDay.clone() ;

        return cloned ;

      }

    }

    只要在clone中含有没有实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNotSupportedException 异常。当然EmployeeDate类都实现了Cloneable 接口,因此不会抛出异常。但是编译器并不知道这些情况,因此需要声明异常:

    public Employee clone() throws CloneNotSupportedException

    一旦为Employee类定义了clone方法,任何人都可以利用它克隆Manager对象。Employee的克隆方法能否完成这项重任,取决于Manager类中包含哪些域。如果Manager中包含一些需要深拷贝的域或者包含一些没有实现Cloneable接口的域,无法保证拷贝正确。

    在标准类库中,只有不到5%的类实现了clone

    接口与回调:

    回调是一种常见的设计模式。在这种模式中,可以指出摸个特定事件发生时应该采取的动作。

    内部类:

    内部类是定义在另一个类中的类。

    需要内部类的原因:

    l 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据

    l 内部类可以对同一个包中的其他类隐藏起来

    l 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

    使用内部类访问对象状态:

    public class TalkingClock {

      private int interval ;

      private boolean beep ;

      public TalkingClock(int interval, boolean beep) {...}

      public void start() {...}

      public class TimerPrinter implements ActionListener {

        public void actionPerformed(ActionEvent event) {

        Date now = new Date() ;

        System.out.println(“At the tone, the time is” + now) ;

        if (beep) Toolkit.getDefaultToolkit().beep() ;

        }

      }

    }

    需要注意,这里的TimerPrinter类位于TalkingClock类内部。

    内部类的特殊语法规则:

    使用外围类的表达式:

    OuterClass.this

    例:

    public void actionPerformed(ActionEvent event) {

    ...

      if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep() ;

    }

    反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:

    outerObject.new InnerClass(construction parameters)

    例如:

    ActionListener listener = this.new TimePrinter() ;

    通常this是多余的

    TalkingClock jabberer = new TalkingClock(1000, true) ;

    TalkingClock.TimePrinter listener = jabberer.new TimePrinter() ;

    内部类是否有用、必要和安全:

    内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成$分隔外部类名与内部类名的常规文件,而虚拟机对此一无所知。

    例如:在TalkingClock类内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class.

    编译器在外围类添加静态方法access$0.它将返回作为参数传递给它的对象域beep。(方法名取决于编译器)

    if(beep) 相当于if(access$0(outer))

    局部内部类:

    TimePrinter这个类名只在start方法中创建这个类型的对象时使用了一次。当遇到这种情况时,可以在一个方法中定义局部类。

    public void start() {

      public class TimerPrinter implements ActionListener {

        public void actionPerformed(ActionEvent event) {

        Date now = new Date() ;

        System.out.println(“At the tone, the time is” + now) ;

        if (beep) Toolkit.getDefaultToolkit().beep() ;

      }

    }

      ActionListener listener = new TimePrinter() ;

      Timer t = new Timer(interval, listener) ;

      t.start() ;

    }

    局部类不能用publicprivate访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

    局部类有一个优势,即对外部世界可以完全地隐藏起来。即使TalkingClock类中的其它代码也不能访问它。除了start方法外,没有任何方法知道TimePrinter的存在。

    由外部方法访问final变量:

    与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过那些局部变量必须被声明为final

    例:将TalkingClock构造器的参数intervalbeep移至start方法中。

    public void start(int interval, final boolean beep) {

      class TimePrinter implements ActionListener {

        public void actionPerformed(ActionEvent event) {

        Date now = new Date() ;

        System.out.println(“At the tone, the time is” + now) ;

        if (beep) Toolkit.getDefaultToolkit().beep() ;

      }

    }

      ActionListener listener = new TimePrinter() ;

      Timer t = new Timer(interval, listener) ;

      t.start() ;

    }

    由于beep是局部变量,start方法结束,beep参数变量不复存在,为了能够让actionPerformed能够正常工作,TimerPrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。final boolean val$beep ;该域放在TalkingClockTimePrinter中。

    final关键字可以用于局部变量、实例变量和静态变量。在所有这些情况下,它们的含义都是:在创建这个变量后,只能够为之赋值一次。此后再也不能修改它的值了,这就是final

    不过在定义final变量的时候,不必进行初始化。

    final int[] counter = new int[] ;

    这样可以更新counter

    匿名内部类:

    将局部类的使用再深一步。加入只创建这个类的一个对象,就不必命名了。这种情况称为匿名内部类。

    public void start(int interval, final boolean beep) {

      ActionListener listener = new ActionListener()

      {

        public void actionPerformed(ActionEvent event) {

        Date now = new Date() ;

        System.out.println(“At the tone, the time is” + now) ;

        if (beep) Toolkit.getDefaultToolkit().beep() ;

      }

    } ;

      Timer t = new Timer(interval, listener) ;

      t.start() ;

    }

    它的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。

    通常的语法格式为:

    new SuperType(construction parameters) {

      inner class methods and data

    }

    其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。

    由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是将构造器参数传递给超类构造器。

    得到类名:

    System.out.println(“Something awful happened in” +  getClass()) ;

    不过对于静态方法无效。getClass()调用的是this.getClass(),静态方法没有this

    静态方法应使用下面的表达式:

    new Object(){}.getClass().getEnclosingClass()

    在这里,new Object(){}会建立Object的一个匿名子类的一个匿名对象,getEnclosingClass则得到其外围类,也就是包含这个静态方法的类。

    静态内部类:

    有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。

    当然,只有内部类可以声明为static

    静态内部类除的对象除了没有对生成它的外围对象的引用特权,与其他所有内部类完全一样。因为静态类生成在外部引用类形成之前。

    外围类的静态方法中使用内部类可以使用外围类中定义的内部类,但给内部类必须是静态的。因为静态方法只能使用定义该静态方法的类的静态变量。

    class ArrayAlg {

    ...

      public static class Pair {

      ...

      }

      public static Pair minmax(double[] values) {

      ....

      return new Pair(min, max) ;

      }

    }

    代理:

    代理是Java SE 1.3新增的特性。

    利用代理可以在运行时创建一个实现了一组给定接口的新类。

    代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口。尤其是,它具有下列方法:

    l 指定接口所需要的全部方法

    l Object类中的全部方法,例如,toStringequals

    然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器。调用处理器是实现了InvocationHandler接口的对象。这个接口中只有一个方法:

    Object invoke(Object proxy, Method method, Object[] args)

    无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并且向其传递Method对象和原始的调用参数。调用处理器必须给出处理器调用方式。

    要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:

    l 一个类加载器。目前,用null表示默认的类加载器

    l 一个Class对象数组,每个元素都是需要实现的接口

    l 一个调用处理器

    使用代理的原因:

    l 路由对远程服务器的方法调用

    l 在程序运行期间,将用户接口事件与动作关联起来

    l 为调试,跟踪方法调用

    要点:

    传入一组接口类对象,运行时才能确定接口的实现方法。这样可以直接使用接口,解决接口不能直接实例化的问题。

     代理类的特性:

    所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。

    所有的代理类都覆盖了Object类中的方法toStringequalshashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invokeObject的其他方法没有被重新定义。

    对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类:

    Class proxyClass = Proxy.getProxyClass(null, interface) ;

    代理类一定是publicfinal。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。

    可以通过调用isProxyClass方法检测一个特定的Class对象是否代表一个代理类。

  • 相关阅读:
    树的前序 中序 后序遍历
    算法入门经典-第四章 例题4-3 救济金发放
    算法入门经典-第五章 例题6-10 下落的树叶
    排序(三) 选择排序

    printf格式输出总结
    并查集
    异或的应用
    ActionContext详解
    ActionContext表格总结
  • 原文地址:https://www.cnblogs.com/chwy/p/5700812.html
Copyright © 2011-2022 走看看