默认方法是啥
默认方法是由default修饰符修饰,并像类中声明的其他方法一样包含方法体。是Java8中新添加的能力。
默认方法能干啥
可选方法
默认方法的主要应用场景在定义API上,可以避免临时往接口里面加方法导致的尴尬。
举一个身边的例子。比如Android升级的时候往接口里加了个方法,但是方法又不常用,升级的时候我们就不得不实现一下这个方法然后加一个空实现或者抛出一个未支持异常。但是现在有了默认方法,这种场景下Android就可以写一个默认方法,不用我们非得实现一个我们不会去使用的接口方法了。
总之,对于那些不是一定要实现的接口方法,我们可以避免在实现的时候刻意留白的情况出现。
函数式接口
之前讲过,函数式接口是只包含一个抽象方法的接口。这句话在以前不需要过多考虑,但是现在有了默认方法就不一样了。因为默认方法不是抽象方法,所以我们多些几个默认方法是不影响的。实际上现在的函数式接口Predicate、Function以及Comparator确实也引入了默认方法。
行为的多继承
当年学Java的时候,学到继承的位置需要特别注意的一点是Java不像C++一样支持行为多继承。现在不一样了,有了默认方法大家可以在接口里写实现了,这时实现多个接口我们自然就拥有它们各个接口的能力了。
利用这一点,我们可以把能够解决独立能力的通用代码抽成接口,并把需要的能力用默认方法实现。当我们想要用的这些能力的时候实现一下接口就可以了。这种方式与模版设计模式类似。
默认方法存在啥问题
方法名冲突
在默认方法之前如果实现两个有重名的方法的接口,对于重名的接口方法实现成一个就好了。但是现在有了默认方法,如果两个接口中有同名的默认方法,那么在同时实现这两个接口的时候会选择使用哪一个方法呢?这种情况有极低可能会出现,但还是要有规则来处理这种问题。
解决问题的三条规则
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
- 如果无法依据第一条进行判断,那么子接口的优先级最高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
- 最后,如果还是无法判断,继承了多个接口的类必须通过显示覆盖和调用期望的方法,显示的选择使用哪一个默认方法的实现。
以上三条规则就能够处理所有冲突的问题,下面举个例子说明一下。
示例一
public interface A {
default void hello() {
System.out.println("A");
};
}
public interface B extends A{
@Override
default void hello() {
System.out.println("B");
};
}
public class D implements A {
//未实现hello()
}
public class C extends D implements B, A {
public static void main(String... args) {
new C().hello();
}
}
看上面代码,首先根据规则1,类中声明的方法具有最高权限,但是类D中并未实现默认方法hello。这里如果类D实现了hello,那么他就拥有最高优先级了。接下来看规则2,看谁更加具体,因为B继承了A,那么B更加具体,所以打印出来的是"B"。
示例二
先上代码
public interface A {
default void hello() {
System.out.println("A");
};
}
public interface B {
default void hello() {
System.out.println("B");
};
}
public class C implements B, A {
public static void main(String... args) {
new C().hello();
}
}
这种情况会打印什么呢?答案是会抛出编译错误,因为无法判断出谁更具体一些。这个时候就需要显式调用了。
具体的方法是覆盖实现该方法,并且在方法中显式调用。关于显示调用,Java8中引入的新语法X.super.m(...),其中X是你希望调用的m方法所在的父接口。例如,上面的问题就可以像下面代码这样显示调用。
public class C implements B,A {
public static void main(String... args) {
new C().hello();
}
@Override
public void hello() {
A.super.hello();
}
}
最后
多说两句
虽然现在接口里也可以写实现了,抽象类和接口还是有区别的。首先类只能继承一个抽象类,但可以实现多个接口。其次抽象类可以定义变量,接口不行。
个人认为默认函数最大的意义还是便于API的编写,避免接口必须实现的痛苦。
最最后
打完,收工!不要问我为什么跳过了Stream,因为功能很强大,写起来太费劲了。欢迎大家讨论提出意见~~
欢迎关注【Funny新青年】微信公众号
网友评论