【编程导论(Java)·4.3Java接口】
在【0.3.1 Java简单介绍】中,有这么一段话:“请注意:Java并不是作为教学语言设计的。世界各地的大学在讲授Java的过程中均遇到一些教学上的困难(如Java语言和API的高速升级),这些困难是计算机科学教育中一般性的挑战。”
Java8中引入的默认方法,充分展示了Java平台中概念的一致性与JDK向前兼容之间的矛盾,并且悲哀地。以牺牲概念的一致性而满足JDK向前兼容。
1.理想与现实
【以前】Java接口纯粹是契约的集合,是一种程序设计的表达方式。从数据抽象的角度看,可以在不定义class的同一时候定义type。将是程序设计中强大而实用的机制。Java接口就是这些纯粹的接口组成的数据抽象。
Java接口仅仅可以拥有抽象方法,它不涉及不论什么实现,也不能创建其对象(这一点和抽象类一致)。
多重继承模型导致额外的复杂性,当中最著名的是钻石问题或者叫“讨嫌的菱形派生”(Dreadful Diamond onDerivation、DDD)。为什么Java接口可以避免多继承的复杂性,关键在于它只包括abstract方法。
然而从设计的角度看,Java接口放弃了多继承的内在/固有目标。而显得是一个权宜之计。
【如今】Java8之前,接口不能升级。由于在接口中加入一个方法,会导致老版本号接口的全部实现类的中断。
λ表达式作为核心出现。为了配合λ表达式,JDK中Collection库须要加入新的方法。如forEach(),stream()等。于是引入了默认方法(defender methods,Virtual extension methods)。它是库/框架设计的程序猿的懊悔药。对于曾经的遗留代码。大家都不知道有这个新方法,既不会调用,也不会去实现。如同不存在。编写新代码的程序猿能够将它视为保底的方法体。类型层次中不论什么符合override规则的方法,优先于默认方法,由于遗留代码可能正好有相同的方法存在。
默认方法。理论上抹杀了Java接口与抽象类的本质差别——前者是契约的集合,后者是接口与实现的结合体。当然。语法上两者的差别和曾经一样。这就须要程序猿来自觉维护两者的本质差别。把默认方法作为库、框架向前兼容的手段。
2.抽象类 Vs 函数接口
在默认方法的使用上,还存在一种刻意的抹杀。
原本应该设计为抽象类,为了方便使用lambda,而设计成函数接口。
public abstract class AbstractClass {
public abstract void foo();
public final void m1() {
}
}
在AbstractClass类中,m()方法能够设计为final,而为了使用lambda,该方法变成了default方法——而在不论什么情况下,我们都不会override该default方法。
public interface NewInterface {
public abstract void foo();
public default void m1(){ }
}
JDK中非常多default方法,事实上就应该是某个抽象类的final方法;Java 8不同意我们在default方法前加上final。
为了使用lambda。这样真的好吗?我不知道怎样评价。
- final体现了我们的设计意图。default方法说明lambda表达式的目标类型须要这些方法(即使子类型不须要override)
- 抽象类包括接口和实现; 函数接口,为lambda作准备。
package defaultMethodDemo;
import static util.Print.pln;
public class Test {
public static void test(AbstractClass a){
a.foo();
}
public static void test(NewInterface f){
f.foo();
} public static void main(String[] args){
AbstractClass a = new AbstractClass(){
@Override public void foo(){pln("AbstractClass");}
};
test(a);
test(()->pln("NewInterface"));
}
}
还有一方面,假设一个抽象类由一个抽象方法加其它静态方法构成。则将它设计为函数接口,比較合理。比如例程11-1的IntSort。
2.钻石问题
【默认方法的一个优点:多继承的著名的是钻石问题(The Diamond Problem )再次须要关注。因而使曾经某些人觉得的“为了解决多继承问题而引入接口机制”的说法变成明显的错误——曾经也是错误的认识。
】
情况一:接口IA有子接口IB1、IB2,而类C implements IB1,IB2
package java8;
public class C implements IB1, IB2{
public static void main(String[] a) {
new C().m();
}
}
1)假设只A定义默认方法m()。运行IA的默认方法。
2)假设仅仅有IB1、IB2当中一个override IA定义的m(),调用“最详细接口”默认方法。
package java8;
public interface IB2 extends IA{
@Override default void m(){
System.out.println("IB2");
}
}
3)假设IB1、IB2都override IA定义的m(),而C不提供自己的方法体,则编译错误。由于 “最详细接口”默认方法有两个。
此时,C若Override m()。能够消去编译错误。
4)类C提供自己的方法体时,能够提供自己的代码,也能够指定调用C implements 的直接父接口的默认方法,代码例如以下:
package java8;
public class C implements IB1, IB2{
@Override public void m(){
IB2.super.m();
// IA.super.m();
};
public static void main(String[] a) {
new C().m();
}
}
小结:多个接口提供默认方法。则“最详细接口”默认方法胜出,可是不得出现多个“最详细接口”。
情况二:接口IA(或IB1)定义了默认方法m(),类A1同样的方法m(),类C是它们的子类型。
假设类A1提供了实现。依照a simple rule: “the superclass always wins.”)。父类的方法 被调用。
假设类A1不提供实现,即A1中m()为抽象方法,仍然依照the superclass always wins.类C须要override m()。给出自己的实现。
否则,要么 C声明为抽象类,要么编译错误。
小结:父类有默认方法的等价物,则默认方法如同不存在。