Effective Java 第三版——65. 接口优于反射

时间:2023-01-05 01:11:42

Tips

书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code

注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

Effective Java 第三版——65. 接口优于反射

65. 接口优于反射

核心反射工具java.lang.reflect提供对任意类的编程访问。 给定一个Class对象,可以获得Constructor,Method和Field实例,这些实例表示由Class实例表示的类的构造方法,方法和属性。 这些对象提供对类的成员名称,属性类型,方法签名等的编程访问。

此外,Constructor,Method和Field实例允许反射性地操作它们的底层对应物:可以通过在Constructor,Method和Field实例上调用方法来构造实例,调用方法和访问底层类的属性。 例如,Method.invoke方法允许在任何类的任何对象上调用任何方法(受通常的安全性约束)。 反射允许一个类使用另一个类,即使在编译前者时后者类并不存在。 然而,这种能力是有代价的:

  • 失去了编译时类型检查的所有好处,包括异常检查。如果一个程序试图通过反射调用一个不存在的或不可访问的方法,会在运行时失败,除非采取了特殊的预防措施。

  • 执行反射访问所需的代码笨拙而冗长。写起来很乏味,读起来很困难。

  • 性能受损。反射方法调用比普通方法调用慢得多。到底慢了多少还很难说,因为有很多因素在起作用。在我的机器上,调用一个没有输入参数和返回int类型的方法时,反射方法执行要比普通方法慢11倍。

有一些复杂的应用程序需要反射。示例包括代码分析工具和依赖注入框架。即使是这样的工具,随着它的缺点变得越来越明显,也在逐渐远离反射。如果你对应用程序是否需要反射有任何疑问,那么它可能是不需要的。

通过以非常有限的形式使用反射,可以获得反射的许多好处,同时花费很少。对于许多必须使用在编译时不可用的类的程序,在编译时存在一个适当的接口或父类来引用该类( 条目64)。如果是这种情况,可以使用反射创建实例,并通过它们的接口或父类正常地访问它们

例如,这是一个创建Set<String>实例的程序,其实例的类由第一个命令行参数指定。 程序将剩余的命令行参数插入到集合中并打印它。 无论第一个参数如何,程序都会打印剩余的参数,并删除重复项。 但是,打印这些参数的顺序取决于第一个参数中指定的类。 如果指定java.util.HashSet,则它们以明显随机的顺序打印; 如果指定java.util.TreeSet,则它们按字母顺序打印,因为TreeSet中的元素是按顺序排序的:

// Reflective instantiation with interface access
public static void main(String[] args) {
// Translate the class name into a Class object
Class<? extends Set<String>> cl = null;
try {
cl = (Class<? extends Set<String>>) // Unchecked cast!
Class.forName(args[0]);
} catch (ClassNotFoundException e) {
fatalError("Class not found.");
}
// Get the constructor
Constructor<? extends Set<String>> cons = null;
try {
cons = cl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fatalError("No parameterless constructor");
} // Instantiate the set
Set<String> s = null;
try {
s = cons.newInstance();
} catch (IllegalAccessException e) {
fatalError("Constructor not accessible");
} catch (InstantiationException e) {
fatalError("Class not instantiable.");
} catch (InvocationTargetException e) {
fatalError("Constructor threw " + e.getCause());
} catch (ClassCastException e) {
fatalError("Class doesn't implement Set");
} // Exercise the set
s.addAll(Arrays.asList(args).subList(1, args.length));
System.out.println(s);
} private static void fatalError(String msg) {
System.err.println(msg);
System.exit(1);
}

虽然这只是一个演示程序,但它演示的技术非常强大的。演示程序可以很容易地变成泛型集合测试程序,通过积极地操纵一个或多个实例并检查它们是否遵守Set约定来验证指定的Set实现。 同样,它可以变成泛型集合性能分析工具。 事实上,这种技术足以实现一个成熟的服务提供者框架(service provider framework)(条目 1)。 通常,这种技术就是你在反射中所需要的。

这个例子说明了反射的两个缺点。 首先,该示例在运行时生成六个不同的异常,如果不使用反射实例化,则所有这些异常都是编译时错误。 (为了好玩,可以通过传入适当的命令行参数使程序生成六个异常中的每一个)。第二个缺点是需要25行繁琐的代码才能从其名称生成类的实例, 而构造函数方法使用一行代码即可。 可以通过捕获ReflectiveOperationException来减少程序的长度,该异常是Java 7中引入的各种反射异常的父类。这两个缺点仅限于实例化对象的程序部分。 实例化后,该集合与任何其他Set实例无法区分。 在真实的程序中,大量的代码因此不会受这种限定的反射使用的影响。

如果编译此程序,会获得未经检查的强制转换警告。 这个警告是合法的, 因此转换Class<? extends Set<String>> 也会成功,即使指定的集合不是Set接口的实现。在这种情况下,程序在实例化类时抛出ClassCastException异常。 要了解有关抑制警告的信息,请阅读条目 27。

反射的合法(如果很少)用途是管理类对运行时可能不存在的其他类、方法或属性的依赖关系。如果你正在编写一个必须针对其他包的多个版本运行的包,这将非常有用。该技术是根据支持包所需的最小环境(通常是最老的版本)编译包,并反射访问任何较新的类或方法。要使此工作正常进行,如果试图访问的新类或方法在运行时不存在,则必须采取适当的操作。适当的行动可能包括使用一些替代方法来完成相同的目标,或者使用简化的功能进行操作。

总之,反射是一种功能强大的工具,对于某些复杂的系统编程任务是必需的,但是它有很多缺点。如果编写的程序必须在编译时处理未知的类,则应该尽可能只使用反射实例化对象,并使用在编译时已知的接口或父类访问对象。

Effective Java 第三版——65. 接口优于反射的更多相关文章

  1. Effective Java 第三版—— 20&period; 接口优于抽象类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Effective Java 第三版——18&period; 组合优于继承

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. Effective Java 第三版——22&period; 接口仅用来定义类型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——39&period; 注解优于命名模式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. Effective Java 第三版——28&period; 列表优于数组

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  7. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  8. effective Java 第三版学习笔记

    创建对象类型的 1,静态工厂方法代替构造器 静态工厂方法有名称,不容易混乱他的作用 不必再每次调用他的时候创建实例,创建实例的代价是高的,可以重复利用缓存的对象 静态工厂甚至能返回子类对象,例如在接口 ...

  9. Effective Java 第三版——14&period;考虑实现Comparable接口

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. CSS3与页面布局学习笔记(六)——CSS3新特性(阴影、动画、渐变、变形&lpar; transform&rpar;、透明、伪元素等)

    一.阴影 1.1.文字阴影 text-shadow<length>①: 第1个长度值用来设置对象的阴影水平偏移值.可以为负值 <length>②: 第2个长度值用来设置对象的阴 ...

  2. Java:正则表达式的详解

    正则表达式:符合一定规则的表达式. 作用:用于专门操作字符串. 特点:用一些特定的符号来表示一些代码的操作.这样就简化书写.所以学习正则表达式就是学习一些特殊符号的使用. 好处:可以简化对字符串的操作 ...

  3. How to install JDK &lpar;Java Development Kit&rpar; on Linux

    This tutorial will guide you on how to install JDK (Java Development Kit) on Linux. Since I use Cent ...

  4. 解决Access restriction问题

    今天在导入一个包时报错了

  5. 在Eclipse中设置文件的默认打开方式

    在Eclipse中,我们可以设置jsp.xml.js.sql等文件默认打开方式: ①.打开配置选项 ②.找到文件设置 ③.选中我们要设置的文件,默认即可:

  6. monkey-----停止正在测试的monkey

    第一步:adb   shell ps | grep monkey:查找到正在测试的monkey包名   第二步:kill   pid:删除查找出的monkey进程   以上完美的停止monkey测试

  7. 大华门禁SDK二次开发(二)-SignalR应用

    经过与大华技术支持的沟通,门禁服务程序已经开发好了,可以正常接收门禁开关事件,可以发送开门命令.基于项目实时性要求,这里使用SignalR实现门禁状态.控制命令的实时传送. 几种场景需求 根据Sign ...

  8. 解决pycharm左侧项目文件名中文字体乱码情况?中文显示口口口口&period;

    解决pycharm左侧项目文件名中文字体乱码情况?中文显示口口口口. 点击file,进入settings 出现 Appearance & Behavior 点击Appearance UI Op ...

  9. C&num;生成树形结构泛型类

    C#生成树形结构泛型类,使用方法: ToTree<ShowMessageUpdatesTableTreeViewModel>.ToDo(models) public class ToTre ...

  10. create react app遇到的问题

    我现在想的是吧 static 资源和动态 api 来分开处理, static 资源开启 nginx 服务器,api 请求由 express 完成, 现在的问题是开发的时候 proxy 设定将所有的请求 ...