多重继承的问题及接口的引入

我们不难发现,在自然界中,很多时候父子关系并不是很显然的树型,而是图型。打个比方,机器狗应该从哪种对象继承呢?机器还是狗?
对于这种问题,C++提供了多重继承的概念,即一个类可以拥有多个父类。但很显然,这种情况虽然方便,但也带来了很多问题,例如不同父类的同名属性/方法的混淆,以及对共同父类多次调用带来的资源浪费(钻石继承问题)。
因此, Java 引入了接口的概念。

接口的实现

  1. public interface AD {
  2. //物理伤害
  3. public void physicAttack();
  4. }
  5. public class ADHero extends Hero implements AD{
  6. @Override
  7. public void physicAttack() {
  8. System.out.println("进行物理攻击");
  9. }
  10. }

ADHero 的父类是 Hero,但是他同时引用了接口 AD。
接口 AD 中有一个方法,但是这个方法是空的(很像抽象方法,是不是?),这依赖于引用它的类来具体实现。
注意,在 ADHero 中必须重写(下面会细讲)这个接口提供的方法,不然会报错。

接口的意义

接口可以理解为一种规范,一旦你引入了这个规范,你便必须遵守这个规范(完整的对规范提供的方法进行实现);与此同时,你实现了规范以后,也可以在其他遵守该规范的程序进行交互。
打个比方,我们对于一个类,需要实现大小比较的功能,但是 Java 不像 C++ ,并没有运算符重载的功能。幸运的是,我们可以采用官方库提供的一个接口,来实现比较功能。实现之后,我们不但可以进行类的比较,而且可以用于众多基于比较来实现的官方库,例如各种有序集合框架,排序,最大最小值等等。

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.Comparator;
  4. class Student implements Comparable<Student> { //引用官方的Comparable接口实现大小比较
  5. String name;
  6. int id, score, cnt;
  7. Student() {
  8. name = "";
  9. score = 0;
  10. cnt = 0;
  11. }
  12. //重写compareTo方法
  13. @Override
  14. public int compareTo(Student s) {
  15. if (score * s.cnt > s.score * cnt) return -1;
  16. else if (score * s.cnt < s.score * cnt) return 1;
  17. else {
  18. if (id > s.id) return 1;
  19. else if (id < s.id) return -1;
  20. else return 0;
  21. }
  22. }
  23. }
  24. public class Main {
  25. public static void main(String []args) {
  26. //新建一个ArrayList
  27. ArrayList<Student> stuList = new ArrayList<Student>();
  28. //一堆向ArrayList里面添加东西的操作
  29. //实现好接口,我们可以直接调用官方写好的排序,就不用我们自己写了
  30. Collections.sort(stuList);
  31. }
  32. }

默认方法

含义

从 JDK8 开始,接口里面的方法可以提供具体内容了,而非以前那样只能空着。如果一个类引用了接口但是没有重写该方法,那么调用的时候就会使用默认方法。

  1. interface animal {
  2. //传统,只提供定义,但不负责实现
  3. public void eat();
  4. public void sleep();
  5. //新版JDK,给出定义的同时还给出了默认实现
  6. default public void run() {
  7. System.out.println("I am running!");
  8. }
  9. }

优点

打个比方,我有一个接口很受欢迎,我自己写的大批 Java 程序都用了这个接口。
有一天,我突然打算更新它,在里面加上新方法。好家伙,难道我要一个个找到那些使用了它的类,然后一个个补上去吗?
这时候,有了默认方法,那么我们的原来程序依旧可以正常运行,不需要一个个给他们编写新方法的实现了。

缺点(我自己感觉的)

我的一个类调用了一个接口,因为代码太多,我忘了写好具体实现了。
搁以前的话,编译器会立刻报错,提醒我忘了重写接口的方法了。但是现在有了默认方法,很多接口都会提供,这样的话编译器就不会报错,正常运行,空留着我看着匪夷所思的运行结果,一脸茫然……为了避免这种情况,还是把编译器的编译警报等级调到最高吧(逃