Java8默认方法Default Methods原理及实例详解
这篇文章主要介绍了Java8默认方法DefaultMethods原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
Java8引入了新的语言特性——默认方法(DefaultMethods)。
Defaultmethodsenablenewfunctionalitytobeaddedtotheinterfacesoflibrariesandensurebinarycompatibilitywithcodewrittenforolderversionsofthoseinterfaces.
默认方法允许您添加新的功能到现有库的接口中,并能确保与采用旧版本接口编写的代码的二进制兼容性。
默认方法是在接口中的方法签名前加上了default关键字的实现方法。
一个简单的例子
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } } classClassAimplementsInterfaceA{ } publicclassTest{ publicstaticvoidmain(String[]args){ newClassA().foo();//打印:“InterfaceAfoo” } }
ClassA类并没有实现InterfaceA接口中的foo方法,InterfaceA接口中提供了foo方法的默认实现,因此可以直接调用ClassA类的foo方法。
为什么要有默认方法
在java8之前,接口与其实现类之间的耦合度太高了(tightlycoupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在lambda表达式作为java8语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backwardcompatibility)提供了途径。
String[]array=newString[]{ "hello", ",", "world", }; Listlist=Arrays.asList(array); list.forEach(System.out::println);//这是jdk1.8新增的接口默认方法
这个forEach方法是jdk1.8新增的接口默认方法,正是因为有了默认方法的引入,才不会因为Iterable接口中添加了forEach方法就需要修改所有Iterable接口的实现类。
下面的代码展示了jdk1.8的Iterable接口中的forEach默认方法:
packagejava.lang; importjava.util.Objects; importjava.util.function.Consumer; publicinterfaceIterable{ defaultvoidforEach(Consumeraction){ Objects.requireNonNull(action); for(Tt:this){ action.accept(t); } } }
默认方法的继承
和其它方法一样,接口默认方法也可以被继承。
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } } interfaceInterfaceBextendsInterfaceA{ } interfaceInterfaceCextendsInterfaceA{ @Override defaultvoidfoo(){ System.out.println("InterfaceCfoo"); } } interfaceInterfaceDextendsInterfaceA{ @Override voidfoo(); } publicclassTest{ publicstaticvoidmain(String[]args){ newInterfaceB(){}.foo();//打印:“InterfaceAfoo” newInterfaceC(){}.foo();//打印:“InterfaceCfoo” newInterfaceD(){ @Override publicvoidfoo(){ System.out.println("InterfaceDfoo"); } }.foo();//打印:“InterfaceDfoo” //或者使用lambda表达式 ((InterfaceD)()->System.out.println("InterfaceDfoo")).foo(); } }
接口默认方法的继承分三种情况(分别对应上面的InterfaceB接口、InterfaceC接口和InterfaceD接口):
不覆写默认方法,直接从父接口中获取方法的默认实现。
覆写默认方法,这跟类与类之间的覆写规则相类似。
覆写默认方法并将它重新声明为抽象方法,这样新接口的子类必须再次覆写并实现这个抽象方法。
默认方法的多继承
Java使用的是单继承、多实现的机制,为的是避免多继承带来的调用歧义的问题。当接口的子类同时拥有具有相同签名的方法时,就需要考虑一种解决冲突的方案。
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } } interfaceInterfaceB{ defaultvoidbar(){ System.out.println("InterfaceBbar"); } } interfaceInterfaceC{ defaultvoidfoo(){ System.out.println("InterfaceCfoo"); } defaultvoidbar(){ System.out.println("InterfaceCbar"); } } classClassAimplementsInterfaceA,InterfaceB{ } //错误 //classClassBimplementsInterfaceB,InterfaceC{ //} classClassBimplementsInterfaceB,InterfaceC{ @Override publicvoidbar(){ InterfaceB.super.bar();//调用InterfaceB的bar方法 InterfaceC.super.bar();//调用InterfaceC的bar方法 System.out.println("ClassBbar");//做其他的事 } }
在ClassA类中,它实现的InterfaceA接口和InterfaceB接口中的方法不存在歧义,可以直接多实现。
在ClassB类中,它实现的InterfaceB接口和InterfaceC接口中都存在相同签名的foo方法,需要手动解决冲突。覆写存在歧义的方法,并可以使用InterfaceName.super.methodName();的方式手动调用需要的接口默认方法。
接口继承行为发生冲突时的解决规则
值得注意的是这么一种情况:
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } } interfaceInterfaceBextendsInterfaceA{ @Override defaultvoidfoo(){ System.out.println("InterfaceBfoo"); } } //正确 classClassAimplementsInterfaceA,InterfaceB{ } classClassBimplementsInterfaceA,InterfaceB{ @Override publicvoidfoo(){ //InterfaceA.super.foo();//错误 InterfaceB.super.foo(); } }
当ClassA类多实现InterfaceA接口和InterfaceB接口时,不会出现方法名歧义的错误。当ClassB类覆写foo方法时,无法通过InterfaceA.super.foo();调用InterfaceA接口的foo方法。
因为InterfaceB接口继承了InterfaceA接口,那么InterfaceB接口一定包含了所有InterfaceA接口中的字段方法,因此一个同时实现了InterfaceA接口和InterfaceB接口的类与一个只实现了InterfaceB接口的类完全等价。
这很好理解,就相当于classSimpleDateFormatextendsDateFormat与classSimpleDateFormatextendsDateFormat,Object等价(如果允许多继承)。
或者换种方式理解:
classClassC{ publicvoidfoo(){ System.out.println("ClassCfoo"); } } classClassDextendsClassC{ @Override publicvoidfoo(){ System.out.println("ClassDfoo"); } } publicclassTest{ publicstaticvoidmain(String[]args){ ClassCclassC=newClassD(); classC.foo();//打印:“ClassDfoo” } }
这里的classC.foo();同样调用的是ClassD类中的foo方法,打印结果为“ClassDfoo”,因为ClassC类中的foo方法在ClassD类中被覆写了。
在上面的ClassA类中不会出现方法名歧义的原因是所谓“存在歧义”的方法其实都来自于InterfaceA接口,InterfaceB接口中的“同名方法”只是继承自InterfaceA接口而来并对其进行了覆写。ClassA类实现的两个接口不是两个毫不相干的接口,因此不存在同名歧义方法。
而覆写意味着对父类方法的屏蔽,这也是Override的设计意图之一。因此在实现了InterfaceB接口的类中无法访问已被覆写的InterfaceA接口中的foo方法。
这是当接口继承行为发生冲突时的规则之一,即被其它类型所覆盖的方法会被忽略。
如果想要调用InterfaceA接口中的foo方法,只能通过自定义一个新的接口同样继承InterfaceA接口并显示地覆写foo方法,在方法中使用InterfaceA.super.foo();调用InterfaceA接口的foo方法,最后让实现类同时实现InterfaceB接口和自定义的新接口,代码如下:
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } } interfaceInterfaceBextendsInterfaceA{ @Override defaultvoidfoo(){ System.out.println("InterfaceBfoo"); } } interfaceInterfaceCextendsInterfaceA{ @Override defaultvoidfoo(){ InterfaceA.super.foo(); } } classClassAimplementsInterfaceB,InterfaceC{ @Override publicvoidfoo(){ InterfaceB.super.foo(); InterfaceC.super.foo(); } }
注意!虽然InterfaceC接口的foo方法只是调用了一下父接口的默认实现方法,但是这个覆写不能省略,否则InterfaceC接口中继承自InterfaceA接口的隐式的foo方法同样会被认为是被InterfaceB接口覆写了而被屏蔽,会导致调用InterfaceC.super.foo()时出错。
通过这个例子,应该注意到在使用一个默认方法前,一定要考虑它是否真的需要。因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。滥用默认方法可能给代码带来意想不到、莫名其妙的错误。
接口与抽象类
当接口继承行为发生冲突时的另一个规则是,类的方法声明优先于接口默认方法,无论该方法是具体的还是抽象的。
interfaceInterfaceA{ defaultvoidfoo(){ System.out.println("InterfaceAfoo"); } defaultvoidbar(){ System.out.println("InterfaceAbar"); } } abstractclassAbstractClassA{ publicabstractvoidfoo(); publicvoidbar(){ System.out.println("AbstractClassAbar"); } } classClassAextendsAbstractClassAimplementsInterfaceA{ @Override publicvoidfoo(){ InterfaceA.super.foo(); } } publicclassTest{ publicstaticvoidmain(String[]args){ ClassAclassA=newClassA(); classA.foo();//打印:“InterfaceAfoo” classA.bar();//打印:“AbstractClassAbar” } }
ClassA类中并不需要手动覆写bar方法,因为优先考虑到ClassA类继承了的AbstractClassA抽象类中存在对bar方法的实现,同样的因为AbstractClassA抽象类中的foo方法是抽象的,所以在ClassA类中必须实现foo方法。
虽然Java8的接口的默认方法就像抽象类,能提供方法的实现,但是他们俩仍然是不可相互代替的:
- 接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。
- 接口中没有this指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法,无法保存状态(state),抽象方法中可以。
- 抽象类不能在java8的lambda表达式中使用。
- 从设计理念上,接口反映的是“like-a”关系,抽象类反映的是“is-a”关系。
接口静态方法
除了默认方法,Java8还在允许在接口中定义静态方法。
interfaceInterfaceA{ defaultvoidfoo(){ printHelloWorld(); } staticvoidprintHelloWorld(){ System.out.println("hello,world"); } } publicclassTest{ publicstaticvoidmain(String[]args){ InterfaceA.printHelloWorld();//打印:“hello,world” } }
其他注意点
- default关键字只能在接口中使用(以及用在switch语句的default分支),不能用在抽象类中。
- 接口默认方法不能覆写Object类的equals、hashCode和toString方法。
- 接口中的静态方法必须是public的,public修饰符可以省略,static修饰符不能省略。
- 即使使用了java8的环境,一些IDE仍然可能在一些代码的实时编译提示时出现异常的提示(例如无法发现java8的语法错误),因此不要过度依赖IDE。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。