Fork me on GitHub

Java8 接口

前言

Java 8已经推出相当长一段时间了,其中,接口部分有一些变化。我们来研究下它。

问题

我们知道,对于一个接口,如果我们声明好后,如果再想对其增加新的方法是困难的,因为我们要改变所有其实现类,也就是每个实现类都要对其新方法进行实现。如下图:

upload successful

这显然是不现实的,如果我们直接把方法写在实现类里,接口中没有此方法,就破坏了我们的多态性。

对于某些已经发布的应用,无论哪种做法都是比较繁重且不被推荐的。

接口默认实现

还好,Java大神们已经意识到了这个问题,于是在Java8中,引入了对接口的默认方法实现。

什么是默认方法实现呢?

简单来说,就是允许接口定义默认方法,在接口中需要有方法体的具体实现,实现类默认继承该方法(如果不重写默认方法的话)。同时为区分默认方法,默认方法在接口中采用default关键字标明。如下图:

upload successful

这样,如果我们新增一个接口方法(对于已经发布的接口),可以使用默认实现,就不会出现我们上述的问题。

思考

你一定会说,这和抽象类有什么区别呢?

当然还是有区别的,Java8以后,接口和抽象类的几点总结如下:

upload successful

新的问题

接口引入了默认方法后,就会有新的问题,好在Java已经替我们解决了,我们来看下。

情况一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface A {
default void doSomething(){
System.out.println("Interface A");
}
}
public interface B extends A{
default void doSomething(){
System.out.println("Interface B");
}
}
public class C implements A, B {
public static void main(String[] args) {
new C().doSomething();
}
}

结果:输出 Interface B

情况二

1
2
3
4
5
6
7
public class D implements A {
}
public class C extends D implements A, B {
public static void main(String[] args) {
new C().doSomething();
}
}

结果:输出 Interface B

情况三

如果D是这样呢?

1
2
3
4
5
public class D implements A {
public void doSomething(){
System.out.println("Class D");
}
}

结果:输出Class D

如果D不对doSomething提供实现(D为抽象的类),则C需要为doSomething提供实现。

情况四

如果B接口不在继承A接口。如下:

1
2
3
4
5
6
7
8
9
10
public interface A {
default void doSomething(){
System.out.println("Interface A");
}
}
public interface B{
default void doSomething(){
System.out.println("Interface B");
}
}

那么我们C类必须为doSomething提供实现,当然我们可以具体制定使用哪个接口的doSomething方法,如下:

1
2
3
4
5
6
public class C implements A, B {
@Override
public void doSomething() {
B.super.doSomething();
}
}

情况五

如果两个函数不一样但差距很小呢?如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface A {
default int doSomething(){
return 100;
}
}
public interface B{
default long doSomething(){
return 200;
}
}
public class C implements A, B {
public static void main(String[] args) {
new C().doSomething();
}
}

在IDEA里我们可以看到,类C是无法编译的,这是不被允许的。

情况六

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface A {
default void doSomething(){
System.out.println("Interface A");
}
}
public interface B extends A{
}
public interface C extends A{
}
public class D implements B,C {
public static void main(String[] args) {
new D().doSomething();
}
}

输出结果Interface A

因为只有A声明了一个默认方法,这个接口是D的父接口,故输出Interface A。如果B也提供了一个默认方法,签名和A一致,那么编译器会选择B的默认方法,如果B添加一个相同签名的抽象方法,则D需要为其提供实现,如果B,C都有相同签名的默认方法doSomething,则会出现冲突,需要我们为doSomething提供实现或者指定使用B,C中的哪个方法。

结论

解决问题的三条规则:

如果一个类使用相同的函数签名从多个地方(比如另一个类或者接口)继承了方法,通过三条规则可进行判断。

  1. 类中的方法优先级最高。类或者父类中声明的方法优先级高于任何声明为默认方法的优先级。

  2. 如果无法依据第一条判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,及如果B继承了A,那么B就比A更加具体。

  3. 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

SakuraTears wechat
扫一扫关注我的公众号
您的支持就是我创作的动力!
0%