java中的介面懂多少
介面很多人都會寫,也明白它用在什麼地方,會產生哪些效果。特別是很多人一提到介面,就脫口而出規範、設計模式,那麼對於介面又瞭解多少呢?本章試著從java中介面的定義和使用出發,談談java中介面具有哪些特性。
一、介面
首先,瞭解介面interface就必須和類class區分開,為什麼這麼說呢?因為介面和類是兩個概念,但介面又和類具有一定的關係。有人不禁會問,怎麼說呢?類class是從java.lang.Object類派生而來,但介面interface並不是從某一個特定介面派生而來,兩個interface可能沒有任何交集,但兩個class一定存在交集。介面不是Object的子類,但介面卻隱士申明瞭Object中的所有可繼承方法,類似於Object子類繼承了它的所有可繼承方法,這裡申明的方法都是abstract形式,也即只有申明,沒有方法體block。
其次,介面的申明形式如下:
[annotation] [modifier] interface identitor [extends interfacelist] {
[public | static | ] [FieldConstant];
[public | abstract | ] [AbstractMethod];
[public | static | ] [MemberType];
}
其中,”[ ]”表示可有可無,而[ a | b ]表示從a或b中選擇一個。annotation表示介面的註解;modifier表示介面的修飾符,正如前面所說top interface只能是public, nested interface可是static,在class中還可以是protected或private;identitor表示該介面的名稱,一般取名時字首為”I” + interfaceName 或者 interfaceName + “able”;interfacelist表示繼承父介面列表,如Comparable, Serializable,Iterator等;FieldConstant表示介面中的欄位;AbstractMethod表示介面中的abstract方法;MemeberType表示介面中的成員,如class,interface。
二、interface種類
java將介面分為兩種:normal interface,annotation type。normal這個單詞不用解釋,既然有兩種,那麼他們肯定存在差異,也具有一定的共性。下面分別談談這兩種interface。
三、normal interface
通用介面又可以分為外層介面top interface、內嵌介面nested interface,也即是作為一個單獨的介面,還是被嵌入於類中、介面中的介面。它們的介面體body定義相同,唯一的區別在於修飾符的差異。nested interface的修飾符可以是static、public、protected或private(這兩者只能是作為class中的內嵌接口才能修飾,而作為top interface中的內嵌介面進行修飾,會編譯報錯)。
1、FieldConstant常量欄位
在介面中定義的欄位都被修飾符public static final預設進行了修飾,雖然可以顯示的新增public、static或final,但是建議不要新增,因為這是多餘的行為,可以新增只是說明欄位的修飾符。此處,還需注意一個問題,那就是常量的初始化問題,因為class中的常量可以在定義的時候進行初始化,也可以在建構函式中程序初始化,那麼問題是介面中的常量何時進行初始化?因為不能顯示的呼叫介面的初始化,所以常見在定義的時候就必須初始化,否則編譯器會丟擲初始化Error。其次,介面中的FieldConstant能否被子類繼承?可以,如果class實現了interface,那麼該介面的例項引用可以引用該介面中的常量欄位,如果subinterface繼承了該介面,那麼subinterface繼承了該介面的所有常量欄位。
public class Test implements ITest {
public static void main(String... args) {
ITest test = new Test();
//通過例項引用介面ITest的常量欄位
System.out.println(test.strTest); //hello world
//通過介面引用常量欄位
System.out.println(ITest.strTest); //hello world
//通過子介面引用常量欄位
System.out.println(ISubTest1.strTest); // hello world
}
}
public interface ITest {
//欄位常量在定義的時候必須進行初始化
String strTest = "hello world";
}
public interface ISubTest1 extends ITest {}
提問:介面中的常量欄位能否hide父介面中相同名稱的欄位?肯定可以!
2、AbstractMethod抽象方法
介面中定義的方法沒有方法體,並且修飾符為public abstract預設進行修飾,也就是可以給介面中的方法新增這兩種修飾符,但是建議不新增,完全是畫蛇添足,理由和常量欄位一樣。既然是方法,那麼就必然涉及到方法的重寫、過載以及實現問題。
public interface ITest {
public abstract testMethod();
//可以不用新增public abstract修飾符,編譯器預設新增
test();
}
- 重寫override
在class中重寫,子類通過重寫父類的方法來改變通過該方法名進行引用時,所具有的行為。那麼,interface的方法重寫是什麼樣的呢?除了方法體,方法重寫的簽名和返回值都和class中方法的重寫要求相同。
public interface ITest {
//定義一個待重寫的方法
Object test() throws IOException, ClassCastException;
String test2();
}
public interface ISubTest extends ITest {
//第一種,返回值型別是重寫方法的子型別
String test() throws IOException, ClassCastException;
//第二種,異常子句重寫
Object test() throws IOException;
String test() throws CLassCastException;
String test() throws EOFException;
//第三種,正常重寫
Object test();
String test(Object obj) throws IOException;
}
public class Test implements ISubTest {
//實現子介面的方法,如果子介面沒有重寫父介面的方法,則必須實現父介面的方法。也即,實現子介面申明的、繼承的方法。
//第一種,返回值型別是重寫方法的子型別
String test() throws IOException, ClassCastException {
return null;
}
//第二種,異常子句重寫
Object test() throws IOException {
return null;
}
String test() throws CLassCastException {
return null;
}
String test() throws EOFException {
return null;
}
//第三種,正常重寫
Object test() {
return null;
}
String test(Object obj) throws IOException {
return null;
}
//實現子介面繼承的方法
String test2(){
return null;
}
}
- 過載overload
過載相對於重寫來說沒那麼複雜,它所要求的是方法的簽名不同,所謂方法簽名是指方法的返回值和方法的引數組合,如String test(Object obj)的簽名signatrue為(Object)String。但是,這裡並不能根據返回值的不同來判定過載,而在JVM的底層中是根據signature來進行實現,只是編譯器進行了過濾處理。
public interface ITest {
//原始方法
String test();
//第一種,引數個數不同
String test(String str);
//第二種,引數的型別不同
String test(int a);
//第三種,引數的型別和個數不同
String test(String str1, String str2);
}
- 方法的實現
介面中方法的實現都必須反映到實現類中,同時允許class通過繼承類實現介面interface。此外,class實現implements介面,那麼該class必須實現介面中的所有abstract方法(後面會說JDK8的新特性)。
public class Test {
//父類中定義了test1, 記得實現時修飾符的全限必須是public
public void test1() {
}
}
class A extends Test implements ITest1, ITest2 {
//實現ITest2介面方法,記得實現時修飾符的全限必須是public
public String test2() {
return null;
}
}
interface ITest1 {
//方法被A的父類Test實現
void test1();
}
interface ITest2 {
//方法被A實現
String test2();
}
提問:介面中欄位和方法是否可以具有相同名稱?肯定可以!
3、MemberType成員型別
class中可以申明內部類innerClass、內部介面innerInterface和內部列舉innerEnum,介面也同樣可以,此時對於innerClass、innerInterface成員型別的預設修飾符為public static,那麼在定義時可以顯示的指定這兩個修飾符,但不建議。但innerEnum是內部列舉型別,此時它的預設修飾符為public static final,也就是說在介面中定義的列舉都是常量列舉。
public interface ITest {
//修飾符預設為public static
class NestedClass {
private int a;
NestedClass (int a) {
this.a = a;
}
}
//預設修飾符為 public static
interface INestedInterface {
}
//預設修飾符為public static final
enum NestedEnum {
RED, YELLOW,GREEN;
}
}
提問:介面中的欄位是否可以和成員型別具有相同的名字?可以;介面中的方法能否具有和成員型別相同的名字呢?可以。
4、內嵌介面NestedInterface
介面可以作為top interface的內嵌成員,也可以作為class中的內嵌成員。無論作為class還是interface的內嵌成員,其中介面體的定義內如如前所述,但是作為class的成員時,介面申明的修飾符還可以是protected 、private或者static。
四、介面中的defaut方法
考慮這樣一種場景,某個已釋出的jar中的某個介面需要整合一個新的功能函式,那麼此時需要往介面中新增一個abstract方法,而所有implements這個介面的class都必須實現這個abstract方法,如果採用一個介面介面卡來實現程式,也就需要在介面卡類中進行實現即可,其他情況是否就很麻煩?JDK8中在介面中引入了default方法,它是一個介面例項方法,可以用來繼承。那麼default方法和介面的abstract方法有什麼相同點和特性?具體體現在重寫、過載和實現。
public interface ITest {
//abstract抽象方法
void test();
//default預設方法
default void testDefault() {
System.out.println("hello world");
}
//@complile-error預設方法不能作為abstract方法的具體實現
/*default void test() {
}*/
}
- 重寫
方法重寫的方式就四種,default方法和普通的abstract方法沒有區別,都必須滿足下列四種情況之一:1、具有和父介面方法相同的返回值、名稱和丟擲異常;2、具有和父介面方法相同名稱、丟擲異常,但返回值型別是子型別;3、具有和父介面方法相同的名稱、返回值,但丟擲異常相容(子異常型別);4、具有和父介面方法相同的名稱,但返回值和丟擲異常相容。
但default方法不能重寫abstract方法,它只能重寫父類的default方法,同時default方法不能具有和abstract方法相同的名稱;
public interface ITest {
//抽象方法
Object test();
//預設方法
default Object testDefault() {
System.out.println("hello world");
}
}
public interface ISubTest extends ITest {
//@compile-error編譯錯誤
//預設方法不能作為abstract方法的重寫方法
/*default Object test() {
System.out.println("test");
}*/
//預設方法可以重寫預設方法
default String testDefault() {
System.out.prinln("hello world");
}
}