2015 7 1 java核心技术 卷1 第6章 接口与内部类

第6章 接口与内部类
首先,介绍一下接口(interface)技术,这种技术主要用来描述类具有什么功能,而并个给出每个功能的实现。一个类可以实现(implment)一个或多个 接口,并在需要接口的地方,随时使用实现了相应接口的对象。

了解接口后,再继续看一下克隆对象(深拷贝)。对象的克隆是指创建一个新的对象,且新对象的状态与原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态。
接下来,看一下内部类(inner class)机制。内部类定义在另外一个类的内部,其中的方法可以访问包含他们的外部类的域,内部类技术主要用于设计具有协作关系的类集合。特别是在编写GUI事件的代码时,使用它将可以让代码看起来更专业。
最后将介绍代理(proxy),这是一种实现任意接口的对象。代理是一种非常专业的构造工具,它可以用来构建系统级的工具。

接口
在Java程序设计中,接口不是类,而是 对类的一组需求的描述,这些类要 遵从接口描述的统一格式的定义。(遵从标准?)

我们经常听到服务提供商这样说:“如果 类遵从某个特定的接口,那么就履行这项服务”。下面给出一个具体的示例。Arrays类中的sort方法承诺可以对 对象数组 进行排序,但要求满足前提: 对象所属的类 必须实现Comparable接口。

下面是Comparable接口的代码:
public interface Comparable<T>
{
    int compareTo(T other);//parameter has type T
}
接口中的 所有方法自动地属于public。因此,在接口中声明方法时, 不必提供关键字public。

在调用x.comparableTo(y)的时候,这个compareTo方法必须确实比较两个对象的内容,并返回比较的结果。当x小于y返回一个负数;当x等于y时返回0;否则返回一个整数。

上面这个接口只有一个方法,而有些接口可能包含多个方法。稍后可以看到,在接口中 还可以定义常量。然而,更为重要的是要知道接口不能提供哪些功能。接口 绝不能含有实例域也不能在接口中实现方法。提供实例域和方法实现的任务应该 由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。但这两个概念还是有一定的区别的。

现在假设希望使用Arrays类的sort方法对Employee对象数组进行排序,Employee类就必须实现Comparable接口。

为了实现一个接口,通常需要下面两个步骤:
1)将类声明为实现给定的接口
2)对接口中的所有方法进行定义
class Employee implements Comparable<Employee>
{
    public int compareTo(Employee other)
    {
         return Double.compare(salary,other.salary);
    }
}
现在我们已经看到,要让一个类使用排序服务必须让它实现compareTo方法。这是理所应当的,因为要向sort方法提供对象的比较方式。但是为什么不能直接在Employee类直接提供一个compareTo方法,而必须实现Comparable接口呢?主要原因在于Java程序设计语言是一种强类型语言。在调用方法的时候,编译器将会检查这个方法是否存在。在sort方法中可能存在下面这样的语句:
if(a[i].compareTo(a[j])>0)
{
    //rearrange a[] and a[j]
    . . .
}
为此编译器必须确认a[i]一定有compareTo方法。如果a是一个Comparable对象的数组,就可以确保compareTo方法,因为每个实现Comparable接口的类都必须提供这个方法的定义。

接口的特性
接口不是类尤其,不能使用new运算符实例化一个接口:

x = new Comparable(...)//Error

尽管不能构造接口对象,却能声明接口的变量 。(和抽象类是一样的)
Comparable x;//OK

接口变量必须引用实现了接口的对象:
x = new Employee(. . .);//OK provided Employee implements Comparable
接下来,如同使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instance检查一个对象是否实现了某个特定的接口:
if(anObject instanceof Comparable){. . .}

与可以建立类的继承关系一样,接口也可以被扩展。允许存在多条从具有较高通用性的接口到较高专用性的接口的链。例如假设有一个称为
Moveable的接口:
public interface Moveable
{
    void move(double x);
}
然后可以在他的基础上,扩展一个Powered的接口
public interface Powered extends Moveable
{
    double milesPerGalon();
    double SPEED_LIMIT = 95;// a public static final constant
}
与接口中的方法都自动地被设置为public一样,接口中的域将被自动设置为public static final。有些接口只定义了常量,而没有定义方法。例如标准库中有一个SwingConstants就是这样一个接口,其中只包含NORTH,SOUTH和HORIZONTAL等常量。任何实现SwingConstants接口的类都自动地继承了这些常量, 并可以在方法中直接得引用NORTH,而不必采用SwingConstants.NORTH这样的繁琐书写形式。

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。例如,Java程序设计语言有一个非常重要的内置接口,称为Cloneable。如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建对象的一个拷贝。如果希望自己设计的类拥有 克隆 和 比较的 能力,只要实现这两个接口就行了。
class Employee implement Cloneable,Comparable
使用逗号将实现的各个接口分隔开。

接口与抽象类
在阅读了第5章的抽象类的内容后,可能会产生这样一个疑问:为什么Java要不辞辛苦地引入接口的概念?为什么不将Comparable直接的设计成如下所示的抽象类。
abstract class Comparable
{
    public abstract int compareTo(Object other);
}
然后,Employee类再直接扩展这个抽象类,并提供compareTo方法的实现:
class Employee extends Comparable
{
    public int compareTo(Object other){. . .}
}

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个类。假设Employee类已经扩展于一个子类,例如Person,它们就不能再像下面这样扩展第二个类了。

class Employee extends Person,Comparable // ERROR

但这个类可以像下面这样实现多个接口
class Employee extends Person implement Comparable // OK
接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性

对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,这就是说,改变一个变量所引用的对象将会对另一个变量产生影响。
Employee original = new Employee("John Public",50000);
Employee copy = original;
copy.raiseSalary(10);//oops--also changed original



如果创建一个对象的新的copy,它的 最初状态与original一样,但以后将 可以各自改变各自的状态,那就需要使用clone方法。
Employee copy = original.clone();        // 对象变量.克隆()
copy.raiseSalary(10);//OK--original unchanged

下图显示了使用 Object类的clone方法克隆Employee对象的结果。可以看到,默认的克隆操作是   浅拷贝。它并 没有克隆包含在对象中的内部对象


如果进行浅拷贝将会发生什么?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也有可能子对象在生命周期内不会发生变化,既没有改变它们的方法,也没有创建对它们引用的方法。

然而更常见的对象是可变的。因此必须 重新定义clone方法以便实现克隆子对象的深拷贝。在列举的hireDay域属于Date类,这就是一个可变的子对象。为此我们要
1)实现Cloneable接口
2)使用public访问修饰符重新定义clone方法

如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检验异常。
(Cloneable 接口是Java提供的几个标记接口之一。我们知道使用接口的目的是为了确保类实现某个特定方法或一组特定的方法,Comparable接口就是这样一个示例。而标记接口没有方法,使用它的唯一目的是可以用instanceof进行类型检查:
    if(obj instanceof Cloneable)
)

即使clone的默认实现(浅拷贝)能够满足需求,也应该实现Cloneable接口,将clone重定义为public,并调用super.clone()。下面是一个实例。
class Employee implement Cloneable
{
    //raise visibility level to public ,change return type
    return (Employee)super.clone();
}
这个clone方法并没有在Object.clone()提供的浅拷贝基础上增加任何新功能,而只是将这个方法声明为public。为了实现深拷贝,必须克隆所有 可变的实例域
class Employee implements Cloneable
{
    public Employee clone() throws CloneNotSupportedException
    {
         //call Object.clone()
         Employee cloned = (Employee)super.clone();
         //clone mutable fields
         cloned.hireDay  = (Date)hireDay.clone();
         return cloned;
    }
}
只要在clone方法中含有没有实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNot-SupportException的异常。当然Employee类
和Date类都实现了Cloneable接口,因此不会抛出异常。


所有的数组类型均包含一个clone方法,这个方法被设置为public,而不是protected。可以利用这个方法创建一个包含所有数据元素拷贝的一个新数组。例如:
package com.sheng;
import java.util.Arrays;
/**
 * 数组的克隆
 * @author Me
 *
 */
public class Welcome9 {
    public static void main(String[] args) {
        int[] luckyNumbers = {2,3,5,7,11,3};
        int[] cloned = luckyNumbers.clone();//doesn't change luckyNumbers[5]
        cloned[5] = 12;
        System.out.println(Arrays.toString(luckyNumbers));
        System.out.println(Arrays.toString(cloned));
    }
}  
结果是:
[2, 3, 5, 7, 11, 3]
[2, 3, 5, 7, 11, 12]  

程序清单6-3 6-4 的debug





接口与回调

回调(callback)是一种常见的设计模式。在这种模式中,可以指出某个 特定事件发生时 应该采取的动作。例如,可以指出在按下鼠标或选择某个菜单时应该采取什么行动。

在java.swing包中有一个Timer类。可以使用它在到达给定的时间间隔发出通告。例如,假如程序有一个时钟,就可以请求每秒钟获得一个通告,以便更新时钟的画面。
在构造定时器时,需要设置一个时间间隔,并告知定时器, 到达时间间隔需要 执行什么操作。
如何告知定时器做什么呢?在很多程序设计语言中,可以提供一个函数名 定时器周期性的调用它。但是在Java标准类库中的 类采用的是面向对象的方法。它 将某个类的对象传递给定时器,然后,定时器 调用这个对象的方法。由于对象可以携带一些附加的信息,所以,传递一个对象比传递一个函数要灵活的多。
当然,定时器需要知道调用哪一个方法,并要 求传递的对象所属的类实现了 java.awt.event包的ActionListener接口。下面是这个接口
public interface ActionListener
{
    void actionPerformed(ActionEvent event);
}
当到达指定的时间间隔时,定时器就调用actionPerformed方法。

假设希望每隔10秒钟打印一条信息“At the tone,the time is ...”,然后响一声,就应该定义一个实现ActionListener接口的类,然后将需要执行的语句
放在actionPerformed方法中。
class TimePrinter implements ActionListener
{
    public void actionPerformed(ActionEvent event)
    {
         Date now = new Date();
         System.out.println("At the tone,the time is " +now);
         Toolkit.getDefaultToolkit().beep();
     }
}
需要注意actionPerformed方法的ActionEvent参数。这个参数提供了事件的相关信息,例如,产生这个时间的源对象。
接下来构造这个类的一个对象,并将它传递给Timer构造器。
ActionListener listener new TimePrinter();     //监听器对象,接口名变量 new 实现接口的类名()
Timer t =new Timer(10000,listener );
Timer构造器的第一个参数是发出通告的时间间隔,它的单位是毫秒。第二个参数是监听器对象。
最后启动定时器: t.start();
每隔10s钟下列信息显示一次,然后响一声铃。
At the tone,the time is Thu Apr 13 23:29:08 PDT 2000


绿色的三角代表覆盖父类的方法

这种三角代表实现接口的方法。


package timer;
/**
   @version 1.00 2000-04-13
   @author Cay Horstmann
*/
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer; 
// to resolve conflict with java.util.Timer
public class TimerTest
{  
   public static void main(String[] args)
   {  
      ActionListener listener = new TimePrinter();
      // construct a timer that calls the listener
      // once every 3 seconds
      Timer t = new Timer(3000, listener);
      t.start();
      JOptionPane.showMessageDialog(null"Quit program?");
      System.exit(0);
   }
}
class TimePrinter implements ActionListener
{  
   public void actionPerformed(ActionEvent event)
   {  
      Date now = new Date();
      System.out.println("At the tone, the time is " + now);
      Toolkit.getDefaultToolkit().beep();
   }
}  
运行结果


内部类

内部类(inner class)是定义在另一个类中的类。为什么需要使用内部类呢?其主要原因有以下三点:

1) 内部类的方法可以访问 该类定义所在的作用域中的数据, 包括私有的数据
2)内部类可以对同一个包中的其他类隐藏起来
3)当想要定义一个 回调函数且不想编写大量代码时,使用 匿名内部类比较便捷

使用内部类访问对象状态
我们选择一个简单但不太实用的例子说明内部类的使用方式。下面进一步介绍TalkingClock类。构造一个语音时钟时需要两个参数:发布通告的时间间隔和开关铃声的标志。
public class TalkingClock
{
    private int interval;
    private boolean beep;

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

    public calss TimerPrinter implements ActionListener
      //an inner class     
     {
        . . .
    }
}
需要注意,这里的TimePrinter类位于TalkingClock内部。 这并不意味这每个 TalkingClock都有一个TimePrinter实例域。如前所示,TimePrinter的对象是由 TalkingClock类的方法构造。
下面是 TimePrinter类的主要内容。需要注意一点,actionPerformed方法在发出铃声之前检查了beep标志。
public 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();
    }
}
令人惊讶的事发生了。 TimePrinter 类没有实例域或者名为beep的变量 ,取而代之的是beep引用了创建 TimePrinter  的 TalkingClock对象的域。从传统意义上讲,一个方法可以引用调用这个方法的对象数据域(即调用这个对象的方法是隐式参数)。内部类既可以访问自身的数据域,也可以访问创建它的外围对象的数据域。

为了能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类的对象

这个引用在内部类的定义中是不可见的 。然而,为了说明这个概念, 我们将外围类对象的引用称为outer 。于是actionPerformed方法将等价于下列形式:
public void actionPerformed(ActionEvent event)
{
    Date now = new Date();
      System.out.println("At the tone,the time is " +now);
    if(outer.beep Toolkit.getDefaultToolkit().beep();
}
外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器。 编译器修改了所有内部类的构造器,添加一个外围类引用的参数。因为
TimePrinter 类没有定义构造器,所以编译器为这个类 生成了一个默认的构造器,其代码如下所示:
public TimePrinter( TalkingClock clock )
{
    outer = clock;
}
请再注意,outer不是Java关键字。我们只是用它说明内部类的机制。
当在start方法中创建了TimePrinter对象后,编译器就会将this引用传递给当前的语音时钟的构造器:
ActionListener listener = new TimePrinter(this); //parameter automatically added

如果有一个TimePrinter是一个常规类,它就需要通过TalkingClock类的公有方法访问beep标志,而使用内部类可以给予改进,即不必提供仅用于访问其他类的访问器。

TimePrinter类声明为私有的。这样一来,只有TalkingClock类的方法才能够构造TimePrinter对象。 只有内部类可以是私有类,而常规类只可以具有 包可见性(默认),或公有可见性(public)。

内部类的特殊语法规则
在上一节,已经讲述了内部类有一个外围类的引用outer。事实上, 使用 外围类的引用 的正规语法还要复杂些。表达式

                                                            OuterClass.this    //外围类的引用

表示外围类引用。例如,可以向下面这样编写 TimePrinter内部类的actionPerformed方法:
public void actionPerformed(ActionEvent event)
{
     if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}

反过来,可以采用下列语法格式 更加明确地编写内部对象的构造器
outerObject. new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();

在这里,最新构造的TimePrinter对象的 外围类引用被设置为创建内部类对象的方法中的this引用。这是一种最常见的情况。通常,this限定词是多余的。不过,可以通过显式地命名将外围类引用设置为其他的对象。例如,如果TimePrinter是一个公有内部类,对于任意的语音时钟都可以构造一个TimePrinter:
TalkingClock jabber = new TalkingClock(1000,true);
TalkingClock.TimePrinter listener = jabber.new  TimePrinter()
需要注意,在外围类的作用域外,可以这样引用内部类:
OuterClass.InnerClass

局部内部类
如果仔细地阅读一下TalkingClock示例的代码就会发现,TimePrinter这个类的名字只在 start方法中 创建这个类型的对象时使用了 一次。(也就是说,我创建内部类,就是为了给这个方法用的,其他方法都没用到)
   public void start()
   {
      ActionListener listener = new TimePrinter();    //TimePrinter只出现了一次
      Timer t = new Timer(intervallistener);
      t.start();
   }  

当遇到这类情况时,可以在一个方法中定义局部类。

public class InnerClassTest {
    public static void main(String[] args) {
          TalkingClock clock = new TalkingClock(1000, true);
          clock.start();
          // keep program running until user selects "Ok"
          JOptionPane.showMessageDialog(null"Quit program?");
          System.exit(0);
    }
}
class TalkingClock
{
   private int interval;
   private boolean beep;
   /**
    * Constructs a talking clock
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public TalkingClock(int intervalboolean beep)
   {
      this.interval = interval;
      this.beep = beep;
   }
   
   public void start()
   {
      class TimePrinter implements ActionListener
      {
        @Override
        public void actionPerformed(ActionEvent e) {
            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(intervallistener);
      t.start();
   }
} 
局部类 不能用public或private访问说明符进行声明。它的 作用域被限定在 声明这个局部类的块中
(局部内部类是块作用域,不能有访问修饰符!!!)

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

匿名内部类(在只创建这个内部类的一个对象时用)

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

  public void start(int intervalfinal boolean beep)
   {
      ActionListener listener = new ActionListener()     //注意这里new后的是接口的名称,是不是有点奇怪,习惯就好
         {
            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(intervallistener);
      t.start();
   }  
这种语法确实难以理解。它的含义是:创建一个ActionListener接口的类的新的对象,需要实现的方法actionPerfomed定义在括号{}内。
通常的语法格式为:
new SuperType(construction parameters)
{
    inner class methods and data;
}
其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它

由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类(superclass)构造器。尤其是在内部类实现接口的时候,不能有任何的参数。不仅如此,还要像下面这样提供一组括号。
new   InterfaceType()
{
   methods and data
}
请仔细研究一下,看看构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间有什么差别。
Person queen = new Person("Mary");
//a Person object
Person count = new Person("Dracula"){. . .};
// an object of  an inner class extending Person
如果 构造参数(new关键字)闭圆括号跟一个开花扩号正在定义的就是匿名内部类


下面的技巧称为“双括号初始化”,这里利用了内部类语法。假设你想构造一个数组列表,并将它传递到一个方法:
ArrayList<String> firends = new ArrayList<>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
如果不再需要这个数组列表,最好让它作为一个匿名列表。该如何为他添加元素呢?方法如下
invite(new ArrayList<String>(){{ add("Harry"); add("Tony"); }})
注意这里的双括号。外层括号建立了一个ArrayList的匿名子类。内层括号则是一个对象的构造块。


再次强调:静态方法没有this指针,无法调用本类的一般方法和实例域
如果要在静态方法中,获得当前类的类名(内部类的一个应用) 调用getClass是不行的,因为调用getClass时调用的是this.getClass(),而静态方法没有this。所以应该用
以下表达式
★new Object(){}.getClass().getEnclosingClass()  //get class of static method
在这里new Object(){}.getClass()会建立Object类的一个匿名子类的一个匿名对象,.getEnclosing()则得到其的外围类,也就是这个包含静态方法的类。

package com.sheng;
public class Welcome7 {
    public static void main(String... args) {
        System.out.println(System.getProperty("user.dir"));
7       System.out.println(getClass().getName());
        //System.out.println(new Object(){}.getClass().getEnclosingClass().getName());
    }
}  


public class Welcome7 {
    public static void main(String... args) {
        System.out.println(System.getProperty("user.dir"));
        System.out.println(new Object(){}.getClass().getEnclosingClass().getName());
    }
}  
运行的结果:
D:\学习资料\源码\corejavabook\v1ch02\Welcome
com.sheng.Welcome7  

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

下面是一个使用静态内部类的典型例子。考虑一下计算数组中最小值和最大值的问题。
方案一:当然,可以编写两个方法,一个方法用于计算最小值,一个方法用于计算最大值。再调用这两个方法的时候,数组被遍历了两次。
方案二:如果只遍历一次,并能够同时计算出最小值和最大值,那么就可以大大地提高效率了。
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for(double v : values)
{
    if(min > v) min = v;
    if(max < v) max = v;
}
然而,这个方法必须返回两个数值,为此, 可以定义一个包含两个值的类pair
class pair
{
    private double first;
    private double second;

    public Pair(double f,double s)
    {
         first = f;
          second = s;
     }
    public double getFirst(){return first;}
    public double getSecond(){return second;}
}
minmax方法可以返回一个Pair类型的对象。
class ArrayAlg
{
    public static Pair minmax(double[] values)
    {
         . . .
         return new Pair(min,max);
    }
}
这个 方法调用者可以使用getFirst和getSecond方法获得答案。
Pair p =ArrayAlg.minmax(d);
System.out.println("min="+p.getFirst());
System.out.println("max="+p.getSecond());

当然, Pair是一个十分大众化的名字。在大型项目中,除了定义包含一对字符串的Pair类之外,其他程序员也可能使用这个名字。这样就会产生名字冲突。解决这个问题的办法是将 Pair定义为ArrayAlg的内部公有类。此后,通过 ArrayAlg.Pair访问它:
ArrayAlg.Pair p = ArrayAlg.minmax(d);
不过与前面的内部类不同,在Pair对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为static:
class  ArrayAlg
{
    public static class Pair
    {
         . . .
    }
    . . .
}
当然, 只有内部类可以声明为static静态内部类的对象除了没有对生成它的外围类对象的引用特权外(没有this),与其他所有内部类完全一样。 在我们列举的示例中必须使用静态内部类,这是由于内部类的对象是在静态方法中构造
public static Pair minmax(double[] d)    //静态方法中
{
    ...
    return new Pair(min,max);            //静态方法中构造了内部类的对象,所以内部类也必须是静态的
}
如果没有将Pair类声明为static,那么编译器将会给出错误报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象。

声明在接口中的内部类自动成为static和public类

程序清单6-8包含ArrayAlg类和嵌套的Pair类的全部源代码
package staticInnerClass;
/**
 * This program demonstrates the use of static inner classes.
 * @version 1.01 2004-02-27
 * @author Cay Horstmann
 */
public class StaticInnerClassTest
{
   public static void main(String[] args)
   {
      double[] d = new double[20];
      for (int i = 0; i < d.lengthi++)
         d[i] = 100 * Math.random();
      ArrayAlg.Pair p = ArrayAlg.minmax(d);//生成静态内部类的对象
      System.out.println("min = " + p.getFirst());
      System.out.println("max = " + p.getSecond());
   }
}
class ArrayAlg
{
   /**
    * A pair of floating-point numbers
    */
   public static class Pair
   {
      private double first;
      private double second;
      /**
       * Constructs a pair from two floating-point numbers
       * @param f the first number
       * @param s the second number
       */
      public Pair(double fdouble s)
      {
         first = f;
         second = s;
      }
      /**
       * Returns the first number of the pair
       * @return the first number
       */
      public double getFirst()
      {
         return first;
      }
      /**
       * Returns the second number of the pair
       * @return the second number
       */
      public double getSecond()
      {
         return second;
      }
   }
   /**
    * Computes both the minimum and the maximum of an array
    * @param values an array of floating-point numbers
    * @return a pair whose first element is the minimum and whose second element
    * is the maximum
    */
   public static Pair minmax(double[] values)
   {
      double min = Double.MAX_VALUE;
      double max = Double.MIN_VALUE;
      for (double v : values)
      {
         if (min > vmin = v;
         if (max < vmax = v;
      } 
      return new Pair(minmax);//静态的方法中只能构造静态的内部类对象
   }
}  
运行结果
min = 9.952270374603279
max = 84.23621676588026  

代理(先略微了解)
Java SE 1.3增加的新特性。利用代理可以在 运行时创建一个实现了一组给定接口的新类。这种功能只有在 编译时无法确定需要实现哪个接口时才有必要使用。 对于应用程序设计人员来说,遇到这种情况很少。然而,对于 系统程序设计人员来说,代理带来的灵活性十分重要。
假设有一个表示接口的Class对象(有可能只包含一个接口),它的确切类型在编译时无法知道。这确实有些难度。要想构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在 程序处于运行状态定义一个新类

为了解决这个问题,有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件。很自然,这样做的速度会比较慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。 代理类可以在运行时创建全新的类。这样的 代理类能够实现指定的接口。尤其是它具有下列方法。
-- 指定接口所需要的全部方法
-- Object类中全部方法,例如,toString、equals等
然而,不能在运行时定义这些方法的新代码。而是要提供一个 调用处理器(invocation handler)。调用处理器是实现了 InvocationHandler接口的类对象。调用处理器实现了 InvocationHandler接口的类对象。在这个接口中只有一个方法:
Object   invoke(Object proxy,Method method,Object[] args)     // 代理对象,  Method对象,原始调用参数
无论何时调用 代理对象的方法,调用处理器的invoke方法都会被调用,并向其传送Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。

要想 创建一个 代理对象,需要使用 Proxy类的 newProxyInstance方法。这个方法有三个参数:
--一个 类加载器(class loader)。作为Java安全模型的一部分,对于系统类和从因特网上下载的类,可以使用不同的类加载器。用null表示使用默认的
    类加载器。
--一个Class对象数组,每个元素都是需要实现的接口
--一个调用处理器
还有两个需要解决的问题。如何定义一个处理器?能够用结果代理对象做些什么?当然,这两个问题的答案取决于打算使用代理机制解决什么问题。使用代理可能出于很多原因:
-- 路由器对远程服务器的方法调用(只有在运行的时候才知道要实现哪些接口,因为不同的服务器的接口不同)
--在程序运行期间,将用户接口事件与动作关联起来
--为调试跟踪方法用

在示例程序中,使用代理和调用处理器跟踪方法调用,并且实现了一个TraceHandler包装器类存储包装的对象。其中的invoke方法打印出被调用方法的名字和参数,随后用包装好的对象作为隐式参数调用这个方法。
class Tracehandler implements   InvocationHandler
{
    private Object target;
    public TraceHandler(Object t)
    {
         target = t;
    }
    public  Object   invoke (Object proxy,Method method,Object[] args) throws Throwable
    {
         //print method name and parameters
         . . .
        // invoke actual method
         return m.invoke(target,args);
    }
}
下面说明一下如何构造用于跟踪方法调用的代理对象。
Object value = . . . ;
//constructor wrapper
InvocationHandler handler = new TraceHandler(value);
//construct proxy for one more interface
Class[] interfaces = new Class[]{Comparable.class};
Object proxy = Proxy.newProxyInstance(null,interfaces,handler);
现在无论何时用proxy调用某个方法,这个方法的名字和参数就会打印出来,之后再用value调用它。

在程序清单6-9给出的程序中,使用代理对象对二分查找进行跟踪。这里,首先将用1~1000整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。

Object[] elements = new Object[1000];
//fill elements with proxies for the integer 1 . . . 1000
for(int i = 0; i < elements.length ; i + +)
{
    Integer value = i + 1 ;
    elements[i] = Proxy.newProxyInstance(. . .);     //proxy for value
}

//construct a  random integer
Integer key = new Random().nextInt(elements.length) + 1;
//Search for the key
int result = Arrays.binarySearch(elements,key);
//print match if found
if(result >0) System.out.println(elements[result]);
在上述代码中,Integer类实现了Comparable接口。代理对象属于再运行时定义的类(它有一个名字$Proxy0).这个类也实现了Comparable接口。然而,它的compareTo方法调用了代理对象处理器的invoke方法。

binarySearch方法按下面这种方式调用:
if(elements[i].compareTo(key)<0)...
由于数组中填充了代理对象,所以compareTo调用了TraceHandler类中的invoke方法。这个方法打印出了方法名和参数,之后用包装好的Interger对象调用compareTo。
最后,在实例程序的结尾调用:
 System.out.println(elements[result]);
println方法调用代理对象的toString,这个调用也会被重定向到调用处理器上。下面是运行的全部跟踪结果。

250.compareTo(240)
125.compareTo(240)
187.compareTo(240)
218.compareTo(240)
234.compareTo(240)
242.compareTo(240)
238.compareTo(240)
240.compareTo(240)
240.toString()
240  
可以看出二分查找关键字的过程,即每一步都将查找区间缩减的一般。注意,即使不属于Comparable接口,toString方法也会被代理。在下一节会看到,有相当一部分的Object方法都被代理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值