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. 接口优于反射的更多相关文章

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

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

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

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

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

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

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

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

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

    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.考虑实现Comparable接口

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

随机推荐

  1. hdu1598 find the most comfortable road (枚举)+【并查集】

    <题目链接> 题目大意: XX星有许多城市,城市之间通过一种奇怪的高速公路SARS(Super Air Roam Structure---超级空中漫游结构)进行交流,每条SARS都对行驶在 ...

  2. unity3d俄罗斯方块源码教程+源码和程序下载

    小时候,大家都应玩过或听说过<俄罗斯方块>,它是红白机,掌机等一些电子设备中最常见的一款游戏.而随着时代的发展,信息的进步,游戏画面从简单的黑白方块到彩色方块,游戏的玩法机制从最简单的消方 ...

  3. myBatis之Clob & Blob

    1. 表结构 1.1 在Mysql中的数据类型,longblob  -->  blob, longtext --> clob 2. 配置文件, 请参考  myBatis之入门示例 3. L ...

  4. for循环以及数据类型

    一.for循环(迭代式循环) 了解:当我们在写代码时,如果代码是纯运算的代码,会占用大量的CPU,如果是I/O代码,则不会占用CPU. for i in range(10):  #可以是任意类型(字符 ...

  5. git 合并子工程

    一.关于合并代码合并带有子工程更改的代码1.先git merge master --no-ff origin/devlop(把develop分支代码合并到master) 解决冲突等 2.进入到子工程目 ...

  6. 校园网使用IPV6 tunnel免流量上网

    前段时间购买了一个vps,做梯子感觉不错,但是在校园网内,vps流量远超10块钱校园流量,眼看着上个月vps的流量被清零.但是校园网有免费的IPV6,而我的VPS也有个IPV6的地址,于是乎就想着如何 ...

  7. BZOJ3273 : liars

    枚举每个人,计算他必定是诚实者的情况下至少有几个人说谎,若超过$t$则他肯定是说谎者. 对于至少有几个人说谎,区间信息可以合并: 每个区间维护最左最右两个人$l,r$以及$f[i][j]$表示$l$和 ...

  8. js实现一个一个打印字体的功能

    var str = "ddll台湾八百壮士抗议苹果正式发邀请函西安铁警查倒票案自制航模逼停高铁林志玲遭老总熊抱拖拽游艇事故通报大马外交官被暗杀鹿晗又和邮筒合影奥迪男辱骂环卫工 " ...

  9. redis(一)

    NoSQL简介 NoSQL,全名为Not Only SQL,指的是非关系型的数据库 随着访问量的上升,网站的数据库性能出现了问题,于是nosql被设计出来 优点/缺点 优点: 高可扩展性 分布式计算 ...

  10. 将map中的值赋值给一个java对象

    Map tag=new HashMap(); tag.put("001"," 张三"); tag.put("002","李四&qu ...