从 Java8的default方法中,我们可以学到哪些优秀的设计思想?

你好,我是猿java。

对于Java程序员来说,Java8绝对是一个里程碑的版本,它为我们提供了很多优秀的设计思想和功能,比如 Lambda,StreamAPI,Optional,Default Method等,今天我们通过 Java8的 default method这个窗口,聊聊其中暗藏了哪些优秀的设计思想。

向后兼容

在Java编程中,我们一直主张面向接口编程,但是接口编程是一把双刃剑,它给我的编程带来了很多便利也会引进复杂度,比如,需要在接口中新增一个方法,该如何处理它的实现类?

我们能想到最直接的方法是:所有的实现类都修改一遍。

那么,JDK的工程师们也是这样操作的吗?

答案是No,聪明的JDK工程师们引入了一种叫default method的机制,所谓default method,是在方法前面使用default修饰符,它允许在接口中定义带有具体实现的方法(JDK8以前,接口中是不允许存在具体实现)。

为了更好地理解default method,我们以Java 8中 List接口引入的 sort方法为例进行讲解,源码截图如下:

img.png

default method允许接口在不破坏实现类的情况下扩展新方法,避免了对所有实现 List接口的类进行修改,而是可以在后续的版本迭代中,按需在实现类中去覆盖接口的默认方法,从而实现平滑的过渡以及提供更灵活的API设计。

多重继承问题

default method很好的解决了向后兼容的问题,但同时带来了多重继承的问题,那么,JDK的工程师们又是如何攻克这个难题的呢?

多重继承又叫做菱形继承问题,它是指在多重继承中,当一个类继承了两个类或接口,这两个类或接口又继承自同一个基类或接口时,可能会导致继承关系的不明确。由于 Java 不支持类的多重继承,但支持接口的多重继承,因此菱形继承问题主要发生在接口的多重继承中。

如下图,有一个接口 A,接口 B 和 C都继承自 A,而另一个类 D 同时实现了接口 B 和 C,这种继承关系形成一个菱形结构:

1
2
3
4
5
  A
/ \
B C
\ /
D

现在设定场景如下:

  • 接口 A定义了一个默认方法print()

  • 接口 B和 C都继承自 A,并且都重写了 print()方法

  • 类 D 实现了接口 B 和 C

  • 当类 D试图调用 print()方法时,会出现继承冲突,因为 D同时继承了 B 和 C,而这两个接口都提供了 print()方法的实现,这个时候就出现了菱形继承问题。

在 JDK中,解决菱形继承的做法是:明确选择,也就是说开发人员在编写代码时必须在类中显式地选择或覆盖默认方法,以解决潜在的冲突。

还是以上面的例子进行说明,当D试图调用 print()方法时,必须在代码中明确的说明是调用 B的 print()还是 C的 print()方法,如下代码:

1
2
3
4
5
6
7
8
class D implements B, C {
@Override
public void print() {
B.super.print();
// or
// C.super.print();
}
}

这样,多重继承问题(菱形继承问题)也就轻松化解。

代码重用

default method允许在接口中定义通用功能,多个实现类可以共享接口中的默认方法,从而避免在每个实现类中重复相同的代码。

另外,default method其实可以设计成模板方法模式,可以通过默认方法在接口中定义模板方法,提供默认的行为实现,然后允许具体实现类覆盖部分步骤。

优秀的设计思想

向后兼容

在日常开发中,如果我们使用的一些框架后面的版本不能兼容原来的版本,如果需要升级版本,那将会是一个很痛苦的事情,所以一个优秀的框架,向后兼容也是一个考核指标。

而 JDK在版本的向后兼容绝对是很优秀,除了仅有的几个版本存在小小的兼容问题,其他版本几乎可以做到无缝升级。

因此,我们在代码设计的时候,向后兼容很大程度上反应了代码的设计是否优秀。

逐步替换

在 java8引入的很多功能对老版本会有影响,但是 JDK并没有把这种升级在一个版本中一步到位,而在接口中引入新的默认方法,然后逐步在实现类中覆盖这些方法,从而实现平滑的过渡。

这种思维可以很好地借鉴到代码重构上面,一步到位的重构往往周期长,风险大,测试久,因此,我们可以把重构按阶段分而治之,从小到大,这样每次重构的风险和代价都是可控的。

代码重用

代码重用也是我们日常开发中一个老生常谈的问题,但是工程中有大量重复代码的问题一直都存在,特别是在业务开发的工程中,其实有很多方法可以重用,但是因为不了解历史或者害怕代码修改带来的风险,很多开发人员都是选择拷贝一份原来的方法,然后在里面做了一丁点的修改,因此造成了大量的代码重复。

总结

通过 Java的default method(默认方法),我们可以学到一个优秀框架很多值得借鉴的设计思想,比如,产品迭代过程中如何向后兼容,产品迭代过程产生的冲突该如何去权衡,以及代码开发中代码重用的重要性。

default method(默认方法),可以很好的衍生到模版模式,在日常开发中,我们可以把一些通用的流程定义成模版模式,然后实现其他类的共用。

一些优秀的开源框架,在迭代过程的取舍和权衡往往也是一道靓丽的风景线,我们应该多了解这种权衡和取舍背后的原理和收益,思考它们是否可以很好的借鉴到自己实际的项目中,如果每次都能多一点思考,相信自己离技术大牛也就越来越近了!

学习交流

如果你觉得本文章对你有帮助,感谢转发给更多的好友,关注我的公众号:猿java,为你呈现更多的硬核文章。

drawing