一、接口的概念
JAVA是只支持單繼承的,但現實之中存在多重繼承這種現象,如“金絲猴是一種動物”,金絲猴從動物這個類繼承,同時“金絲猴是一種值錢的東西”,金絲猴從“值錢的東西”這個類繼承,同時“金絲猴是一種應該受到保護的東西”,金絲猴從“應該受到保護的東西”這個類繼承。這樣金絲猴可以同時從 “動物類”、“值錢的東西類”、“應該受到保護的東西” 這三個類繼承,但由於JAVA只支持單繼承,因此金絲猴只能從這三個類中的一個來繼承,不能同時繼承這三個類。因此為了封裝現實生活中存在的多重繼承現象,為了實現多繼承,可以把其中的兩個類封裝成接口。使用接口可以幫助我們實現多重繼承。
接口的本質——接口是一種特殊的抽象類,這種抽象類里面只包含常量和方法的定義,而沒有變量和方法的實現。
抽象類所具有的一些東西接口可以具有,假如一個抽象類里面所有的方法全都是抽象的,沒有任何一個方法需要這個抽象類去實現,並且這個抽象類里面所有的變量都是靜態(static)變量,都是不能改變(final)的變量,這時可以把這樣的抽象類定義為一個接口(interface)。把一個類定義成一個接口的格式是把聲明類的關鍵字class用聲明接口的關鍵字interface替換掉即可。
1 /** 2 * java中定義接口 3 */ 4 public interface JavaInterfaces { 5 6 }
接口(interface)是一種特殊的抽象類,在這種抽象類里面,所有的方法都是抽象方法,並且這個抽象類的屬性(即成員變量)都是聲明成“public static final 類型 屬性名”這樣的,默認也是聲明成“public static final”即里面的成員變量都是公共的、靜態的,不能改變的。因此在接口里面聲明常量的時候,可以寫成“public static final 類型 常量名=value(值)”這樣的形式,也可以直接寫成“類型 常量名=value(值)”如:“public static final int id=10”可以直接寫成“int id=10”這樣的形式,因為在接口里面默認的屬性聲明都是“public static final”的,因此“public static final”可以省略不寫。在接口里面聲明的抽象方法可以不寫abstract關鍵字來標識,因為接口里面所有的方法都是抽象的,因此這個“abstract”關鍵字默認都是省略掉的,如在一個接口里面聲明這樣的三個方法:“public void start()”、“public void run()”、“public void stop()”這三個方法前面都沒有使用abstract關鍵字來標識,可它們就是抽象方法,因為在接口里面的聲明的方法都是抽象方法,因此在接口里面的抽象方法都會把abstract關鍵字省略掉,因為默認聲明的方法都是抽象的,所以就沒有必要再寫“abstract”字了,這一點與在抽象類里面聲明抽象方法時有所區別,在抽象類里面聲明抽象方法是一定要使用“abstract”關鍵字的,而在接口里面聲明抽象方法可以省略掉“abstract”。注意:在接口里面聲明的抽象方法默認是“public(公共的)”的,也只能是“public(公共的)”之所以要這樣聲明是為了修正C++里面多重繼承的時候容易出現問題的地方,C++的多繼承容易出現問題,問題在於多繼承的多個父類之間如果他們有相同的成員變量的時候,這個引用起來會相當地麻煩,並且運行的時候會產生各種各樣的問題。JAVA為了修正這個問題,把接口里面所有的成員變量全都改成static final,成員變量是static類型,那么這個成員變量就是屬於整個類里面的,而不是專屬於某個對象。對於多重繼承來說,在一個子類對象里面實際上包含有多個父類對象,而對於單繼承來說,子類對象里面就只有一個父類對象。多繼承子類對象就有多個父類對象,而這些父類對象之間可能又會存在有重復的成員變量,這就非常容易出現問題,因此在JAVA里面避免了這種問題的出現,采用了接口這種方式來實現多繼承。作為接口來說,一個類可以從接口繼承(或者叫實現接口),這也是多繼承,接口里面的成員變量不專屬於某個對象,都是靜態的成員變量,是屬於整個類的,因此一個類去實現多個接口也是無所謂的,不會存在對象之間互相沖突的問題。實現多個接口,也就實現了多重繼承,而且又避免了多重繼承容易出現問題的地方,這就是用接口實現多重繼承的好處。
二、接口特性
2.1.接口舉例
1 package javastudy.summary; 2 3 /** 4 * 這里定義了接口:Painter。 在Painter接口里面定義了paint()和eat()這兩個抽象方法。 5 * 6 * @author gacl 7 * 8 */ 9 interface Painter { 10 public void eat(); 11 12 public void paint(); 13 } 14 15 /** 16 * 這里定義了兩個接口:Singer 在Singer接口里面定義了sing()和sleep()這兩個抽象方法。 17 * 18 * @author gacl 19 * 20 */ 21 interface Singer { 22 public void sing(); 23 24 public void sleep(); 25 } 26 27 /** 28 * 類Student實現了Singer這個接口 29 * 30 * @author gacl 31 * 32 */ 33 class Student implements Singer { 34 35 private String name; 36 37 public Student(String name) { 38 this.name = name; 39 } 40 41 public String getName() { 42 return name; 43 } 44 45 public void setName(String name) { 46 this.name = name; 47 } 48 49 /** 50 * 實現接口中定義的sing方法 51 */ 52 @Override 53 public void sing() { 54 System.out.println("student is singing"); 55 } 56 57 /** 58 * 實現接口中定義的sleep方法 59 */ 60 @Override 61 public void sleep() { 62 System.out.println("student is sleeping"); 63 } 64 65 public void study() { 66 System.out.println("Studying..."); 67 } 68 69 } 70 71 /** 72 * Teacher這個類實現了兩個接口:Singer和Painter。 這里Teacher這個類通過實現兩個不相關的接口而實現了多重繼承。 73 * 74 * @author gacl 75 * 76 */ 77 class Teacher implements Singer, Painter { 78 79 private String name; 80 81 public Teacher(String name) { 82 this.name = name; 83 } 84 85 /** 86 * 在Teacher類里面重寫了這兩個接口里面的抽象方法, 87 * 通過重寫抽象方法實現了這兩個接口里面的抽象方法。 88 */ 89 @Override 90 public void eat() { 91 System.out.println("teacher is eating"); 92 } 93 94 public String getName() { 95 return name; 96 } 97 98 @Override 99 public void paint() { 100 System.out.println("teacher is painting"); 101 } 102 103 public void setName(String name) { 104 this.name = name; 105 } 106 107 @Override 108 public void sing() { 109 System.out.println("teacher is singing"); 110 } 111 112 @Override 113 public void sleep() { 114 System.out.println("teacher is sleeping"); 115 } 116 117 public void teach() { 118 System.out.println("teaching..."); 119 } 120 } 121 122 public class TestInterfaces { 123 124 public static void main(String[] args) { 125 /** 126 * 這里定義了一個接口類型的變量s1 127 */ 128 Singer s1 = new Student("le"); 129 s1.sing(); 130 s1.sleep(); 131 Singer s2 = new Teacher("steven"); 132 s2.sing(); 133 s2.sleep(); 134 Painter p1 = (Painter)s2; 135 p1.paint(); 136 p1.eat(); 137 } 138 }
這里驗證了兩個規則,“一個類可以實現多個無關的接口”,Teacher類既實現了Singer接口,同時也實現了Painter接口,而Singer接口和Painter接口是無關系的兩個接口。“多個無關的類可以實現同一接口”,Student類和Teacher類都實現了Singer接口,而Student類和Teacher類並不是關系很密切的兩個類,可以說是無關的兩個類。
運行結果:
2.2.畫內存分析圖體會接口與實現類之間存在的多態性
首先分析main方法的第一句話
Singer s1 = new Student(“le”);
這里首先定義了一個接口類型的變量s1,接口Singer是Student類實現的,即相當於Student類從Singer接口繼承,Singer接口的本質是一個特殊的抽象類,所以這里Singer接口就是Student類的父類,因此s1就是父類對象的一個引用,即這里這句話執行完后就是一個父類對象s1的引用指向子類對象Student。所以內存里面的布局應該是這樣:棧空間里面有一個父類對象的引用s1,堆空間里面new出了一個Student對象,創造這個Student對象的時候調用了Student類的構造方法Student(String name),其定義如下:
Student(String name){
this.name = name;
}
通過調用構造方法使得這個Student對象有了一個自己的名字“le”,因此堆內存里面的Student對象的name屬性值為“le”。
這個Student對象能夠訪問位於代碼區里面的sleep()方法和sing()方法,因為Student類從父類Sing繼承而來,因此自然可以訪問到這兩個方法,除此之外,還能訪問Student類里面自定義的Study()方法。因此代碼區里面存放着這三個方法等待着Student類的對象去訪問,也就是去調用。一個正常的Student可以直接調用這三個方法。那么怎么找得到位於代碼區的這三個方法呢?Student對象里面存在着能找得到這個三個方法的函數指針,引用對象通過這個指針的索引指向就能找到代碼區里面的這三個方法。
s1是父類對象的索引,但此時s1指向的卻是子類對象,即一個父類對象的索引指向了子類對象。這里很不幸的是,由於這個s1是一個父類對象的引用,站在s1的角度上,它就是只把你這個子類對象Student當成是一個Singer,s1只能看到Student對象里面的sing()和sleep這兩個方法的方法指針,因此使用這個s1引用對象只能去訪問從父類繼承下來的sleep()和sing()這兩個方法,但由於這兩個方法在子類Student里面被重寫了,那么現在就是這種情況了,子類Student從父類Singer繼承,在子類里面重寫了從父類繼承下來的sing()和sleep()這兩個方法,父類對象的引用指向了子類對象,這三種情況加在一起就使得多態可以存在了,這樣調用位於代碼區里面的方法時,會根據new出來的實際對象去調用代碼區里面的方法,因此這里在s1眼里雖然是把這個new出的Student當成一個Singer,但這個對象實際上就是一個Student,因此使用父類對象的引用s1調用代碼區里面的sleep()和sing()方法時,調用的是在子類里面重寫過后的sing()和sleep()方法。
接着分析第二句話
Singer s2 = new Teacher(“steven”);
Teacher這個類實現了Singer接口和Painter接口,即相當於從兩個父類繼承,一個父類是Singer,另一個父類是Painter。
這里的s2也是父類對象Singer的引用,指向的卻是子類對象Teacher,因此也是一個父類對象的引用指向子類對象。
創造這個Teacher對象的時候,調用Teacher(String name)構造方法,其定義如下:
Teacher(String name){
this.name=name;
}
調用構造方法后,Teacher有了自己的名字steven,所以Teacher的name屬性值為steven,由於這個Teacher實現了Painter接口和Singer接口,因此也繼承這兩個接口里面的方法,因此一個正常的Teacher可以訪問的方法有:paint()、eat()和sing()、sleep。前面兩個方法是從Painter類繼承過來的,后面兩個方法是從Singer類繼承過來的。除了這四個方法外,還有自己定義的Teach()方法。可是很不幸的是,由於s2是一個Singer類對象的引用,因此站在s2的角度來看,它只把Teacher當成是一個普通的Singer,因此它看到的只是Teacher對象里面的sing()和sleep()這兩方法,然后要調用時就通過Teacher對象里面的函數指針找到位於代碼區的sleep()和sing()這兩個方法。別的方法s2是看不到的,因此也調用不了。
Painter p1=(Painter)s2;
這里把s2強制轉換成Painter,s2對象實際是指向Teacher的,把s2強制轉換成Painter以后,就可以把Teacher當成Painter來用,所以p1會把Teacher當成Painter來看待,因此p1只能看到Teacher里面的painter()方法和eat()方法,因此能夠訪問到的也只有這兩個方法。所以接口對於我們實際當中的對象來說,每一個接口暴露了我們這個實際對象的一部分方法。你使用什么樣的接口,就只能訪問這個接口里面定義的方法,別的接口定義的方法就沒辦法訪問得到。
接口可以幫助我們實現多重繼承這種邏輯,接口和它的實現類之間存在多態性。
2.3.通過下面這些代碼驗證接口更進一步的特性
1 package javastudy.summary; 2 3 /** 4 * 把“值錢的東西”這個類定義成一個接口Valuable。在接口里面定義了一個抽象方法getMoney() 5 * @author gacl 6 * 7 */ 8 interface Valuable { 9 public double getMoney(); 10 } 11 12 /** 13 * 把“應該受到保護的東西”這個類定義成一個接口Protectable。 14 * 在接口里面定義了一個抽象方法beProtected(); 15 * @author gacl 16 * 17 */ 18 interface Protectable { 19 public void beProteced(); 20 } 21 22 /** 23 * 這里是接口與接口之間的繼承,接口A繼承了接口Protectable, 24 * 因此自然而然地繼承了接口Protectable里面的抽象方法beProtected()。 25 * 因此某一類去實現接口A時,除了要實現接口A里面定義的抽象方法m()以外, 26 * 還要實現接口A從它的父接口繼承下來的抽象方法beProtected()。 27 * 只有把這兩個抽象方法都實現了才算是實現了接口A。 28 * @author gacl 29 * 30 */ 31 interface A extends Protectable { 32 void m(); 33 } 34 35 /** 36 * 這里定義了一個抽象類Animal。 37 * @author gacl 38 * 39 */ 40 abstract class Animal { 41 private String name; 42 /** 43 * 在Animal類里面聲明了一個抽象方法enjoy() 44 */ 45 abstract void enjoy(); 46 } 47 48 /** 49 * 這里是為了實現了我們原來的語義: 50 * “金絲猴是一種動物”同時“他也是一種值錢的東西”同時“他也是應該受到保護的東西”。而定義的一個類GoldenMonKey。 51 * 為了實現上面的語義,這里把“值錢的東西”這個類定義成了一個接口Valuable, 52 * 把“應該受到保護的東西”這個類也定義成了一個接口Protectable。這樣就可以實現多繼承了。 53 * GoldenMonKey類首先從Animal類繼承,然后GoldenMonKey類再去實現Valuable接口和Protectable接口, 54 * 這樣就可以實現GoldenMonKey類同時從Animal類,Valuable類,Protectable類繼承了,即實現了多重繼承, 55 * 實現了原來的語義。 56 * @author gacl 57 * 58 */ 59 class GoldenMonKey extends Animal implements Valuable,Protectable { 60 61 /** 62 * 在GoldenMoKey類里面重寫了接口Protectable里面的beProtected()這個抽象方法, 63 * 實現了接口Protectable。 64 */ 65 @Override 66 public void beProteced() { 67 System.out.println("live in the Room"); 68 } 69 70 /** 71 * 在GoldenMoKey類里面重寫了接口Valuable里面的getMoney()這個抽象方法,實現了接口Valuable。 72 */ 73 @Override 74 public double getMoney() { 75 return 10000; 76 } 77 78 /** 79 * 這里重寫了從抽象類Animal繼承下來的抽象方法enjoy()。 80 * 實現了這抽象方法,不過這里是空實現,空實現也是一種實現。 81 */ 82 @Override 83 void enjoy() { 84 85 } 86 87 public static void test() { 88 /** 89 * 實際當中在內存里面我們new的是金絲猴,在金絲猴里面有很多的方法, 90 * 但是接口的引用對象v能看到的就只有在接口Valuable里面聲明的getMoney()方法, 91 * 因此可以使用v.getMoney()來調用方法。而別的方法v都看不到,自然也調用不到了。 92 */ 93 Valuable v = new GoldenMonKey(); 94 System.out.println(v.getMoney()); 95 /** 96 * 把v強制轉換成p,相當於換了一個窗口,通過這個窗口只能看得到接口Protectable里面的beProtected()方法 97 */ 98 Protectable p = (Protectable)v; 99 p.beProteced(); 100 } 101 } 102 103 /** 104 * 這里讓Hen類去實現接口A,接口A又是從接口Protectable繼承而來,接口A自己又定義了一個抽象方法m(), 105 * 所以此時相當於接口A里面有兩個抽象方法:m()和beProtected()。 106 * 因此Hen類要去實現接口A,就要重寫A里面的兩個抽象方法,實現了這兩個抽象方法后才算是實現了接口A。 107 * @author gacl 108 * 109 */ 110 class Hen implements A { 111 112 @Override 113 public void beProteced() { 114 115 } 116 117 @Override 118 public void m() { 119 120 } 121 122 } 123 124 /** 125 * java中定義接口 126 */ 127 public class JavaInterfacesTest { 128 129 public static void main(String[] args) { 130 GoldenMonKey.test(); 131 } 132 }
接口總結:接口和接口之間可以相互繼承,類和類之間可以相互繼承,類和接口之間,只能是類來實現接口。