但是如果我们想从接口A中调用默认实现方法foo(),而不是实现我们自己的方法,该怎么办呢?这是有可能的,引用A中的foo(),如下所示:
public class Clazz implements A, B {
public void foo(){
A.super.foo();
}
}
public void foo(){
A.super.foo();
}
}
现在我不能十分确信我喜欢这个最终方案。也许它比在签名里声明默认方法的实现更为简练,正如在默认方法规范的第一手稿里所声明的:
public class Clazz implements A, B {
public void foo() default A.foo;
}
public void foo() default A.foo;
}
但是这确实更改了语法,难道不是吗?它看起来更像一个接口的方法声明而不是实现。假若接口A和接口B定义了许多相互冲突的默认方法,而我愿意使用所有接口A的默认方法解决冲突,那又如何呢?目前我不得不一个接着一个的解决冲突,改写每一对冲突的方法。这可能需要大量的工作和书写大量的模板代码。
我估计解决冲突的方法需要进行大量的讨论,不过看起来创建者决定接受这无法避免的灾难。
真实的例子
默认方法实现的真实例子可以在 JDK8早期打的包中找到。回到集合的forEach方法的例子中, 我们可以发现在java.lang.Iterable接口中,它的默认实现如下:
@FunctionalInterface
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
forEach使用了一个java.util.function.Consumer功能接口类型的参数,它使得我们可以传入一个lambda表达式或者一个方法引用,如下:
List<?> list = …
list.forEach(System.out::println);
list.forEach(System.out::println);
方法调用
让我们看一下实际上是如何调用默认的方法的。如果你不熟悉这个问题,那么你可能有兴趣阅读一下Rebel实验室有关Java字节的报告。
从客户端代码的视角来看,默认的方法仅仅是常见的虚拟方法。因此名字应该是虚拟扩展方法。因此对于把默认方法实现为接口的简单例子类来说,客户端代码将在调用默认方法的地方自动调用接口。
A clazz = new Clazz();
clazz.foo(); // invokeinterface foo()
Clazz clazz = new Clazz();
clazz.foo(); // invokevirtual foo()
clazz.foo(); // invokeinterface foo()
Clazz clazz = new Clazz();
clazz.foo(); // invokevirtual foo()