Java核心技术第6章(4)

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对象和原始的调用参数.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值