![Java无难事:详解Java编程核心思想与技术](https://wfqqreader-1252317822.image.myqcloud.com/cover/59/35011059/b_35011059.jpg)
6.7 接口的默认方法和静态方法
接口的默认方法和静态方法是Java 8新增的特性。
6.7.1 默认方法
前面已经介绍过,接口中的方法都是抽象的,某个类实现了接口,就要实现接口中的所有方法,如果没有完全实现接口中的方法,那么这个类就必须声明为抽象类。在接口和实现类都编写完毕后,如果需要在接口中新增一个方法,那么该接口的实现类也必须重新编码,以实现这个新增的方法。如果该接口的实现类还比较多,那么修改起来就比较痛苦了,为此,Java 8新增了接口的默认方法这一特性,允许你在接口中定义带有默认实现的方法,默认方法需要用default关键字来声明。为原有的接口添加新的默认方法,不会影响到现有的实现类。
我们可以给Animal接口添加两个默认方法,如代码6.23所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_37.jpg?sign=1739137928-NVchLAduCtrswb0pZBjAcK6TqY4zyqCP-0-7c92a2b264c7c38f1ba3bef8d353a9c5)
与接口中的普通方法一样,默认方法默认就是public访问权限,不同的是,默认方法有方法体。与我们通常所理解的“默认”代表一个有所区别,接口中的默认方法可以有多个。
在Animal接口添加默认方法后,并不会影响到现有的实现了Animal接口的类,前述的程序依然可以照常运行。
接口中的默认方法本身是有实现的,因此接口的实现类并不需要去实现这个默认方法,可以自动继承默认方法。
修改代码6.15,添加对默认方法的调用,如代码6.24所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_38.jpg?sign=1739137928-I1o5OLM51zQxNbbJ2paGl3X4i0sB3x1Y-0-f7957c806684c1813fd06e3508c0349f)
直接执行代码6.16中的Zoo类,程序输出结果是:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_39.jpg?sign=1739137928-5D3zkeLWgZ6PYN81D3blDHk7HSIJ9GjQ-0-0673b7c0b5a6ce3821591a84b0bb3f65)
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_40.jpg?sign=1739137928-8BBmNNNV3jfm5iOcjOcbqLKLAvJNfnb8-0-fceb6a3bf3264b2d33993770e4d349ca)
当然实现类也是可以重写接口的默认方法的。我们在Dog类和Cat类中重写Animal接口的默认方法desc,如代码6.25所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_41.jpg?sign=1739137928-iQUx3uwiDaB2NuMSTBt1uXp2t1zkS6Hf-0-e7b61f5b7e9f87b6a825d6c4658b0f60)
编译Animal.java,执行Zoo类,程序的输出结果是:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_42.jpg?sign=1739137928-H5I2DMn5P8VCSthWnKvEyU0rE1L1BvKd-0-71867dc35a06ba790e53f363c36e3b44)
我们知道,在子类中可以通过super关键字来调用父类被覆盖的方法,那么接口中被重写的默认方法能不能被调用呢?又要如何调用呢?答案是可以调用,不过需要采用特殊的语法格式:“接口名字.super.方法名”。
修改代码6.25中的Dog类,在重写的desc方法中调用Animal接口的默认方法desc,如代码6.26所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_43.jpg?sign=1739137928-pFWCEuWm2bUR6x3BSerhMoC70jgiyewA-0-d5895ab1afb45a9cbe77f0d4202fceb8)
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_44.jpg?sign=1739137928-ghTkUykPSCDIJqcouJVLDE61Bo5Y7gKE-0-f5f76a237900f5f3c6ca1df8fb83eb49)
编译Animal.java,执行Zoo类,程序的输出结果是:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_45.jpg?sign=1739137928-01Qeyp0iSDCXzhdGKljjHerySCWKNo5U-0-0b1677f8e4f8e4ae37d95a675af0311b)
接下来,我们给代码6.11的Flyable接口也添加一个默认方法desc,如代码6.27所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_46.jpg?sign=1739137928-oYLmU4C2WAY7gGlYvo1OPParoU4tH5Ox-0-6a2ecec8ace4334b5dd03795261425c7)
编译Flyable.java,再编译代码6.12的Bird.java,你会看到如图6-5所示的错误。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_47.jpg?sign=1739137928-BBiqJZOJhc55jvaiLtxDkVnkvIL7MdoN-0-07194f24617093158675a8b35a0d9854)
图6-5 类实现的两个接口中有同名的默认方法而出错
这是因为Animal接口有默认方法desc,而Flyable接口也有一个同名的默认方法desc,Bird类同时实现了这两个接口,如果Bird类的对象调用desc方法,那么应该调用哪个接口中的desc方法呢?无法确定,所以编译器在编译的时候就报出了错误。
这就是新增了接口默认方法特性后所带来的一个问题。在Java 8之前的接口方法都是抽象的,没有方法实现,方法实现是在实现类中给出的,因此不管类实现了几个接口,也不管这些接口中的方法是否同名,在程序中该方法的代码都只存在一份,在调用时根本不会存在二义性的问题。但默认方法是有方法实现的,不同的接口都有各自的实现,因此类在实现多个接口时,如果存在相同的默认方法,就无从选择了。
要解决这个问题,只能是在实现类中重写接口的默认方法,给出自己的实现,或者通过“接口名字.super.方法名”来调用指定接口的默认方法。修改代码6.12的Bird类,重写desc方法,分别给出两种解决方案的实现,如代码6.28和代码6.29所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_48.jpg?sign=1739137928-WTd1MGGkVLeVlNyxsCqSmiYGE1g93Kb4-0-882eb23ce4223fa9246d45b90d4c8f27)
由于引入了接口的默认方法,因而在接口继承和实现时,情况就会变得复杂,如代码6.30所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_49.jpg?sign=1739137928-nDSSZh26czrdnjqherJZwB6DAsYIzh8l-0-d336c35a67d3978078d8f228d546a4ff)
接口A有一个默认方法print,接口B扩展了接口A,同时也给出了一个同名的默认方法print,类InterfaceDefualtMethod同时实现了接口A和接口B,在main方法中调用idm.print()会输出什么结果呢?
执行该程序,可以看到结果是“B”。为什么是“B”不是“A”呢?这里要记住一个规则,如果一个接口继承了另外一个接口,两个接口中包含了相同的默认方法,那么继承接口(子接口)的版本具有更高的优先级。比如这里,B继承了A接口,那么优先使用B接口中的默认方法print。当然,如果实现类覆盖了默认方法,则优先使用实现类中的方法。
我们再看另外一种情况,在接口A中有一个默认方法,接口B和C都继承了A接口,然后一个类同时实现了接口B和C,如代码6.31所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_50.jpg?sign=1739137928-uMnL99a9JyAKTCkIQ1BuIE0NbDsqkWjQ-0-328f7cb3e9ad0722af60a01e9c63369e)
上述程序可以正常编译和执行,输出结果是“A”。可以发现,出现问题的情况,都是实现类继承了多个同名的默认方法,如果实现类中只存在一份默认方法的代码,那么情况就会变得简单,程序就不会出错,也不会有歧义。
6.7.2 静态方法
Java 8还为接口增加了静态方法特性,也就是说,现在可以在接口中定义静态方法,如代码6.32所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_51.jpg?sign=1739137928-FQ1MUoGdOjVeGhcnGeR8rebGBbyqucet-0-87c741460070f3221fc61f7521faca63)
与接口中的默认方法一样,静态方法默认也是public访问权限,而且也必须有方法体。
这里我们可能会有些疑问,Java 8新增的接口默认方法,可以解决给接口添加新方法而导致的已有实现类出现的问题,但新增的接口静态方法貌似和在类中直接定义静态方法没什么区别。实际上并非如此,在接口中定义的静态方法,只能通过该接口名来调用,通过子接口名或者实现类名来调用都是不允许的。修改代码6.32,让InterfaceStaticMethod类实现Math接口,同时在main方法中通过实现类的类名来调用add方法,如代码6.33所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_52.jpg?sign=1739137928-tz7Y4XgdlvuHjRzyxJcD1MAJWx1Gudjs-0-3b83456e1c2e3413d1e6a9c6e82c1ee3)
编译InterfaceStaticMethod.java,会提示如图6-6所示的错误。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_53.jpg?sign=1739137928-teV69Ry19SQRNxgpW4boU4rsPaSperNC-0-cee1e0d3564cffd165b5fecc19b88344)
图6-6 通过实现类的类名来调用接口中的静态方法导致出错
说明这和类中定义的静态方法调用还是有区别的,类中定义的静态方法是可以通过子类名来调用的。也就是说,实现接口的类或者子接口不会继承接口中的静态方法。
还要注意的是,默认方法不能同时是静态方法,即static关键字和default关键字不能同时使用。