JavaRush /Java 博客 /Random-ZH /Java 8 中的默认方法:它们能做什么和不能做什么?
Spitfire
第 33 级

Java 8 中的默认方法:它们能做什么和不能做什么?

已在 Random-ZH 群组中发布
Peter Verhas于 2014 年 4 月撰写的文章的翻译。 Java 8 中的默认方法:它们能做什么和不能做什么? - 1来自译者:术语“默认方法”刚刚出现在 Java 中,我不确定是否有一个已确定的俄语翻译。我将使用术语“默认方法”,尽管我认为它并不理想。我邀请您讨论更成功的翻译。

默认方法是什么

现在,随着 Java 8 的发布,您可以向接口添加新方法,以便接口与实现它的类保持兼容。如果您正在开发一个被从基辅到纽约的许多程序员使用的库,这一点非常重要。在 Java 8 之前,如果您在库中定义了接口,则无法向其中添加方法,否则会面临运行接口的某些应用程序在更新时崩溃的风险。那么,在 Java 8 中你可以不再害怕这个了吗?你不能。 向接口添加默认方法可能会使某些类无法使用。 让我们首先看看默认方法的好处。在Java 8中,该方法可以直接在接口中实现。(接口中的静态方法现在也可以实现,但那是另一回事了。)在接口中实现的方法称为默认方法,并由default关键字表示。如果一个类实现了一个接口,它可以(但不是必须)实现该接口中实现的方法。该类继承了默认实现。这就是为什么在更改类实现的接口时无需修改类。

多重继承?

如果一个类实现了多个(比如两个)接口,并且它们实现了相同的默认方法,事情就会变得更加复杂。该类将继承哪个方法?答案是否定的。在这种情况下,类必须实现该方法本身(直接或通过从另一个类继承它)。如果只有一个接口有默认方法,而在另一个接口中相同的方法是抽象的,则情况类似。Java 8 试图遵守纪律并避免出现模棱两可的情况。如果在多个接口中声明方法,则该类不会继承默认实现 - 您将收到编译错误。尽管如此,如果您的类已经编译,您可能不会收到编译错误。Java 8 在这方面还不够健壮。这是有原因的,我不想讨论(例如:Java 版本已经发布,讨论的时间已经过去了,总的来说,这不是他们的地方)。
  • 假设您有两个接口,并且一个类实现了这两个接口。
  • 其中一个接口实现了默认方法 m()。
  • 您编译所有接口和类。
  • 您可以通过将没有 m() 方法的接口声明为抽象方法来更改它。
  • 您仅编译修改后的接口。
  • 开始上课吧。
Java 8 中的默认方法:它们能做什么和不能做什么? - 2在这种情况下,类就可以工作了。您无法使用更新的接口编译它,但它是使用旧版本编译的,因此可以工作。现在
  • 使用抽象 m() 方法更改接口并添加默认实现。
  • 编译修改后的界面。
  • 运行类:错误。
当有两个接口提供方法的默认实现时,该方法不能在类中调用,除非该方法由类本身实现(同样,无论是自己实现还是从另一个类继承)。 Java 8 中的默认方法:它们能做什么和不能做什么? - 3类兼容。它可以加载修改后的界面。它甚至可以运行,直到调用在两个接口中都有默认实现的方法为止。

示例代码

Java 8 中的默认方法:它们能做什么和不能做什么? - 4为了演示上述内容,我为 C.java 类创建了一个测试目录,并为文件 I1.java 和 I2.java 中的接口创建了 3 个子目录。测试的根目录包含 C.java 类的源代码。基目录包含适合执行和编译的接口版本:接口 I1 有一个默认方法 m();I2 接口还没有任何方法。该类有一个方法main,因此我们可以执行它来测试它。它检查是否有任何命令行参数,因此我们可以轻松地执行它,无论是否调用m().
~/github/test$ cat C.java
public class C implements I1, I2 {
  public static void main(String[] args) {
    C c = new C();
    if( args.length == 0 ){
      c.m();
    }
  }
}
~/github/test$ cat base/I1.java
public interface I1 {
  default void m(){
    System.out.println("hello interface 1");
  }
}
~/github/test$ cat base/I2.java
public interface I2 {
}
您可以从命令行编译并运行该类。
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
兼容目录包含 I2 接口的一个版本,该版本将 m() 方法声明为抽象方法,并且出于技术原因,还包含 I1.java 的未修改副本。
~/github/test$ cat compatible/I2.java

public interface I2 {
  void m();
}
这样的集合不能用于编译 C 类:
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
       ^
1 error
错误信息非常准确。但是,我们有之前编译的 C.class,如果我们将接口编译到兼容目录中,我们将有两个仍然可用于运行该类的接口:
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
第三个目录--wrong包含版本I2,它也声明了方法m()
~/github/test$ cat wrong/I2.java
public interface I2 {
  default void m(){
    System.out.println("hello interface 2");
  }
}
您甚至不必担心编译。即使该方法被声明两次,该类仍然可以使用并运行,直到调用 m() 方法。这就是我们需要命令行参数的原因:
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
    at C.m(C.java)
    at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$

结论

当您将库移植到 Java 8 并更改接口以包含默认方法时,您可能不会遇到任何问题。至少,这是 Java 8 库开发人员在添加功能时所希望的。使用您的库的应用程序仍在 Java 7 中使用它,其中没有默认方法。如果多个库一起使用,就有可能发生冲突。如何避免呢?按照与以前相同的方式设计库的 API。不要因依赖默认方法的功能而沾沾自喜。他们是最后的手段。仔细选择名称以避免与其他接口冲突。让我们看看使用这个特性的Java开发将会如何发展。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION