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不允许我们扩展多个类,因为这将导致“菱形问题”,编译器无法决定使用哪个超类方法。使用默认方法,菱形问题也会出现在接口中。因为如果一个类同时实现Interface1Interface2,并且不实现公共默认方法,编译器无法决定选择哪一个。扩展多个接口是Java的一个重要部分,你会在核心Java类以及大多数企业应用程序和框架中找到它。因此,为了确保接口中不会出现这个问题,必须为公共默认方法提供实现是强制性的。因此,如果一个类同时实现上述两个接口,它将不得不为log()方法提供实现,否则编译器将抛出编译时错误。一个简单实现了Interface1Interface2的类将是:

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接口默认方法的重要点:

  1. Java 接口默认方法将帮助我们扩展接口,而无需担心破坏实现类。
  2. Java 接口默认方法已经弥合了接口和抽象类之间的差异。
  3. Java 8 接口默认方法将帮助我们避免使用工具类,例如所有的 Collections 类方法可以直接在接口中提供。
  4. Java 接口默认方法将帮助我们消除基础实现类,我们可以提供默认实现,而实现类可以选择要覆盖的实现。
  5. 引入接口默认方法的一个主要原因是增强 Java 8 中的 Collections API 以支持 lambda 表达式。
  6. 如果层级中的任何类具有相同签名的方法,则默认方法将变得无关紧要。默认方法不能覆盖 java.lang.Object 中的方法。原因很简单,因为 Object 是所有 java 类的基类。因此,即使我们在接口中定义了 Object 类方法作为默认方法,它也是无用的,因为始终会使用 Object 类方法。为了避免混淆,我们不能有覆盖 Object 类方法的默认方法。
  7. Java 接口默认方法也称为 Defender Methods 或虚拟扩展方法。

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)是一个简单的类方法,它没有覆盖接口方法。例如,如果我们给isNull()方法添加@Override注释,会导致编译错误。现在当我们运行应用程序时,我们会得到以下输出。

Interface Null Check
Impl Null Check

如果我们将接口方法从静态改为默认,我们会得到以下输出。

Impl Null Check
MyData Print::
Impl Null Check

Java接口的静态方法只对接口方法可见,如果我们从MyDataImpl类中移除isNull()方法,我们将无法为MyDataImpl对象使用它。然而,像其他静态方法一样,我们可以使用接口静态方法来使用类名。例如,一个有效的语句将是:

boolean result = MyData.isNull("abc");

关于Java接口静态方法的重要点:

  1. Java接口的静态方法是接口的一部分,我们无法将其用于实现类对象。
  2. Java接口的静态方法非常适合提供实用方法,例如空值检查、集合排序等。
  3. Java接口的静态方法通过不允许实现类覆盖它们来帮助我们提供安全性。
  4. 我们不能为Object类的方法定义接口静态方法,否则会出现编译器错误,提示“This static method cannot hide the instance method from Object”(这个静态方法不能隐藏来自Object的实例方法)。这是因为在Java中不允许这样做,因为Object是所有类的基类,我们不能在一个类级别有一个静态方法,而在同一签名下有另一个实例方法。
  5. 我们可以使用Java接口的静态方法来删除实用类,比如Collections,并将所有静态方法移动到相应的接口中,这样更容易找到和使用。

Java函数接口

在我总结这篇文章之前,我想简要介绍一下函数接口。具有确切一个抽象方法的接口被称为函数接口。一个新的注解@FunctionalInterface已经被引入,用于标记一个接口为函数接口。@FunctionalInterface注解是一个工具,用于避免在函数接口中意外添加抽象方法。使用它是可选的,但是是个好习惯。函数接口是Java 8中期待已久的功能,因为它使我们能够使用lambda表达式来实例化它们。一个新的java.util.function包被添加,其中包含一堆函数接口,用于为lambda表达式和方法引用提供目标类型。我们将在未来的文章中深入探讨函数接口和lambda表达式。

Source:
https://www.digitalocean.com/community/tutorials/java-8-interface-changes-static-method-default-method