第6章 接口与内部类
接口
对象克隆
接口与回调
内部类
代理
6.1 接口
在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
接口使用interface关键字声明。
要将类声明为实现某个接口,需要使用关键字implements。
6.1.1 接口的特性
- 接口不能使用new运算符实例化。
- 可以使用instanceof检查一个对象是否实现了某个特定的接口。
- 接口可以被扩展,接口扩展接口使用extends关键字。
- 接口中的所有方法自动地属于public。
- 接口中不能包含实例域或静态方法,但却可以包含常量,接口中的域将被自动设为public static final。
- 每个类能够拥有一个超类,但却可以实现多个接口。
6.1.2 接口与抽象类
java中不存在多继承。
6.2 对象克隆
对于每个类,都需要做出下列判断:
- 默认的clone方法是否满足要求
- 默认的clone方法是否能够通过调用可变子对象的clone得到修补。
- 是否不应该使用clone.
实际上,选项3是默认的。如果要选择1或2,类必须:
- 实现Cloneable接口。
- 使用public 访问修饰符重新定义clone方法。
6.3 接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。
6.4 内部类
内部类(inner class)是定义在另一个类中的类。使用内部类的主要原因有以下三点:
内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
内部类可以对同一个包中的其他类隐藏起来。
当想要定义一个回调函数且不想编写大量代码是,使用匿名(anonymous)内部类比较便捷。
6.4.1 使用内部类访问对象状态
内部类既可以访问自身的数据域,也可以访问创建它的外围对象的数据域。
6.4.2 内部类的特殊语法规则
内部类使用外围类引用的语法OuterClass.this
在外围类的作用域之外,可以这样引用内部类OuterClass.InnerClass
6.4.3 内部类是否又用、必要和安全
内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
6.4.4 局部内部类
public void start(){ class TimePrinter implements ActionListener{ public void actionPerformed(ActionEvent event){
} } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval,listener); t.start(); } |
局部类有一个优势,即对外部世界可以完全地隐藏起来。除了声明它的方法之外,没有任何方法知道它的存在。
6.4.5 由外部方法访问final变量
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。
6.4.6 匿名内部类
假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。
public void start(int interval,final boolean beep){ ActionListener listener = new ActionListener(){ public void actionPerformed(ActionEvent event){ } } Timer t = new Timer(interval,listener); t.start(); } |
6.4.7 静态内部类
有时候,使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围对象。为此,可以将内部类声明为static,以便取消产生的引用。
6.5 代理
利用代理(proxy)可以在运行是创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。
代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有些列方法:
- 指定接口所需要的全部方法。
- Object类中的全部方法,例如,toString、equals等。
然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了InvocationHandler接口的类对象。
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
- 一个类加载器(class loader)。
- 一个Class对象数组,每个元素都是要实现的接口。
- 一个调用处理器。
package proxy; import java.lang.reflect.*; import java.util.*; /** * This program demonstrates the use of proxies. * @version 1.00 2000-04-13 * @author Cay Horstmann */ public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; // fill elements with proxies for the integers 1 ... 1000 for (int i = 0; i < elements.length; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler); elements[i] = proxy; } // 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]); } } /** * An invocation handler that prints out the method name and parameters, then * invokes the original method */ class TraceHandler implements InvocationHandler { private Object target; /** * Constructs a TraceHandler * @param t the implicit parameter of the method call */ public TraceHandler(Object t) { target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // print implicit argument System.out.print(target); // print method name System.out.print("." + m.getName() + "("); // print explicit arguments if (args != null) { for (int i = 0; i < args.length; i++) { System.out.print(args[i]); if (i < args.length - 1) System.out.print(", "); } } System.out.println(")"); // invoke actual method return m.invoke(target, args); } } |
代理类的特性
所有的代理类都扩展于Proxy类。
一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。
对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类。
Class proxyClass = Proxy.getProxyClass(null,interfaces);
代理类一定是public和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公共有的接口都必须属于同一个包。同时,代理类也属于这个包。
可以通过调用Proxy类中的isProxyClass检测一个特定的Class对象是否代表一个代理类。