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