1.1 抽象类和接口的区别
在IT技术中,抽象类和接口都是重要的概念。它们都可以用来定义方法,但有一些关键的区别。
抽象类可以包含具体方法的实现,而接口只能指定方法的签名。抽象类只能被单继承,而一个类可以实现多个接口。此外,抽象类可以有构造函数,而接口不能有任何构造函数。当然,这些不是所有区别的详尽列表,但是这些是最基本和最常见的。
1.1.1. Abstract classes versus interfaces in Java 8
1.1.2. Conceptual Difference:
1.2 Interface Default Methods in Java 8
1.2.1. What is Default Method?
1.2.2. ForEach method compilation error solved using Default Method
1.2.3. Default Method and Multiple Inheritance Ambiguity Problems
1.2.4. Important points about java interface default methods:
1.3 Java接口静态方法
1.3.1. Java Interface Static Method, code example, static method vs default method
1.3.2. Important points about java interface static method:
1.4 Java函数式接口
1.1.1. Java 8中的抽象类与接口
Java 8接口的变化包括在接口中添加静态方法和默认方法。在Java 8之前,我们只能在接口中声明方法。但是从Java 8开始,我们可以在接口中定义默认方法和静态方法。
引入默认方法后,接口和抽象类似乎相同。但是,在Java 8中它们仍然是不同的概念。
抽象类可以定义构造函数。它们更有结构性并且可以与状态相关联。而默认方法则只能在调用其他接口方法的情况下实现,没有对特定实现状态的引用。因此,两者用于不同的目的,并且在两者之间进行选择取决于场景上下文。
1.1.2. 概念上的区别:
抽象类对于接口的骨架(即部分)实现是有效的,但是没有匹配的接口时不应存在。
因此,当抽象类有效降低为低可见性的接口骨架实现时,缺省方法是否也会被剥夺?明确:不会!实现接口几乎总是需要一些或所有这些类构建工具,而默认方法缺少这些工具。如果某个接口不需要这些工具,那么显然它是一个特殊情况,不应使您走上岔路。
1.2 Java 8中的接口默认方法
Java 8引入了“默认方法”或(Defender方法)新功能,允许开发人员向接口添加新方法,而不会破坏这些接口的现有实现。它提供了灵活性,允许接口定义实现,在具体类未提供该方法的情况下使用作为默认值。
让我们考虑一个小例子来理解它的工作原理:
public interface OldInterface {
public void existingMethod();
default public void newDefaultMethod() {
System.out.println("New default method"
+ " is added in interface");
}
}
以下Java类可在Java JDK 8中成功编译:
public class OldInterfaceImpl implements OldInterface {
public void existingMethod() {
}
}
如果您创建一个OldInterfaceImpl的实例:
OldInterfaceImpl obj = new OldInterfaceImpl ();
obj.newDefaultMethod();
默认方法永远不会被设为final,不能被同步并且不能覆盖Object类的方法。它们总是公开的,这严重限制了编写简短和可重用方法的能力。
默认方法可以被提供到接口中而不影响实现类,因为它包含一个实现。如果在接口中定义每个添加的方法都有实现,则没有任何实现类受到影响。实现类可以重写接口提供的默认实现。
默认方法使得可以在不破坏旧接口的实现的情况下向现有接口添加新功能。
当我们扩展包含默认方法的接口时,可以执行以下操作:
- 不覆盖默认方法并继承默认方法。
- 像覆盖子类中的其他方法一样覆盖默认方法。
- 重新声明默认方法为抽象方法,强制子类覆盖它。
1.2.2. 使用默认方法解决ForEach方法编译错误
对于Java 8,JDK集合已经扩展,forEach方法已添加到整个集合(与lambda一起使用)。用传统的方式,代码如下所示:
public interface Iterable<T> {
public void forEach(Consumer<? super T> consumer);
}
自从这个结果出现后,每个实现类都有编译错误,因此添加了一个默认方法,并需要实现,以便不改变现有的实现。
下面是带有默认方法的Iterable接口:
public interface Iterable<T> {
public default void forEach(Consumer
<? super T> consumer) {
for (T t : this) {
consumer.accept(t);
}
}
}
使用相同的机制将 Stream 添加到 JDK 接口中,而不会破坏实现类。
1.2.3. 默认方法和多继承模糊问题
由于 Java 类可以实现多个接口,每个接口都可以定义具有相同方法签名的默认方法,因此继承的方法可能会发生冲突。
考虑下面的示例:
public interface InterfaceA {
default void defaultMethod(){
System.out.println("Interface A default method");
}
}
public interface InterfaceB {
default void defaultMethod(){
System.out.println("Interface B default method");
}
}
public class Impl implements InterfaceA, InterfaceB {
}
上面的代码会出现以下错误而无法编译:
java: class Impl inherits unrelated defaults for defaultMethod() from
types InterfaceA and InterfaceB
为了修复这个类,我们需要提供默认方法实现:
public class Impl implements InterfaceA, InterfaceB {
public void defaultMethod(){
}
}
此外,如果我们想调用任何超级接口提供的默认实现而不是我们自己的实现,我们可以按照以下方式进行:
public class Impl implements InterfaceA, InterfaceB {
public void defaultMethod(){
InterfaceA.super.defaultMethod();
}
}
我们可以选择任何默认实现或两者都作为新方法的一部分。
1.2.4. Java接口默认方法的重要点:
1. Java接口默认方法将帮助我们扩展接口而不用担心破坏实现类。
2. Java接口默认方法弥合了接口和抽象类之间的差异。
3. Java 8接口默认方法将帮助我们避免实用程序类,例如所有集合类方法都可以在接口中提供。
4. Java接口默认方法将帮助我们删除基础实现类,我们可以提供默认实现,实现类可以选择覆盖哪个实现。
5. 引入接口默认方法的主要原因之一是增强Java 8中的Collections API以支持lambda表达式。
6. 如果层次结构中的任何类具有相同签名的方法,则默认方法变得无关紧要。默认方法不能覆盖java.lang.Object中的方法。原因很简单,因为Object是所有java类的基类。因此,即使我们在接口中定义了Object类方法作为默认方法,它也是无用的,因为始终会使用Object类方法。这就是为什么为了避免混淆,我们不能有覆盖Object类方法的默认方法。
7. Java接口默认方法也称为Defender方法或Virtual扩展方法。
资源链接:
1.
何时使用:Java 8+接口默认方法与抽象方法
2.
JDK 8时代的抽象类与接口
3.
通过虚拟扩展方法进行接口演变
1.3 Java接口静态方法
1.3.1 Java接口静态方法,代码示例,静态方法与默认方法
Java接口静态方法类似于默认方法,只是我们不能在实现类中覆盖它们。这个特性帮助我们避免在实现类中出现不良实现导致的意外结果。让我们用一个简单的例子来看看这个特性。
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()方法。
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()方法中,将导致编译器错误。
现在当我们运行应用程序时,我们会得到以下输出:
接口空检查
实现空检查
如果我们将接口方法从静态更改为默认,则会得到以下输出:
实现空检查
MyData打印:
实现空检查
Java接口静态方法仅对接口方法可见,如果我们从MyDataImpl类中删除isNull()方法,则无法将其用于MyDataImpl对象。但是像其他静态方法一样,我们可以使用接口静态方法使用类名。例如,一个有效的语句将是:
boolean result = MyData.isNull("abc")
1.3.2. Java接口静态方法的重要点:
- Java接口静态方法是接口的一部分,我们不能用它来实现类对象。
- Java接口静态方法适用于提供实用程序方法,例如null检查、集合排序等。
- Java接口静态方法通过不允许实现类覆盖它们来帮助我们提供安全性。
- 我们不能为Object类方法定义接口静态方法,否则会出现编译器错误“此静态方法无法隐藏来自Object的实例方法”。这是因为在Java中不允许这样做,因为Object是所有类的基类,我们不能有一个类级别的静态方法和另一个具有相同签名的实例方法。
- 我们可以使用Java接口静态方法来删除实用程序类,例如Collections,并将它的所有静态方法移动到相应的接口中,这样就容易找到并使用了。
1.4 Java功能接口
在结束本文之前,我想简要介绍一下功能接口。具有正好一个抽象方法的接口称为功能接口。
引入了一个新的注释@FunctionalInterface
来标记接口为功能接口。@FunctionalInterface
注释是避免在功能接口中意外添加抽象方法的一种方式。它是可选的,但使用它是一个好习惯。
Java 8的功能接口是期待已久的和迫切需要的特性,因为它使我们能够使用lambda表达式来实例化它们。引入了一个名为java.util.function的新包,其中添加了一堆功能接口,以提供lambda表达式和方法引用的目标类型。我们将在未来的帖子中深入研究功能接口和lambda表达式。
资源位置:
- Java 8接口变更 - 静态方法、默认方法