文章目录
- 1、简介
- 2、java 8中的默认方法是什么?
- 3、为什么java 8中需要默认方法?
- 4、调用默认方法时如何解决冲突?
- 5、注意事项
1、简介
Java 程序的接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供具体的实现,或者从父类中继承它。但是,一旦接口更新,现有的实体类为了适配接口也需要进行更新。
Java 8为了解决这一问题引入了一种新的机制。Java 8中的接口现在支持在声明方法的同时提供实现,通过两种方式可以完成这种操作:
- 第一,Java 8允许在接口内声明静态方法。
- 第二,Java 8引入了一个新功能,叫默认方法,通过默认方法你可以指定接口方法的默认实现。
接口能提供方法的具体实现。因此,实现接口的类不需要显式地提供该方法的具体实现,就可以自动继承默认的实现。
我将在这篇文章中讨论弄清楚以下几点:
- java 8中的默认方法是什么?
- 为什么java 8中需要默认方法?
- 调用默认方法时如何解决冲突?
2、java 8中的默认方法是什么?
顾名思义,java 8中的默认方法只是默认方法。 如果不覆盖它们,则它们是调用者类将调用的方法。
默认方法使您能够向接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。
public interface Moveable {
default void move(){
System.out.println("I am moving");
}
}
Moveable 接口定义了一个方法move(), 并提供了默认实现。 如果任何类实现此接口,则它不需要实现move()方法。 它可以直接调用instance.move()。
public class Animal implements Moveable {
public static void main(String[] args) {
Animal dog = new Animal();
dog.move();
}
}
如果我们要自定义行为,那么可以提供自定义实现并覆盖该方法。
public class Animal implements Moveable {
public void move() {
System.out.println("I am running");
}
public static void main(String[] args) {
Animal tiger = new Animal();
tiger.move();
}
}
3、为什么java 8中需要默认方法?
在 java 8 之前,接口与其实现类之间的耦合度太高了,当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
forEach 方法是 jdk 1.8 新增的接口默认方法,正是因为有了默认方法的引入,才不会因为 Iterable 接口中添加了 forEach 方法就需要修改所有 Iterable 接口的实现类。
下面的代码展示了 jdk 1.8 的 Iterable 接口中的 forEach 默认方法:
package java.lang;
import java.util.Objects;
import java.util.function.Consumer;
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
在java 8之前,如果迭代一个java集合,那么你将获得一个迭代器实例并调用它的下一个方法,直到hasNext()返回false。 这是常用代码,我们在日常编程中使用了数千次。 语法也总是一样的。
现在,我们可以使用紧凑的单行代码,实现跟之前一样的功能,如下:
public class Animal implements Moveable{
public static void main(String[] args){
List<Animal> list = new ArrayList();
list.add(new Animal());
list.add(new Animal());
list.add(new Animal());
list.forEach((Consumer<Moveable>) Moveable::move);
}
}
4、调用默认方法时如何解决冲突?
在java中,一个类可以实现N个接口。 另外,接口也可以扩展另一个接口。 如果有任何默认方法在两个接口中声明,这些接口由单个类实现。 很显然方法的调用会混淆,不清楚调用的是哪个类中方法。
interface A {
default void hello() {
System.out.println("Hello from A");
}
}
interface B extends A {
default void hello() {
System.out.println("Hello from B");
}
}
class C implements A, B {
public static void main(String[] args) {
new C().hello();
}
}
解决冲突的规则如下:
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。
1、类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
2、如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果 B 继承了 A ,那么 B 就比 A 更加具体。
3、最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
我们回顾上边的例子,例子中 C 类同时实现了 B 接口和 A 接口,而这两个接口恰巧又都定义了名为 hello 的默认方法。
编译器会使用声明的哪一个 hello 方法呢?
其实上面的代码是编译不通过的,按照规则(2),应该选择的是提供了最具体实现的默认方法的接口。
但,在C中不知道谁比谁更具体,所以需要显示的指定调用哪个接口的方法:
public class C implements A, B {
public static void main(String[] args) {
new C().hello();
}
@Override
public void hello() {
A.super.hello();
}
OR
@Override
public void hello() {
B.super.hello();
}
OR
@Override
public void hello() {
System.out.println("Hello from C!");
}
}
比如:调用 A.super.Hello(),那么打印的是 Hello form A!,调用 B.super.Hello() 那么输出的是 Hello from B!。
5、注意事项
1)default 关键字只能在接口中使用(以及用在 switch 语句的 default 分支),不能用在抽象类中。
2)接口默认方法不能覆写 Object 类的 equals、hashCode 和 toString 方法。
3)接口中的静态方法必须是 public 的,public 修饰符可以省略,static 修饰符不能省略。
参考资料:
https://howtodoinjava.com/java8/default-methods-in-java-8/
《Java 8 实战》