Java 默认方法教程

在上一篇文章中,我们了解了 Lambda 表达式和函数式接口 。 现在,让我们继续进行讨论,并讨论另一个相关特性,即默认方法。 好吧,这对于 Java 开发人员来说确实是革命性的。 到 Java 7 为止,我们已经学到了很多关于接口的知识,而在编写代码或设计应用程序时,所有这些事情都已经牢记在心。 在引入默认方法之后,其中一些概念将从 Java 8 发生巨大变化。

I will discuss following points in this post:

What are default methods in java 8?
Why default methods were needed in java 8?
How conflicts are resolved while calling default methods?

Java 8 中的默认方法是什么?

默认方法使您可以向库的接口添加新功能,并确保与为这些接口的较早版本编写的代码二进制兼容。

顾名思义,java 8 中的默认方法就是默认的。 如果不覆盖它们,则它们是调用者类将调用的方法。 它们在接口中定义。

让我们看一个例子:

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

可移动接口定义方法move(); 并提供了默认实现。 如果有任何类实现此接口,则无需实现其自己的move()方法版本。 它可以直接调用instance.move();

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am moving

而且,如果类愿意定制行为,那么它可以提供自己的自定义实现并覆盖该方法。 现在将调用它自己的自定义方法。

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();
    }
}

Output: I am running

这还没有全部完成。 最好的部分是以下好处:

  1. 静态默认方法:您可以在接口中定义静态默认方法,这些方法将对实现该接口的所有类实例可用。 这使您可以更轻松地在库中组织帮助程序方法。 您可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中。 这使您可以在类之外定义方法,但仍可与所有子类共享。
  2. 它们为您提供了一种非常理想的特性,可以在不触及其代码的情况下为多个类添加功能。 只需在它们都实现的接口中添加默认方法即可。

为什么 Java 8 需要默认方法?

这是您下一个面试问题的不错的选择。 最简单的答案是在 Java 中启用 lambda 表达式的特性。 Lambda 表达本质上是函数式接口的类型。 为了无缝支持 lambda 表达式,必须修改所有核心类。 但是,这些核心类(例如java.util.List)不仅在 JDK 类中实现,而且在数千个客户端代码中也实现。 核心类中任何不兼容的更改都将肯定会产生反作用,并且根本不会被接受。

默认方法打破了这种僵局,并允许在核心类中添加对函数式接口的支持。 让我们来看一个例子。 以下是已添加到java.lang.Iterable的方法。

default void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action);
	for (T t : this) {
		action.accept(t);
	}
}

在 Java 8 之前,如果必须迭代 Java 集合,则将获得一个迭代器实例,并调用它的下一个方法,直到hasNext()返回false。 这是常见的代码,我们在日常编程中已经使用了数千次。 语法也总是相同的。 因此,我们可以使其精简吗,使其仅占用一行代码,仍然像以前一样为我们完成工作。 上面的函数可以做到这一点。

现在要迭代并对列表中的每个项目执行一些简单的操作,您需要做的是:

import java.util.ArrayList;
import java.util.List;

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());

        //Iterator code reduced to one line
        list.forEach((Moveable p) -> p.move());
    }
}

因此,这里,在不破坏列表的任何自定义实现的情况下,已将其他方法添加到List中。 长期以来,它一直是 Java 中非常需要的特性。 现在就在我们身边。

调用默认方法时如何解决冲突?

到目前为止,一切都很好。 我们的所有基础知识都很好。 现在转到复杂的事情。 在 Java 中,一个类可以实现 N 个接口。 另外,一个接口也可以扩展另一个接口。 如果在两个这样的接口中声明了默认方法,则该接口由单个类实现。 那么很明显,类会混淆调用哪个方法。

解决此冲突的规则如下:

1)最优选的是类中的覆盖方法。 如果在匹配任何内容之前找到它们,它们将被匹配并被调用。
2)选择在“最特定的默认提供接口”中具有相同签名的方法。 这意味着如果Animal类实现了两个接口,即MoveableWalkable,从而Walkable扩展了Moveable。 那么Walkable是最具体的接口,如果方法签名匹配,则将从此处选择默认方法。
3)如果MoveableWalkable是独立的接口,则会发生严重的冲突情况,并且编译器将抱怨并无法确定。 您必须通过提供额外的信息来帮助编译器,该信息应从哪个接口调用默认方法。 例如:

	Walkable.super.move();
	//or 
	Moveable.super.move();

这就是这里的全部内容。 下次,当我想到一些有趣的事情时,我将继续讨论。 不要忘记发表您的评论/想法或问题。