当前位置: 首页 > 科技观察

Java8默认方法与多重继承深入剖析

时间:2023-03-13 23:06:03 科技观察

之前常说的Java相对于C++的优势之一就是Java不存在多重继承的问题。因为Java中的子类只能继承(扩展)单个父类,虽然可以实现多个接口(implements),但是接口中只有抽象方法,方法体是空的,没有具体的方法实现,并且有不会有方法冲突的问题。这些都是流传已久的说法。自今年Java8发布后,接口中也可以定义方法(默认方法)了。之所以打破以往的设计,在接口中加入特定的方法,是为了给Java类库中已有的数千个类增加新的功能,而不必重新设计这些类。比如你只需要在Collection接口中添加默认的Streamstream(),对应的Set和List接口及其子类都包含这个方法,你不必为每个都重新拷贝这个方法子类。这是一个折衷的设计,问题在于它给Java引入了多重继承问题。我们知道接口可以继承接口,类可以继承类实现接口。一旦继承的类和实现的接口具有具有相同签名的方法,会发生什么?本文将探讨各种情况下的多重继承,让您清楚地了解Java多重继承的规则。接口继承多个父接口假设有三个接口InterfaceA,InterfaceB,InterfaceC,继承关系如下:+----------------++------------+|接口A||接口B|+------------^---++---^--------+||||||+-+----------+--+|InterfaceC|+------------+A,B有相同签名的默认方法defaultStringsay(Stringname),如果接口C没有override方法,会出现编译错误。interfaceA{defaultStringsay(Stringname){return"hello"+name;}}interfaceB{defaultStringsay(Stringname){return"hi"+name;}}interfaceCextendsA,B{}错误信息:C:/Lambda/src>javac-J-Duser.country=UScom/colobu/lambda/chapter3/MultipleInheritance1.javacom/colobu/lambda/chapter3/MultipleInheritance1.java:17:error:interfaceCinheritsunrelateddefaultsforsay(String)fromtypesAandBstaticinterfaceCextendsA,B{^1error我们可以在子接口C中覆盖这个方法,这样编译就不会出错:interfaceCextendsA,B{defaultStringsay(Stringname){return"greet"+name;}}注意方法签名不包括方法的返回值,即只有两个方法不同返回值签名也一样。下面的代码编译没有错误,因为A和B有不同的默认方法,而C隐式继承了两个默认方法。interfaceA{defaultvoidsay(intname){}}interfaceB{defaultvoidsay(Stringname){}}interfaceCextendsA,B{}但在某些情况下,即使是具有不同签名的方法也很难区分:interfaceA{defaultvoidsay(inta){System.out.println("A");}}interfaceB{defaultvoidsay(shorta){System.out.println("B");}}interfaceCextendsA,B{}staticclassDimplementsC{}publicstaticvoidmain(String[]args){Dd=newD();bytea=1;d.say(a);//B}Java会选择最合适的方法,请参考Java规范15.12。继承关系如下图所示,A2继承自A1,C继承自A2。+----------------+|InterfaceA1|+--------+------+|||+------+------+|InterfaceA2|+------+------+|||+------+------+|InterfaceC|+--------------+根据我们之前对类继承的理解,很容易知道C会继承A2的默认方法,包括直接定义的默认方法和重写的默认方法,以及从A1接口隐式继承的默认方法。interfaceA{defaultvoidsay(inta){System.out.println("A");}defaultvoidrun(){System.out.println("A.run");}}interfaceBextendsA{defaultvoidsay(inta){System.out.println("B");}defaultvoidplay(){System.out.println("B.play");}}interfaceCextendsA,B{}多层多继承上面的例子还是单继承的例子,如果如下图所示的多重继承呢?23456789101112131415+------------+|InterfaceA1|+------+------+|||+--------+------++----------------+|InterfaceA2||InterfaceB|+------+--------++---------+-----+|+--------+--------^||||+------+-------++|InterfaceC|+----------------+如果A2和B有相同签名的方法,这和第一个例子一样。如果不想编译出错,可以重写父接口的默认方法,或者调用指定父接口的默认方法:interfaceA1{defaultvoidsay(inta){System.out.println("A1");}}interfaceA2extendsA1{}interfaceB{defaultvoidsay(inta){System.out.println("B");}}interfaceCextendsA2,B{defaultvoidsay(inta){B.super.say(a);}}更复杂的多层多继承+-------------+|InterfaceA1|+-----+-----++|^+------+||+--------+------+||InterfaceA2||+------------+--+|^--++|||+--+------+-----+|InterfaceC|+----------------+接口A2继承A1,接口C继承A2和A1。代码如下,interfaceA1{defaultvoidsay(){System.out.println("A1");}}interfaceA2extendsA1{defaultvoidsay(){System.out.println("A2");}}interfaceCextendsA2,A1{}staticclassDimplementsC{}publicstaticvoidmain(String[]args){Dd=newD();d.say();}以上代码不会编译报错,运行并输出A2。可以看出接口C会隐式继承子接口的方法,也就是子接口A2的默认方法。类继承如果继承关系类型都是类,那么由于类仍然是单继承,不会有多继承的问题。混合类和接口如果我们用类替换第一个示例中的一个接口,会发生什么?+----------------++------------+|InterfaceA||ClassB|+------------+-++-----+-----+^-++--+-----^||+---+----+-+|ClassC|+----------+以下代码不会编译报错:interfaceA{defaultvoidsay(){System.out.println("A");}}staticclassB{publicvoidsay(){System.out.println("B");}}staticclassCextendsBimplementsA{}publicstaticvoidmain(String[]args){Cc=newC();c.say();//B}结果输出B。可见子类先继承了父类的方法,如果父类没有相同签名的方法,则继承接口的默认方法。结论一个比较复杂的继承关系可以简化为上面的继承关系。基于以上示例,可以得出以下结论:类优先于接口。如果一个子类继承了父类并且接口有相同的方法实现。那么子类继承父类的方法,子类中的方法优先于父类中的方法。如果以上条件均未满足,则必须显式覆盖/实现该方法,或将其声明为抽象方法。