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不允許我們擴展多個類,因為這將導致“Diamond Problem”,其中編譯器無法決定使用哪個超類方法。對於接口,使用默認方法也會引起這種“Diamond Problem”。因為如果一個類同時實現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 表達式 來實例化它們。一個新的 package java.util.function
與一系列功能接口一起被添加,以提供 lambda 表達式和方法引用的目標類型。我們將在未來的文章中深入探討功能接口和 lambda 表達式。