Java 8 接口变化 - 静态方法、默认方法
Java 8 接口变化包括接口中的静态方法和默认方法。在 Java 8 之前,我们只能在接口中使用方法声明。但是从 Java 8 开始,我们可以在接口中使用默认方法和静态方法。
Java 8 接口
设计接口一直是一项艰巨的工作,因为如果我们想在接口中添加其他方法,就需要更改所有实现类。随着接口的老化,实现它的类的数量可能会增长到无法扩展接口的程度。这就是为什么在设计应用程序时,大多数框架都提供了一个基本实现类,然后我们扩展它并覆盖适用于我们应用程序的方法。让我们研究一下默认接口方法和静态接口方法,以及它们在 Java 8 接口更改中引入的原因。
Java 接口默认方法
为了在 Java 接口中创建默认方法,我们需要在方法签名中使用“ default ”关键字。例如,
package com.journaldev.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.journaldev.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.journaldev.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.journaldev.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.journaldev.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)
是一个简单的类方法,它不会覆盖接口方法。例如,如果我们将@Override 注释添加到 isNull() 方法,则会导致编译器错误。现在,当我们运行应用程序时,我们会得到以下输出。
Interface Null Check
Impl Null Check
如果我们将接口方法从静态改为默认,我们将得到以下输出。
Impl Null Check
MyData Print::
Impl Null Check
Java 接口静态方法仅对接口方法可见,如果我们从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 表达式。