Effective Java(3rd)-Item65 更喜欢接口

2018-09-18  本文已影响0人  难以置信的优雅

  核心反射设施,java.lang.reflect,提供对任意类的编程访问。给与一个Class对象,您可以获得表示类实例所表示的类的构造函数、方法和字段实例的构造函数、方法和字段。这些对象提供对类的成员名、字段类型、方法签名等的编程访问。
  此外,构造函数、方法和字段实例允许您反射性地操作它们的底层对应项:您可以通过调用构造函数、方法和字段实例上的方法来构造实例、调用方法和访问底层类的字段。例如,method.invoke允许您在任何类的任何对象上调用任何方法(受通常的安全约束)。反射允许一个类使用另一个类,即使在编译前者时后者并不存在。然而,这种力量是有代价的:

  例如,这是一个创建Set<String>实例的程序,其类由第一个命令行参数指定。程序插入将剩余的命令行参数放入集合并打印它。不管第一个参数是什么,程序都会打印剩余的参数,去掉重复项。然而,打印这些参数的顺序取决于第一个参数中指定的类。如果您指定java.util.HashSet,它们显然是随机排列的;如果您指定java.util.TreeSet,它们是按字母顺序打印的,因为TreeSet中的元素是排序的:


image.png

  虽然这个程序只是一个玩具,但它演示的技术非常强大。toy程序可以很容易地转换成一个通用的set tester,通过积极地操作一个或多个实例并检查它们是否遵守set契约来验证指定的set实现。类似地,它可以变成一个通用的集性能分析工具。事实上,该技术足够强大,可以实现一个成熟的服务提供者框架(item1 )。通常,这种技术就是您需要的反射方式。

  这个例子说明了反射的两个缺点。首先,该示例可以在运行时生成六个不同的异常,如果没有使用反射实例化,所有这些异常都将是编译时错误。(有趣的是,您可以通过传入适当的命令行参数,使程序生成六个异常中的每一个。)第二个缺点是,从类的名称生成类的实例需要25行冗长的代码,而构造函数调用恰好适合在一行中。过捕获ReflectiveOperationException (Java 7中引入的各种反射异常的超类),可以减少程序的长度。这两个缺点都局限于实例化对象的程序部分。实例化后,集合与任何其他集合实例不可区分。在实际的程序中,大量的代码因此不受这种有限的反射使用的影响。
  如果编译此程序,将得到未检查的强制转换警告。这个警告是合法的,即使指定的类不是Set实现,extended Set<String>>也会成功,在这种情况下,程序在实例化类时抛出ClassCastException。要了解如何抑制警告,请阅读第27项。
  反射的合法(如果很少)用途是管理类对运行时可能不存在的其他类、方法或字段的依赖关系。如果您正在编写一个必须针对其他包的多个版本运行的包,这将非常有用。该技术是根据支持包所需的最小环境(通常是最老的版本)编译包,并反射性地访问任何较新的类或方法。要使此工作正常进行,如果您试图访问的新类或方法在运行时不存在,则必须采取适当的操作。适当的行动可能包括使用一些替代方法来完成相同的目标,或者使用减少的功能进行操作。
  总之,反射是一种功能强大的工具,对于某些复杂的系统编程任务是必需的,但是它有很多缺点。如果编写的程序必须在编译时处理未知的类,则应该尽可能只使用反射实例化对象,并使用在编译时已知的接口或超类访问对象。

本文写于2019.7.19,历时1天

上一篇下一篇

猜你喜欢

热点阅读