Java編程接口詳細
一、抽象類和抽象方法
抽象:從具體事物抽出、概括出它們共同的方面、本質屬性與關系等,而將個別的、非本質的方面、屬性與關系舍棄,這種思維過程,稱為抽象。
這句話概括瞭抽象的概念,而在Java
中,你可以隻給出方法的定義不去實現方法的具體事物,由子類去根據具體需求來具體實現。
抽象類除瞭包含抽象方法外,還可以包含具體的變量和具體的方法。類即使不包含抽象方法,也可以被聲明為抽象類,防止被實例化。
抽象類不能被實例化,也就是不能使用new
關鍵字來得到一個抽象類的實例,抽象方法必須在子類中被實現。
抽象類總結規定:
- 抽象類不能被實例化,如果被實例化,就會報錯,編譯無法通過。隻有抽象類的非抽象子類可以創建對象。
- 抽象類中不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
- 抽象類中的抽象方法隻是聲明,不包含方法體,就是不給出方法的具體實現也就是方法的具體功能。
- 構造方法,類方法(用 static 修飾的方法)不能聲明為抽象方法。
- 抽象類的子類必須給出抽象類中的抽象方法的具體實現,除非該子類也是抽象類。
二、接口
interface
關鍵字使得抽象的概念更加向前邁進瞭一步,abstract
關鍵字允許人們在類中創建一個或多個沒有任何定義的方法—提供瞭接口部分。但是沒有提供任何相應的具體實現,這些實現是由此類的繼承者實現的。
在抽象類中,可以包含一個或多個抽象方法;但在接口(interface
)中,所有的方法必須都是抽象的,不能有方法體,它比抽象類更加“抽象”。
接口使用 interface
關鍵字來聲明,可以看做是一種特殊的抽象類,可以指定一個類必須做什麼,而不是規定它如何去做。
與抽象類相比,接口有其自身的一些特性:
- 接口中隻能定義抽象方法,這些方法默認為
public abstract
的,因而在聲明方法時可以省略這些修飾符。試圖在接口中定義實例變量、非抽象的實例方法及靜態方法,都是非法的 - 接口中沒有構造方法,不能被實例化
- 一個接口不實現另一個接口,但可以繼承多個其他接口。接口的多繼承特點彌補瞭類的單繼承
接口與抽象類的區別:
接口作為系統和外界交互的窗口,接口體現的是一種規范。對於接口的實現者而言,接口規定瞭實現者必須向外提供哪些服務(以方法的形式來提供);對於接口的調用者而言,接口規定瞭調用者可以調用哪些服務,以及如何調用這些服務(就是如何來調用方法)。當在一個程序中使用接口時,接口是多個模塊間的耦合標準;當在多個應用程序之間使用接口時,接口是多個程序之間的通信標準。
從某種角度上來看,接口類似於整個系統的“總綱”,它制定瞭系統各模塊之間應該遵循的標準,因此一個系統中的接口不應該經常改變。一旦接口改變,對整個系統而言甚至其他系統的影響將是輻射式的,導致系統中的大部分類都需要改寫。所以,在一般的應用裡,最頂級的是接口,然後是抽象類實現接口,最後才到具體類實現。
抽象類則不一樣,抽象類作為系統中多個子類的共同父類,它所體現的是模板式設計。抽象類作為多個子類的的抽象父類,可以被當成系統實現過程中的中間產品,這個產品已經實現瞭系統的部分功能(那些在抽象類中已經提供實現的方法),但這個產品依然不能當成最終產品,必須有更進一步的完善。
除此之外,接口和抽象類在用法上也存在如下區別:
- 接口裡隻能包含抽象方法,抽象類則可以包含普通方法。
- 接口裡不能定義靜態方法,抽象類裡可以定義靜態方法。
- 接口裡不包含構造器,抽象類可以包含構造器。抽象類裡的構造器並不是用於創建對象,而是讓其子類調用這些構造器來完成屬於抽象類的初始化操作。
- 接口裡不能包含初始化塊,但抽象類可以包含初始化塊。
- 接口裡隻能定義靜態常量,抽象類既可以定義普通變量,也可以定義靜態常量。
- 接口可以可以繼承多個接口,類隻能繼承一個類。
- 抽象類主要是用來抽象類別,接口主要是用來抽象方法功能。當關註事物的本質時,使用抽象類,當關註一種操作時,使用接口。
三、Java中的多重繼承
接口不僅僅是一種更加純粹的抽象類,它的目標比這更高。因為接口中根本沒有任何具體實現,所以沒有任何與接口相關的存儲,因此也就無法阻止多個接口的組合。在C++
中,組合多個類的接口的行為叫做多重繼承,但這可能會帶來很多副作用,因為每個類都有一個具體實現。在Java
中,可以執行一樣的行為,但是隻有一個類可以有具體實現,所以通過組合多個接口,C++的問題不會在Java中發生。
表達這樣一個意思:“ x 從屬於 a,也從屬於 b,也從屬於 c ”
使用接口的核心原因:
1).為瞭能夠向上轉型為多個基類型(以及由此帶來的靈活性);
2).防止客戶端程序員創建該類的對象,並確保這僅僅是建立一個接口(這與使用抽象基類原因相同)
這帶來的一個問題是,應該使用接口還是抽象類?
如果要創建不帶任何方法定義和成員變量的基類,那麼就應該選擇接口而不是抽象類。事實上,若知道某事物應該成為一個基類,那麼第一選擇應該是接口。
四、通過繼承來擴展接口
1、組合接口時的名字沖突
在實現多重繼承時,會碰到一個小陷阱,在前面的例子中,CanFight
和ActionCharacter
都有一個相同的void fight()
方法。問題不是它們方法相同,問題是,如果它們的簽名(參數)或返回類型不同,會怎麼樣呢?
//: interfaces/InterfaceCollision.java package object; interface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } } class C2 implements I1, I2 { public void f() {} public int f(int i) { return 1; } // overloaded } class C3 extends C implements I2 { public int f(int i) { return 1; } // overloaded } class C4 extends C implements I3 { // Identical, no problem: public int f() { return 1; } } // Methods differ only by return type: //!class C5 extends C implements I1 {} --23 //! interface I4 extends I1, I3 {} ///:~ --24 I1, I3中f()返回值類型不一致 //class C5 extends C implements I1{ //實現的方法和積累方法命名相同,但方法的返回值不一樣。 // int f(){ // return 0; // } //} // //interface I4 extends I1 , I3{ //重寫的方法名相同,但是返回值不同。 // // @Override // void f(); //}
因為他們的方法名都相同,但是返回值不同,並不能實現方法重載。所以不能實現多重繼承和組合接口。
五、適配接口
接口最吸引人的原因之一就是允許同一個接口具有多種不同的實現。
接口最常見的用法就是使用策略設計模式。此時你編寫一個執行某些操作的方法,而該方法將接受一個你指定的接口。你主要就是聲明:“ 你可以用任何你想要的對象來調用我的方法,隻要你的對象遵循我的接口。”
比如Java SE5
的Scanner
類,它的構造器接收的是一個Readable
接口。
public Scanner(Readable source) { this(Objects.requireNonNull(source, "source"), WHITESPACE_PATTERN); } // Readable 是一個字符源。read方法的調用方能夠通過 CharBuffer 使用 Readable 中的字符。 public interface Readable { // 將輸入內容添加到CharBuffer參數中。 public int read(java.nio.CharBuffer cb) throws IOException; }
example1 : 實現Readable接口。
import java.io.IOException; import java.nio.CharBuffer; import java.util.Random; import java.util.Scanner; public class RandomWords implements Readable { private int count; public RandomWords(int count) { this.count = count; } private static Random random = new Random(47); private static final char[] capitals = "ABCDEFTHIGKLMNOPQRSTUVWXYZ".toCharArray(); private static final char[] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray(); private static final char[] vowerls = "aeiou".toCharArray(); @Override public int read(CharBuffer cb) throws IOException { if (count-- == 0) { return -1; } cb.append(capitals[random.nextInt(capitals.length)]); for (int i = 0; i < 4; i++) { cb.append(vowerls[random.nextInt(vowerls.length)]); cb.append(lowers[random.nextInt(lowers.length)]); } cb.append(" "); return 10; } public static void main(String[] args) { @SuppressWarnings("resource") Scanner scanner = new Scanner(new RandomWords(10)); while (scanner.hasNext()) { System.out.println(scanner.next()); } } } /*output: Yazeruyac Fowenucor Toeazimom Raeuuacio Nuoadesiw Hageaikux Ruqicibui Numasetih Kuuuuozog Waqizeyoy */
example2 : 未實現Readable的類,就可以使用適配器+代理的方式
class RandomDoubles{ private static Random rand =new Random(47); public double next(){ return rand.nextDouble(); } } // --------------------------------------------------- import java.io.IOException; import java.nio.CharBuffer; import java.util.Random; import java.util.Scanner; public class Test { public static void main(String[] args) { Scanner s=new Scanner(new AdaptedRandomDoubles(7)); while(s.hasNext()){ System.out.println(s.next()); } } } class AdaptedRandomDoubles extends RandomDoubles implements Readable { private int count; public AdaptedRandomDoubles(int count){ this.count=count; } public int read(CharBuffer cb) throws IOException { if(count--==0){ return -1; } String result=Double.toString(this.next()); cb.append(result); return result.length(); } }
六、接口中的域
實例變量都是static final
七、嵌套接口
在類中嵌套接口的語法是相當顯而易見的,就像非嵌套接口一樣,可以擁有public
和“包訪問”兩種可視性。
1.類中的接口
{ void f(); } class A { interface B { void f(); } public class BImp implements B { public void f() { } } private class BImp2 implements B { public void f() { } } public interface C { void f(); } class CImp implements C { public void f() { } } private class CImp2 implements C { public void f() { } } private interface D private class DImp implements D { public void f() { } } public class DImpl2 implements D { public void f() { } } public D getD() { return new DImpl2(); } private D dRef; public void receive(D d) { dRef = d; dRef.f(); } } interface E { interface G { void f(); } //Redundant "public" public interface H { void f(); } void g(); //cannot be private within an interface } public class NestingInterface { public class BImpl implements A.B { public void f() { } } class CImpl implements A.C { public void f() { } } // cannot implement a private interface // class DImpl implements A.D { // public void f() { // } // } class EImpl implements E { public void g() { } } class EImpl2 implements E.G { public void f() { } class EG implements E.G { public void f() { } } } public static void main(String[] args) { A a = new A(); A a2 = new A(); //Can't access A.D: 不能訪問私有接口A.D //! A.D ad = a.getD(); //Doesn't return anything but A.D: 除瞭私有接口A.D,不能返回任何東西 //! A.DImp2 di2 = a.getD(); //返回回來的私有接口A.D, 不能向下轉型為A.DImp2 //Cannot access a member of the interface: 不能訪問私有接口A.D中的成員 //! a.getD().f(); //Only another A can do anything with getD(): 隻有另一個A才能使用getD()做任何事 a2.receive(a.getD()); } }
A.DImp2
隻能被其自身所使用。你無法說它實現瞭一個private
接口D,因此,實現一個private
接口隻是一種方式,它可以強制該接口中的方法定義不要添加任何類型信息(也就是說,不允許向上轉型),即A.DImp2
不能轉型為private
接口D;- 接口也可以被實現為
private
的,就像在A.D中看到的那樣;private
接口不能在定義它的類之外被實現 - 將返回值交給有權使用它的對象。在本例中,是另一個A通過
receiveD()
方法來實現的; - 嵌套在另一個接口中的接口自動是
public
的,而不能聲明為private
的;
2.接口中的接口
interface E{ // 隻能是默認或者public interface G { //默認為public void f(); } // Cannot be private within an interface: //! private interface I {} } class t2 implements E.G{ public void f() { } }
八、接口與工廠
接口時實現多重繼承的途徑,而生成遵循某個接口的對象的典型方式就是工廠方法設計模式
通過工廠方法,接口和實現完全分離,可以非常方便的更改實現。
interface Service // Service接口,可以有多種實現 { void method1(); void method2(); } interface ServiceFactory // 工廠接口,可以由多種實現 { Service getService(); } class Implementation1 implements Service { //Service接口的實現1 public Implementation1() { } public void method1() { System.out.println("Implementation1 method1"); } public void method2() { System.out.println("Implementation1 method2"); } } class Implementation1Factory implements ServiceFactory{ //生成對象1的工廠1 public Service getService() { return new Implementation1(); } } class Implementation2 implements Service { // Service接口的實現2 public Implementation2() { } public void method1() { System.out.println("Implementation2 method1"); } public void method2() { System.out.println("Implementation2 method2"); } } class Implementation2Factory implements ServiceFactory{//生成對象2的工廠2 public Service getService() { return new Implementation1(); } } public class Factories { //使用service的模塊 public static void serviceConsumer(ServiceFactory fact) { Service s = fact.getService(); //向上造型,工廠將生成某類實現接口的對象 s.method1(); s.method2(); } public static void main(String[] args) { serviceConsumer(new Implementation1Factory()); //serviceConsumer(new Implementation2Factory());很方便就可以更改實現 } } /*output: Implementation1 method1 Implementation1 method2 Implementation2 method1 Implementation2 method2 */
匿名內部類改進
interface Service { void method1(); void method2(); } interface ServiceFactory { Service getService(); } class Implementation1 implements Service { private Implementation1() { } public void method1() { System.out.println("Implementation1 method1"); } public void method2() { System.out.println("Implementation1 method2"); } public static ServiceFactory factory = new ServiceFactory() { public Service getService() { return new Implementation1(); } }; } class Implementation2 implements Service { private Implementation2() { } public void method1() { System.out.println("Implementation1 method1"); } public void method2() { System.out.println("Implementation1 method2"); } public static ServiceFactory factory = new ServiceFactory() { public Service getService() { return new Implementation2(); } }; } public class Factories { public static void serviceConsumer(ServiceFactory fact) { Service s = fact.getService(); s.method1(); s.method2(); } public static void main(String[] args) { serviceConsumer(Implementation1.factory); serviceConsumer(Implementation2.factory); } }
總結:
優先選擇類而不是接口。從類開始,如果接口的必需性變得非常明確,那麼就進行重構。
到此這篇關於Java編程接口詳細的文章就介紹到這瞭,更多相關Java編程 接口內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!