Java接口演变的历史

图片

多年来,Java中的接口已经有了很大的发展。 让我们看看它的开发过程中发生了什么变化。

原始界面


与现在的接口相比,Java 1.0中的接口非常简单。 它们只能包含两种类型的元素:常量和公共抽象方法。

常数场


接口可以像常规类一样包含字段,但有一些区别:

  • 字段必须初始化。
  • 字段被视为public static final
  • 不需要明确指定修饰符public,static和final(默认情况下它们是“放下”的)

public interface MyInterface { int MY_CONSTANT = 9; } 

即使未明确指定,MY_CONSTANT字段也被视为公共静态最终常量。 您可以添加这些修饰符,但这不是必需的。

抽象方法


接口最重要的元素是其方法。 接口方法也不同于常规类方法:

  • 方法没有身体
  • 方法实现由实现此接口的类提供。
  • 即使没有明确指定,方法也被认为是公共的和抽象的。
  • 方法不能是final的,因为Java中不允许abstract和final修饰符的组合

 public interface MyInterface { int doSomething(); String doSomethingCompletelyDifferent(); } 

套料


Java 1.1引入了可以放置在其他类中的类的概念。 这样的类有两种类型:静态和非静态。 接口还可以包含其他接口和类。

即使未明确指定,此类接口和类也被视为公共和静态的。

 public interface MyInterface { class MyClass { //... } interface MyOtherInterface { //... } } 

枚举和注释


Java 5中引入了另外两种类型:Enumerations和Annotations。 它们也可以放在接口内部。

 public interface MyInterface { enum MyEnum { FOO, BAR; } @interface MyAnnotation { //... } } 

通用类型


Java 5引入了泛型,泛型类型的概念。 简而言之:泛型允许您使用泛型类型而不是指定特定类型。 因此,您可以编写适用于不同数量类型的代码,而不会牺牲安全性,也无需为每种类型提供单独的实现。

在以Java 5开头的接口中,您可以定义一个通用类型,然后将其用作方法的返回值类型或方法的参数类型。

无论您使用Box接口来存储诸如String,Integer,List,Shoe或任何其他对象,该接口都有效。

 interface Box<T> { void insert(T item); } class ShoeBox implements Box<Shoe> { public void insert(Shoe item) { //... } } 

静态方法


从Java 8开始,您可以在接口中包括静态方法。 这种方法改变了界面为我们工作的方式。 现在,它们的工作方式与Java 8之前的工作方式截然不同。最初,接口中的所有方法都是抽象的。 这意味着该接口仅提供签名,但不提供实现。 该实现留给了实现您的接口的类。

在接口中使用静态方法时,还需要提供方法主体的实现。 要在接口中使用此方法,只需使用static关键字。 默认情况下,静态方法被视为公共方法。

 public interface MyInterface { // This works static int foo() { return 0; } // This does not work, // static methods in interfaces need body static int bar(); } 

静态方法继承


与常规静态方法不同,接口中的静态方法不会被继承。 这意味着,如果要调用这样的方法,则必须直接从接口而不是从实现它的类中调用它。

 MyInterface.staticMethod(); 

此行为对于避免多重继承问题非常有用。 假设您有一个实现两个接口的类。 每个接口都有一个具有相同名称和签名的静态方法。 首先应使用哪个?

为什么有用


想象一下,您有一个接口以及与实现此接口的类一起使用的整套帮助程序方法。

传统上,有一种使用伴随类的方法。 除了接口之外,还创建了一个实用程序类,其名称非常相似,其中包含属于该接口的静态方法。

您可以直接在JDK中找到使用此方法的示例:java.util.Collection接口和随附的实用程序类java.util.Collections。

对于接口中的静态方法,此方法不再适用,不再需要并且不建议使用。 现在,您可以将所有内容放在一个地方。

默认方法


默认方法与静态方法相似,因为您还必须为其提供一个主体。 要声明默认方法,只需使用default关键字。

 public interface MyInterface { default int doSomething() { return 0; } } 

与静态方法不同,默认情况下,方法是由实现接口的类继承的。 重要的是,此类在必要时可以重新定义其行为。

虽然有一个例外。 接口不能具有与Object类的toString,equals和hashCode方法具有相同签名的默认方法。 看一看Brian Goetz的答案,以了解这种解决方案的有效性: 允许默认方法覆盖Object的方法。

为什么有用


直接在接口中实现方法的想法似乎并不完全正确。 那么为什么首先引入此功能呢?

接口有一个问题。 一旦将您的API提供给其他人,它将永远被“石化”(不能轻易更改)。

按照传统,Java非常重视向后兼容性。 默认方法提供了一种使用新方法扩展现有接口的方法。 最重要的是,默认方法已经提供了特定的实现。 这意味着实现您的接口的类不需要实现任何新方法。 但是,如果有必要,如果默认方法的实现不再合适,则可以随时覆盖它们。 因此,简而言之,您可以为实现接口的现有类提供新功能,同时保持兼容性。

矛盾冲突


假设我们有一个实现两个接口的类。 这些接口具有具有相同名称和签名的默认方法。

 interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B { } 

现在,具有相同签名的相同默认方法将从两个不同的接口继承。 每个接口都有此方法的自己的实现。

那么,我们的班级如何知道要使用两种不同实现中的哪种?

他不会知道。 上面的代码将导致编译错误。 如果需要使其工作,则需要重写类中的冲突方法。

 interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B { // Without this the compilation fails @Override public int doSomething() { return 256; } } 

私人方法


随着Java 8的出现以及默认方法和静态方法的引入,接口现在不仅能够包含方法签名,而且还能够包含其实现。 在编写此类实现时,建议将复杂的方法分为更简单的方法。 这样的代码更易于重用,维护和理解。

为此,您将使用专用方法,因为它们可以包含所有不应从外部看到和使用的实现细节。

不幸的是,在Java 8中,接口不能包含私有方法。 这意味着您可以使用:

  1. 长期,复杂且难以理解的身体技巧。
  2. 界面中的辅助方法。 这违反了封装原理,并且污染了接口和实现类的公共API。

幸运的是,从Java 9开始,您可以在interfaces中使用私有方法 。 它们具有以下功能:

  • 私有方法有一个主体,它们不是抽象的
  • 它们可以是静态的也可以是非静态的
  • 它们没有被实现接口和接口的类所继承
  • 他们可以调用其他接口方法
  • 私有方法可以调用其他私有,抽象,静态或默认方法
  • 私有静态方法只能调用其他静态和私有静态方法

 public interface MyInterface { private static int staticMethod() { return 42; } private int nonStaticMethod() { return 0; } } 

年代顺序


以下是Java版本更改的时间顺序列表:

Java 1.1


嵌套类

嵌套接口

Java 5


通用类型

封闭转账

嵌套注释

Java 8


默认方法

静态方法

Java 9


私人方法

Source: https://habr.com/ru/post/zh-CN482498/


All Articles