Java 8系列之默认方法

来源:互联网 发布:access数据库实验心得 编辑:程序博客网 时间:2024/06/05 03:38

转载自: Java8:纠结的默认方法

【编程导论(Java)·4.3Java接口】

在【0.3.1 Java简介】中,有这么一段话:“中请注意:Java并非作为教学语言设计的。世界各地的大学在讲授Java的过程中均遇到一些教学上的困难(如Java语言和API的快速升级),这些困难是计算机科学教育中一般性的挑战。

Java8中引入的默认方法,充分展示了Java平台中概念的一致性与JDK向前兼容之间的矛盾,而且悲哀地,以牺牲概念的一致性而满足JDK向前兼容。

理想与现实

【曾经】Java接口纯粹是契约的集合,是一种程序设计的表达方式。从数据抽象的角度看,能够在不定义class的同时定义type,将是程序设计中强大而有用的机制。Java接口就是这些纯粹的接口组成的数据抽象。Java接口只能够拥有抽象方法,它不涉及任何实现,也不能创建其对象(这一点和抽象类一致)。

多重继承模型导致额外的复杂性,其中最著名的是钻石问题或者叫“讨嫌的菱形派生”(Dreadful Diamond onDerivation、DDD)。为什么Java接口能够避免多继承的复杂性,关键在于它仅仅包含abstract方法。然而从设计的角度看,Java接口放弃了多继承的内在/固有目标,而显得是一个权宜之计。

【现在】Java8之前,接口不能升级。因为在接口中添加一个方法,会导致老版本接口的所有实现类的中断。λ表达式作为核心出现,为了配合λ表达式,JDK中Collection库需要添加新的方法,如forEach(),stream()等,于是引入了默认方法(defender methods,Virtual extension methods)。它是库/框架设计的程序员的后悔药。对于以前的遗留代码,大家都不知道有这个新方法,既不会调用,也不会去实现,如同不存在;编写新代码的程序员可以将它视为保底的方法体。类型层次中任何符合override规则的方法,优先于默认方法,因为遗留代码可能正好有同样的方法存在。

默认方法,理论上抹杀了Java接口与抽象类的本质区别——前者是契约的集合,后者是接口与实现的结合体。当然,语法上两者的差别和以前一样。这就需要程序员来自觉维护两者的本质区别,把默认方法作为库、框架向前兼容的手段

抽象类 Vs 函数接口

在默认方法的使用上,还存在一种刻意的抹杀。原本应该设计为抽象类,为了方便使用lambda,而设计成函数接口。
[java] view plain copy
public abstract class AbstractClass {
public abstract void foo();
public final void m1() {
}
}
在AbstractClass类中,m()方法可以设计为final,而为了使用lambda,该方法变成了default方法——而在任何情况下,我们都不会override该default方法。
[java] view plain copy
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。

钻石问题

【默认方法的一个好处:多继承的著名的是钻石问题(The Diamond Problem )再次需要关注。因而使以前某些人认为的“为了解决多继承问题而引入接口机制”的说法变成明显的错误——以前也是错误的认识。

情况一:接口IA有子接口IB1、IB2,而类C implements IB1,IB2

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(),调用“最具体接口”默认方法;

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 的直接父接口的默认方法,代码如下:

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声明为抽象类,要么编译错误。

小结:父类有默认方法的等价物,则默认方法如同不存在。

0 0