Java核心技术第6章(4)

来源:互联网 发布:centos企业邮箱搭建 编辑:程序博客网 时间:2024/05/22 04:29

6.4.4   局部内部类

    在TalkingClock示例中,TimePrinter这个类名字只在start方法中创建这个类型的对象时使用了一次.
    当遇到这类情况时,可以在一个方法中定义局部类.
public void start(){    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();}
    局部类不能用 public 或 private 访问说明符进行声明,它的作用域被限定在声明这个局部类的块中.
    局部类有一个优势,即对外部世界可以完全地隐藏起来.即使TalkingClock类中的其他代码也不能访问它.

6.4.5   由外部方法访问 final 变量

    与其他内部类相比较,局部类还有一个优点,它们不仅能够访问它们的外部类,还可以访问局部变量.不过那些局部变量必须被声明为 final,下面是一个典型的示例.这里,将TalkingClock构造器的参数interval和beep移至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();}
    请注意,TalkingClock类不再需要存储实例变量beep了,它只是引用start方法中的beep参数变量.
    这看起来好像没什么值得大惊小怪的,程序
if (beep) ...
    毕竟在start方法内部,为什么不能访问beep变量的值呢?仔细看一下调用流程:
    1.调用start方法
    2.调用内部类TimePrinter的构造器,以便初始化对象变量listener
    3.将listener引用传递给Timer构造器,定时器开始计时,start方法结束.此时start方法的beep参数变量不复存在.
    4.然后,actionPerformed方法执行 if(beep) ...
    为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份.实际上也是这样做的.
    局部类的方法只可以引用定义为 final 的局部变量.鉴于此情况,在列举的实例中,将beep参数声明为 final,对它进行初始化后不能够再进行修改,因此,就使得局部变量与局部类内建立的拷贝保持一致.

6.4.6   匿名局部类

    将局部内部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了.这种被被称为匿名内部类(annoymous inner class).
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可以是一个类,于是内部类就要扩展它.
    由于构造器的名字必须类名相同,而匿名类没有类名,所以,匿名类不能有构造器.取而代之的是,将构造器参数传递给超类的构造器.尤其是在内部类实现接口的时候,不能有任何构造参数.不仅如此,还要像下面这样提供一组括号:
new InterfaceType(){    methods and data}
    如果构造参数的闭圆括号跟着一个开花括号,正在定义的类就是匿名内部类.
    程序6-7包含了用匿名内部类实现语音时钟程序的代码,它与程序6-6相比,它的使用匿名内部类的解决方法比较简短,更切实际.
    anonymousInnerClass/AnonymousInnerClassTest.java如下所示:
package anonymousInnerClass;import javax.swing.*;public class AnonymousInnerClass{    public static void main(String[] args)    {        TalkingClock clock = new TalkingClock();        clock.start(1000, true);        // keep program running until user selects "Ok"        JOptionPane.showMessageDialog(null, "Quit program?");        System.exit(0);    }}
    AnonymousInnerClass/TalkingClock.java如下所示:
package anonymousInnerClass;import java.awt.*;import java.awt.event.*;import java.util.*;import javax.swing.*;import javax.swing.Timer;public class TalkingClock{    /**     * starts the clock     * @param interval the interval between messages(in milliseconds)     * @param beep true if the clock should beep     */    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();    }}
    运行结果如下所示:

6.4.7   静态内部类

    如果使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象.为此,可以将内部类声明为 static,以便取消产生的引用.
    下面是一个使用静态内部类的典型例子.考虑一下计算数组中最小值和最大值的问题.当然,可以编写两个方法,一个方法用于计算最小值,另一个方法用于计算最大值.在调用这两个方法的时候,数组被遍历两次.如果只是遍历数组一次,并且能够同时计算出最小值和最大值,那么就可以大大提高效率了.
double min = Double.MAX_VALUE;double max = Double.MIN_VALUE;for (double v : values){    if (min > v)        min = v;    if (min < 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定义为ArrayAlg的内部公有类.此后,通过ArrayAlg.Pair调用它:
ArrayAlg.Pair p = ArrayAlg.minmax(d);
    不过,与前面例子中所使用的内部类不同,在Pair对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为 static:
class ArrayAlg{    public static class Pair    {        ...    }}
    当然,只有内部类可以声明为 static .静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样.在列举的示例中,必须使用静态内部类,这是由于内部类对象是在静态方法中构造的:
public static Pair minmax(double[] d){    ...    return new Pair(min, max);}
    如果没有将Pair类声明为 static,那么编译器将会给出错误的报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象.
    注释:在内部类不需要访问外围类对象的时候,应该使用静态内部类.
    注释:声明在接口中的内部类自动称为 static 和 public 类.
    程序6-8包含ArrayAlg类和嵌套的Pair类的代码,staticInnerClass/StaticInnerClassTest.java如下所示:
package staticInnerClass;/** * This program demonstrates the use of static inner classes */public class StaticInnerClassTest{    public static void main(String[] args)    {        double[] d = new double[20];        for (int i = 0; i < d.length; i++)            d[i] = 100 * Math.random();        ArrayAlg.Pair p = ArrayAlg.minmax(d);        System.out.println("min = " + p.getFirst());        System.out.println("max = " + p.getSecond());    }}
    staticInnerClass/ArrayAlg.java如下所示:
package staticInnerClass;public class ArrayAlg{    /**     * A pair of floating-point numbers     */    public static class Pair    {        private double first;        private double second;        /**         * Constructor a pair from two floating-point numbers         * @param f the first number         * @param s the second number         */        public Pair(double f, double 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 a element is the maximum     */    public static Pair minmax(double[] values)    {        double max = Double.MIN_VALUE;        double min = Double.MAX_VALUE;        for (double v : values)        {            if (min > v)                min = v;            if (max < v)                max = v;        }        return new Pair(min, max);    }}
    运行结果如下所示:

6.5 代理

    利用代理(proxy)可以在运行时创建一个实现了一组给定接口的新类.这个功能只有在编译时无法确定需要实现哪个接口时才有必要使用.对于应用程序设计人员来说,遇到这种情况的机会很少.但对于系统程序设计人员来说,代理带来的灵活性非常重要.
    假设有一个表示接口的Class对象,它的确切类型在编译时无法知道.这确实有些难度.要想构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器.但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类.
    为了解决这个问题,有些程序将生成代码;将这些代码放置在一个文件中;调用编译器,然后再加载结果类文件.但这样做的速度会比较慢,并且需要将编译器与程序放在一起.而代理机制则是一种更好的解决方案.代理类可以在运行时创建全新的类.这样的代理类能够实现指定的接口.尤其是,它具有下列方法:
    指定接口所需要的全部方法
    Object类中的全部方法,例如,toString,equals等
    然而,不能在运行时定义这些方法的新代码,而是要提供一个调用处理器(invocation handler).调用处理器是实现了InvocationHandler接口的类对象.在这个接口中只有一个方法:
Object invoke(Object proxy, Method method, Object[] args);
    无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数.

0 0