Core Java, Volume I 第 6 章 接口、lambda 表达式与内部类
发布日期:2021-06-29 02:39:28 浏览次数:2 分类:技术文章

本文共 6626 字,大约阅读时间需要 22 分钟。

▲ 接 口

▲ 接 口 示 例
▲ lambda 表 达 式
▲ 内 部 类
▲ 代 理

6.1 接口

6.2 接口示例

6.3 lambda 表达式

6.4 内部类

6.5 代理

在本章的最后, 讨论一下代理(proxy)。 利用代理可以在运行时创建一个实现了一组给定接口的新类 。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。对于应用程序设计人员来说, 遇到这种情况的机会很少。如果对这种高级技术不感兴趣,可以跳过本节内容。然而,对于系统程序设计人员来说,代理带来的灵活性却十分重要。

6.5.1 何时使用代理

假设有一个表示接口的 Class 对象(有可能只包含一个接口) ,它的确切类型在编译时无法知道。这确实有些难度。要想构造一个实现这些接口的类,就需要使用 newlnstance 方法或反射找出这个类的构造器。但是, 不能实例化一个接口,需要在程序处于运行状态时定义一个新类。

为了解决这个问题, 有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件。很自然, 这样做的速度会比较慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有下列方法:

  • 指定接口所需要的全部方法。
  • Object 类中的全部方法, 例如, toString、 equals 等。

然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器( invocation handler)。调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)

无论何时调用代理对象的方法,调用处理器的 invoke 方法都会被调用, 并向其传递Method 对象和原始的调用参数。 调用处理器必须给出处理调用的方式。

6.5.2 创建代理对象

要想创建一个代理对象, 需要使用 Proxy 类的 newProxylnstance 方法。 这个方法有三个参数:

  • 一个类加栽器(class loader) 。作为 Java 安全模型的一部分, 对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。有关类加载器的详细内容将在卷 II 第 9 章中讨论。目前, 用 null 表示使用默认的类加载器。
  • 一个 Class 对象数组, 每个元素都是需要实现的接口。
  • 一个调用处理器。

还有两个需要解决的问题。如何定义一个处理器? 能够用结果代理对象做些什么? 当然, 这两个问题的答案取决于打算使用代理机制解决什么问题。使用代理可能出于很多原因,例如:

  • 路由对远程服务器的方法调用。
  • 在程序运行期间,将用户接口事件与动作关联起来。
  • 为调试, 跟踪方法调用。

在示例程序中,使用代理和调用处理器跟踪方法调用,并且定义了一个 TraceHander 包装器类存储包装的对象。其中的 invoke 方法打印出被调用方法的名字和参数,随后用包装好的对象作为隐式参数调用这个方法。

class TraceHandler implements InvocationHandler{
private Object target; public TraceHandler(Object t) {
target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
// print method name and parameters ... // invoke actual method return m.invoke(target , args); }}

下面说明如何构造用于跟踪方法调用的代理对象。

Object value = ...;// construct wrapperInvocationHandler handler = new TraceHandler(value) ; // construct proxy for one or more interfacesClass[] interfaces = new Class[] {
Comparable.class};Object proxy = Proxy.newProxyInstance(null , interfaces, handler);

现在,无论何时用 proxy 调用某个方法, 这个方法的名字和参数就会打印出来, 之后再用 value 调用它。

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

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]);

在上述代码中,Integer 类实现了 Comparable 接口。代理对象属于在运行时定义的类(它有一个名字,如$Proxy0)。这个类也实现了Comparable接口。然而,它的 compareTo 方法调用了代理对象处理器的invoke方法。

注释:前面已经讲过,在Java SE 5.0中,Integer类实际上实现了Comparable<Integer>。然而,在运行时,所有的泛型类都被取消,代理将它们构造为原Comparable类的类对象。

binarySearch方法按下面这种方式调用:

if (elements[i].compareTo(key) < 0) …

由于数组中填充了代理对象,所以compareTo调用了TraceHandler类中的invoke方法。这个方法打印出了方法名和参数,之后用包装好的Integer对象调用compareTo。

最后,在示例程序的结尾调用:

System.out.println(elements[result]);
println 方法调用代理对象的toString,这个调用也会被重定向到调用处理器上。

下面是程序运行的全部跟踪结果:

500.compareTo(288)
250.compareTo(288)
375.compareTo(288)
312.compareTo(288)
281.compareTo(288)
296.compareTo(288)
288.compareTo(288)
288.toString()

可以看出,二分査找算法査找关键字的过程,即每一步都将査找区间缩减一半。注意,即使不属于Comparable接口,toString方法也被代理。在下一节中会看到,有相当一部分的Object方法都被代理。

程序清单 6-10 proxy/ProxyTest.java

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); }}

6.5.3 代理类的特性

现在,我们已经看到了代理类的应用。接下来了解它们的一些特性。需要记住,代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。

所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中,例如,在程序清单 6-10给出的程序中,代理Comparable对象时,TraceHandler包装了实际的对象。

所有的代理类都覆盖了Object类中的方法toString、equals和hashCode,如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clone和getClass)没有被重新定义。

没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。

对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类:

Class proxyClass = Proxy.getProxyClass(null, interfaces);

代理类一定是public和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。

可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。

API java.lang.reflect.InvocationHandler 1.3

  • Object invoke(Object proxy, Method method, Object[] args)
    定义了代理对象调用方法时希望执行的动作。

API java.lang.reflect.Proxy 1.3

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)

    返回实现指定接口的代理类。

  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)

    构造实现指定接口的代理类的一个新实例。
    所有方法会调用给定处理器对象的 invoke 方法。

  • static boolean isProxyClass(Class<?> cl)

    如果 cl 是一个代理类则返回 true。

到此为止,Java 程序设计语言的基础概念介绍完毕了。接口、lambda 表达式和内部类是经常使用的几个概念。然而,前面已经提到过,克隆和代理是库设计者和工具构造者感兴趣的高级技术, 对应用程序员来说,它们并不十分重要。 下面准备在第 7 章学习如何处理程序中的异常情况。

参考:

Java核心技术卷一 基础知识 第10版

转载地址:https://blog.csdn.net/yzpbright/article/details/105267398 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Thinking in Java 第14章 类型信息(type information)
下一篇:Thinking in Java 第20章 注解

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年04月01日 15时27分56秒