Effective Java 第三版——65. 接口优于反射
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。
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. 接口优于反射的更多相关文章
- Effective Java 第三版—— 20. 接口优于抽象类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——18. 组合优于继承
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——22. 接口仅用来定义类型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——39. 注解优于命名模式
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——28. 列表优于数组
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- effective Java 第三版学习笔记
创建对象类型的 1,静态工厂方法代替构造器 静态工厂方法有名称,不容易混乱他的作用 不必再每次调用他的时候创建实例,创建实例的代价是高的,可以重复利用缓存的对象 静态工厂甚至能返回子类对象,例如在接口 ...
- Effective Java 第三版——14.考虑实现Comparable接口
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- 关于使用jmeter函数助手生成随机数的使用方法
记录自己的生活! 1.使用jmeter函数助手的生成随机数的方法,主要包含以下几个函数: [_Random] [_RandomString] 2.关于[_Random]函数的说 ...
- HDU 4370 0 or 1 (01规划)【Dijkstra】||【spfa】
<题目链接> 题目大意: 一个n*n的01矩阵,满足以下条件 1.X12+X13+...X1n=12.X1n+X2n+...Xn-1n=13.for each i (1<i<n ...
- Pytorch安装(基于anaconda虚拟环境)
Pytorch安装倒腾了一上午终于搞定,记录一下安装过程. 1. 首先尝试官网的安装方式,但是网速太慢了. 除去cudnn100, torchvision和pytorch三个文件,其余可以直接从清华镜 ...
- js日期处理函数 -- 判断闰年,获取当月的总天数、添加月份
1. 判断是否是闰年 function isLeapYear(eDate) { var year = eDate.getFullYear(); return (((0 == year % 4) &am ...
- ServiceNow在中国还有没有模仿者?
美国版的“ServiceNow”:https://www.servicenow.com 中国版的“ServiceHot” :http://www.itsmcn.com
- C# Work PPT to PDF
//// <summary> /// 把ppt文件转换成pdf文件2 /// </summary> /// <param name="sourcePath&qu ...
- logging模块、sys模块、shelve模块
一.logging模块 1.logging模块就是用于记录日志的,日志就是记录某个时间点,发生的事情. 2.记录日志是为了日后来复查,提取有用的信息. 3.如何去记录日志:可以直接打开文件,记录信息, ...
- redis -- python操作连接redis简单示例
1.先安装 redis,pyredis sudo pip install redis sudo pip install python-redis 2.示例: importredis >>& ...
- 什么是C++
1.到底什么是C++ C++是一门面向对象的程序设计语言. 关键词:程序设计语言,面向对象 程序设计语言是用来书写计算机程序的语言,更形象的说,程序设计语言是用来和计算机“交流的语言.” 面向对象(o ...
- Yii2 表单(form)
表单 1.表单的创建 在 yii 中主要通过 yii\widgets\ActiveForm 类来创建表单 ActiveForm::begin() 不仅创建了一个表单实例,同时也标志着表单的开始. 放在 ...