Java8特性之接口的默认方法与静态方法

时间:2022-02-07 19:31:52


简介

     Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。 它允许添加新方法到已有接口中,但是不会破坏那些基于老版接口实现的代码的二进制兼容性。 默认方法和抽象方法的区别在于:抽象方法是必须要实现的,而默认方法不是。

意义

众所周知,我们应该使用接口编程,接口使得在交互时不需要关注具体的实现细节,从而保持程序的松散耦合。在API的设计中,设计简约而清晰的接口非常重要。被称作固定定律的接口分离定律,其中有一条就讲到了应该设计更小的特定客户端接口而不是一个通用目的的接口。良好的接口设计是让应用程序和库的API保持简洁高效的关键。如果你曾有过接口API设计的经验,那么有时候你会感觉到为API增加方法的必要。

举个例子,假设你设计了一个简单的API Calculator,里面有add、subtract、devide和multiply函数。 为了实现Calculator这个接口,需要写如下一个BasicCalculator类。如果 Calculator这个API非常简单实用,其他开发者只需要创建一个BasicCalculator就可以使用这个API。 然而,对于多个开发者来说,事实上给人的感觉却是此API的用户并不是面向这个接口进行编程,而是面向这个接口的实现类在编程。
如果我们使用默认方法,这样,用户就被强制要求对Calculator接口进行编程,并且不需要关注接口的详细实现。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private interface
Defaulable {
     // Interfaces now allow default methods, the implementer may or
     // may not implement (override) them.
     default String notRequired() {
         return "Default implementation" ;
     }       
}
         
private static
class
DefaultableImpl implements Defaulable {
}
     
private static
class
OverridableImpl implements Defaulable {
     @Override
     public String notRequired() {
         return "Overridden implementation" ;
     }
}

Defaulable接口用关键字default声明了一个默认方法notRequired(),Defaulable接口的实现者之一DefaultableImpl实现了这个接口,并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。

Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:

1
2
3
4
5
6
private interface
DefaulableFactory {
     // Interfaces now allow static methods
     static Defaulable create( Supplier< Defaulable > supplier ) {
         return supplier.get();
     }
}

下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。

1
2
3
4
5
6
7
public static
void
main( String[] args ) {
     Defaulable defaulable = DefaulableFactory.create( DefaultableImpl:: new );
     System.out.println( defaulable.notRequired() );
         
     defaulable = DefaulableFactory.create( OverridableImpl:: new );
     System.out.println( defaulable.notRequired() );
}

这个程序的控制台输出如下:

1
2
Default
implementation
Overridden
implementation

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……

尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档