Java 8 接口变化 – 静态方法、默认方法
Java 8接口的变化包括在接口中引入了静态方法和默认方法。在Java 8之前,接口中只能有方法声明。但是从Java 8开始,接口中可以有默认方法和静态方法。
Java 8 接口
设计界面一直是一项艰巨的工作,因为如果我们想在界面中添加额外的方法,就必须修改所有实现这些界面的类。随着界面的老化,实现它的类的数量可能增长到无法扩展界面的程度。这就是为什么在设计应用程序时,大多数框架会提供一个基础实现类,然后我们可以继承它并重写适用于我们应用程序的方法。让我们来看一下Java 8界面更改中默认界面方法和静态界面方法的原因。
Java接口默认方法
在Java接口中创建默认方法,我们需要在方法签名中使用“default”关键字。例如,
package com.Olivia.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
请注意,log(String str) 是 Interface1 中的默认方法。现在,当一个类实现 Interface1 时,并不强制要求为接口的默认方法提供实现。这个特性可以帮助我们通过提供默认实现来扩展接口。假设我们有另一个接口,其中包含以下方法:
package com.Olivia.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
我们知道Java不允许我们继承多个类,因为这会导致“菱形问题”,编译器无法决定使用哪个超类的方法。通过默认方法,接口也会出现菱形问题。因为如果一个类同时实现了Interface1和Interface2,并且没有实现公共默认方法,编译器无法决定选择哪个方法。继承多个接口是Java的一个重要部分,它可以在核心Java类以及大多数企业应用程序和框架中找到。为了确保在接口中不会出现这个问题,必须强制提供接口的公共默认方法的实现。因此,如果一个类同时实现了上述接口,它将必须为log()方法提供实现,否则编译器将抛出编译时错误。一个简单的同时实现Interface1和Interface2的类如下:
package com.Olivia.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
关于Java接口默认方法的重要要点:
-
- Java接口的默认方法将帮助我们扩展接口,而不用担心破坏实现类。
-
- Java接口的默认方法消除了接口和抽象类之间的差异。
-
- Java 8接口的默认方法将帮助我们避免使用实用类,例如所有的Collections类方法都可以提供在接口本身中。
-
- Java接口的默认方法将帮助我们移除基础实现类,我们可以提供默认实现,而实现类可以选择哪个要重写。
-
- 引入接口默认方法的主要原因之一是增强Java 8中的Collections API以支持lambda表达式。
-
- 如果类层次结构中的任何一个类具有相同签名的方法,则默认方法变得无关紧要。默认方法不能覆盖来自java.lang.Object的方法。原因很简单,因为Object是所有Java类的基类。因此,即使我们在接口中定义了Object类的默认方法,它也是无用的,因为始终会使用Object类的方法。为了避免混淆,我们不能有覆盖Object类方法的默认方法。
- Java接口的默认方法也被称为Defender方法或虚拟扩展方法。
Java 接口的静态方法
Java接口的静态方法与默认方法类似,唯一的不同在于无法在实现类中重写它们。这个特性在避免实现类中的错误实现时能够帮助我们避免不必要的结果。让我们通过一个简单的例子来看这个问题。
package com.Olivia.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
现在让我们看一个有着糟糕实现的isNull()方法的实现类。
package com.Olivia.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
请注意,isNull(String str)是一个简单的类方法,而不是重写接口方法。例如,如果我们为isNull()方法添加@Override注解,将会导致编译错误。现在当我们运行应用程序时,我们会得到以下输出。
Interface Null Check
Impl Null Check
如果我们将接口方法从静态变为默认,我们将获得以下输出。
Impl Null Check
MyData Print::
Impl Null Check
如果从MyDataImpl类中删除isNull()方法,我们将无法将其用于MyDataImpl对象。然而,就像其他静态方法一样,我们可以使用类名来使用接口静态方法。例如,一个有效的语句将是:
boolean result = MyData.isNull("abc");
有关Java接口静态方法的重要要点是:
-
- Java接口的静态方法是接口的一部分,我们不能用它来实现类对象。
-
- Java接口的静态方法非常适合提供实用方法,例如空值检查,集合排序等。
-
- Java接口的静态方法在不允许实现类重写它们时,帮助我们提供安全性。
-
- 我们不能为Object类方法定义接口静态方法,否则将会得到编译器错误提示“此静态方法不能隐藏来自Object的实例方法”。这是因为在Java中不允许这样做,因为Object是所有类的基类,并且我们不能有一个类级别的静态方法和另一个具有相同签名的实例方法。
- 我们可以使用Java接口的静态方法来消除实用程序类,例如Collections,并将所有静态方法移动到相应的接口中,这样更容易找到和使用。
Java功能接口
在我结束这篇文章之前,我想先对功能接口做一个简要的介绍。只有一个抽象方法的接口被称为功能接口。引入了一个新的注解@FunctionalInterface,用于标记接口为功能接口。@FunctionalInterface注解是一种避免在功能接口中意外添加抽象方法的机制。这是可选的,但是使用它是一个好的习惯。功能接口是Java 8中期待已久、引人瞩目的特性,因为它使我们可以使用lambda表达式来实例化它们。新增了一个java.util.function包,其中包含了许多功能接口,为lambda表达式和方法引用提供了目标类型。我们将在未来的文章中深入探讨功能接口和lambda表达式。