一文读懂《Effective Java》第52条:通过接口引用对象_编程语言

国庆节快乐~

一般来讲,应该优先使用接口而不是类来引用对象。如果有合适的接口类型,那么对于参数、返回值、变量和域来说,都应该使用接口类型来进行声明。如果你养成了使用接口作为类型的习惯,你的程序将会更加灵活。

一文读懂《Effective Java》第52条:通过接口引用对象_js_02

接口作为引用

不推荐写法:

Vector subsribers = new Vector();

推荐写法:

List subsribers = new Vector();

List是Vector实现的接口,这里使用了List接口作为引用;这样当你决定更好实现时,比如说,我不需要Vector这种做了并发处理的工具类,想改用ArrayList,那么要做到的知识改变构造器中累的名称。

List subsribers = new ArrayList();

这样子,周围的所有代码的都可以继续工作,且不知道你已经将原来的实现类型变更了,对于这个变化是无感知的。

一文读懂《Effective Java》第52条:通过接口引用对象_js_02

接口作引用的注意事项

上面提到,将实现修改而周围代码无感知。

有一点值得注意:如果原来的实现提高了某种特殊的功能,而这种功能并不是抽象接口所提供的通用约定,并且周围代码又依赖这个功能。那么,很关键的是,新的实现也必须提供相同的功能才可以保证,修改实现的代码运行如初。

以上面为例,我们将实现从Vector 改为 ArrayList,而恰好场景属于高并发的场景,周围业务也依赖于Vector 的同步策略,而ArrayList 是线程不安全的工具类,那么我们得出结论:使用ArrayList 替代Vector 是不正确的。

所以,我们在写代码时,如果业务依赖于实现类的任何特殊属性,最好能够在变量的地方给这个需求建立文档说明

一文读懂《Effective Java》第52条:通过接口引用对象_抽象类_04

一文读懂《Effective Java》第52条:通过接口引用对象_js_02

接口引用的实现类为什么要修改呢?

很简单,因为新的实现类提供了更好的性能,或者因为它提供了期望得到的额外功能。

举个例子:ThreadLocal 类,在它的内部,这个类在Thread 中石油了一个包级私有的Map 域,将每个线程的值(per-thread values)与ThreadLocal 实例关联起来。

在java 1.3 发行版中,这个域初始化为HashMap 实例。

在java 1.4 发行版中,这个域初始化为IdentityHashMap 实例。(IdentityHashMap是专用的Map实现,感兴趣的同学可以自行在jdk中查看源码)

如此一改,将会让ThreadLocal 机制变快许多。

一文读懂《Effective Java》第52条:通过接口引用对象_抽象类_04

一文读懂《Effective Java》第52条:通过接口引用对象_js_02

接口作不适合作引用的3种情况

一文读懂《Effective Java》第52条:通过接口引用对象_设计模式_08

使用接口类型声明域“让程序员保持诚实”,因为凡是错误修改了实现类的地方,最终在编译阶段都会出错,从而减少后续运行时出现风险的代价。

但我们还是要清楚几个不适合使用接口作为引用的场景:

  • 如果没有合适的接口存在,完全可以用类而不是接口来引用对象。(比如,Random类,因为具体类没有关联的接口,别无他法了)
  • 对象属于一个框架,而框架基本类型就是类,不是接口。(对象属于基于类的框架,class-based framwork,就应该继续用相关的基类,往往是抽象类,来引用这个对象,而不是它的实现类)
  • 类实现了接口,但是它提供了接口不存在的额外方法。(例如:LinkedHashMap,如果程序依赖了这些额外的方法,这种类就一个只被用来引用它的实例)

一文读懂《Effective Java》第52条:通过接口引用对象_java_09

—END—

扫描二维码

获取知识干货

后台技术汇

一文读懂《Effective Java》第52条:通过接口引用对象_抽象类_10