Java8新加入一个特性,允许在接口方法中给定一个默认实现。前提是在方法前面加一个default
关键字。
public interface InterfaceMethod {
default void say() {
System.out.println("hello");
}
default int getAge() {
return 1;
}
}
这种设计看起来是有点违背接口设计原则的,因为当初规定接口中的所有方法都是抽象方法,需要子类去实现它。
但是在Java8中不得不打破这种规则,Java设计者们也有苦衷,考虑到这样一种情况,如果要在接口中新增一个方法,那么之前已经实现它的那些类必然要实现新增的方法。如果接口实现类比较多的话那改起来会非常痛苦,这种牵一发而动全身的行为显然是不可取的。
于是Java8就有了接口默认方法这个特性,如此一来在接口中新增一个默认方法,对于其下面的实现类是无感知的。
Java系统中的接口默认方法非常多,比如一些函数式接口和集合接口就有定义它的默认方法,常见的有以下几个
java.util.function.Predicate.and(Predicate<? super T>)
java.util.function.Function.compose(Function<? super V, ? extends T>)
java.util.Collection.stream()
接口实现
接口中已经有默认方法后,其实现类不用实现这个默认方法
interface MyInterface {
default void say() {
System.out.println("hello");
}
}
public class MyClass implements MyInterface {
}
当然也可以覆盖重写这个默认方法
public class MyClass implements MyInterface {
@Override
public void say() {
System.out.println("goodbye");
}
}
如果要在覆盖的方法中调用原来的默认方法,格式如下:
接口名字.super.方法名
如:MyInterface.super.say();
public class MyClass implements MyInterface {
@Override
public void say() {
System.out.println("goodbye");
// 调用默认方法
MyInterface.super.say();
}
public static void main(String[] args) {
new MyClass().say();
}
}
多重继承问题
并列多继承
有了接口默认方法这个新特性后,会带来一些新的问题,假如两个接口A,B都有一个统一的默认方法 void say()
,如果一个类同时实现这两个接口会发生什么情况呢。
interface A {
default void say() {
System.out.println("A");
}
}
interface B {
default void say() {
System.out.println("B");
}
}
static class MyClass implements A,B {
}
很显然这种情况是不允许的,编译会报错。
如果把接口B的方法改为抽象方法,会发生什么情况呢
interface A {
default void say() {
System.out.println("A");
}
}
interface B {
void say();
}
static class MyClass implements A, B {
}
这个编译也是会报错的,提示需要实现接口B中的方法
static class MyClass implements A, B {
// 实现B
@Override
public void say() {
System.out.println("myclass");
}
}
这个时候如果调用say方法会调用MyClass中的实现方法
new MyClass().say();
输出myclass,如果想要调用接口A中默认方法,则可以这样写:
static class MyClass implements A, B {
@Override
public void say() {
// 调用A接口中的默认方法
A.super.say();
System.out.println("myclass");
}
public static void main(String[] args) {
new MyClass().say();
}
}
间接多继承
在上面的例子中,如果把接口B改成继承接口A,然后调用say方法会打印什么内容呢?
interface A {
default void say() {
System.out.println("A");
}
}
interface B extends A {
default void say() {
System.out.println("B");
}
}
static class MyClass implements A, B {
public static void main(String[] args) {
new MyClass().say();
}
}
输出B
为什么是B而不是A呢,这里Java列出三条规则,在遇到这种方法冲突的情况下,可参考这三条规则
- 类中的方法优先级最高
- 子接口的优先级最高,优先选择有具体实现的默认方法接口。
- 显示覆盖并调用指定接口的默认方法。
在上面的例子中,通过规则2
可以看到,B继承了A,那么B中的方法优先级最高,因此打印B。
那么第三条规则是什么意思呢,回到一开始提到的例子,并列继承
interface A {
default void say() {
System.out.println("A");
}
}
interface B {
default void say() {
System.out.println("B");
}
}
static class MyClass implements A, B {
}
这里编译报错,解决方式为重写say方法,并指定调用哪个接口中的默认方法:
static class MyClass implements A, B {
@Override
public void say() {
A.super.say();
}
}
菱形继承
菱形继承是C++
中遇到的一个问题,C++
支持多重继承,但是会存在一个问题。
有一个类A,A中有个say方法,B继承A,C继承A,然后MyClass继承了B和C,如下表示
A
╱ ╲
B C
╲ ╱
MyClass
继承图很像一个菱形,此时在如果调用myClass.say()
在C++
下会报request for member 'say' is ambiguous
错误。
那么在Java8下会是怎样的情况呢?一起来看下面的代码:
public class RhombusTest {
interface A {
default void say() {
System.out.println("A");
}
}
interface B extends A {}
interface C extends A {}
static class MyClass implements B,C {}
public static void main(String[] args) {
new MyClass().say();
}
}
上面的代码会编译通过并打印A。
根据规则2:子接口的优先级最高,优先选择有具体实现的默认方法接口。
B和C都拥有A的默认方法,有具体实现的默认方法在A当中,最终还是会选择A。此时如果在B中添加一个同样的方法
interface B extends A {
default void say() {
System.out.println("B");
}
}
那么根据就近原则,优先选择B。如果把B中的方法改成抽象方法,那么会编译报错,提示需要实现B中的方法,因为选择了B,但是没有具体实现。
interface B extends A {
void say();
}
总结
本篇主要介绍了Java8中的接口默认方法,以及在多重继承的情况下Java系统的判断规则。
欢迎关注作者微信公众号:猿敲月下码,第一时间获得技术分享