Java程式設計思想 第九章 介面

pursuingdreams 發表於 2021-10-06
Java

第九章 介面

抽象類和抽象方法

抽象:從具體事物抽出、概括出它們共同的方面、本質屬性與關係等,而將個別的、非本質的方面、屬性與關係捨棄,這種思維過程,稱為抽象。

這句話概括了抽象的概念,而在Java中,你可以只給出方法的定義不去實現方法的具體事物,由子類去根據具體需求來具體實現。

抽象類除了包含抽象方法外,還可以包含具體的變數和具體的方法。類即使不包含抽象方法,也可以被宣告為抽象類,防止被例項化。

抽象類不能被例項化,也就是不能使用new關鍵字來得到一個抽象類的例項,抽象方法必須在子類中被實現。

抽象類總結規定:

  1. 抽象類不能被例項化,如果被例項化,就會報錯,編譯無法通過。只有抽象類的非抽象子類可以建立物件。
  2. 抽象類中不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
  3. 抽象類中的抽象方法只是宣告,不包含方法體,就是不給出方法的具體實現也就是方法的具體功能。
  4. 構造方法,類方法(用 static 修飾的方法)不能宣告為抽象方法。
  5. 抽象類的子類必須給出抽象類中的抽象方法的具體實現,除非該子類也是抽象類。

介面

interface關鍵字使得抽象的概念更加向前邁進了一步,abstract關鍵字允許人們在類中建立一個或多個沒有任何定義的方法---提供了介面部分。但是沒有提供任何相應的具體實現,這些實現是由此類的繼承者實現的。

在抽象類中,可以包含一個或多個抽象方法;但在介面(interface)中,所有的方法必須都是抽象的,不能有方法體,它比抽象類更加“抽象”。

介面使用 interface 關鍵字來宣告,可以看做是一種特殊的抽象類,可以指定一個類必須做什麼,而不是規定它如何去做。

與抽象類相比,介面有其自身的一些特性:

  • 介面中只能定義抽象方法,這些方法預設為 public abstract 的,因而在宣告方法時可以省略這些修飾符。試圖在介面中定義例項變數、非抽象的例項方法及靜態方法,都是非法的
  • 介面中沒有構造方法,不能被例項化
  • 一個介面不實現另一個介面,但可以繼承多個其他介面。介面的多繼承特點彌補了類的單繼承

介面與抽象類的區別:

介面作為系統和外界互動的視窗,介面體現的是一種規範。對於介面的實現者而言,介面規定了實現者必須向外提供哪些服務(以方法的形式來提供);對於介面的呼叫者而言,介面規定了呼叫者可以呼叫哪些服務,以及如何呼叫這些服務(就是如何來呼叫方法)。當在一個程式中使用介面時,介面是多個模組間的耦合標準;當在多個應用程式之間使用介面時,介面是多個程式之間的通訊標準。

從某種角度上來看,介面類似於整個系統的“總綱”,它制定了系統各模組之間應該遵循的標準,因此一個系統中的介面不應該經常改變。一旦介面改變,對整個系統而言甚至其他系統的影響將是輻射式的,導致系統中的大部分類都需要改寫。所以,在一般的應用裡,最頂級的是介面,然後是抽象類實現介面,最後才到具體類實現。

抽象類則不一樣,抽象類作為系統中多個子類的共同父類,它所體現的是模板式設計。抽象類作為多個子類的的抽象父類,可以被當成系統實現過程中的中間產品,這個產品已經實現了系統的部分功能(那些在抽象類中已經提供實現的方法),但這個產品依然不能當成最終產品,必須有更進一步的完善。

除此之外,介面和抽象類在用法上也存在如下區別:

  • 介面裡只能包含抽象方法,抽象類則可以包含普通方法。

  • 介面裡不能定義靜態方法,抽象類裡可以定義靜態方法。

  • 介面裡不包含構造器,抽象類可以包含構造器。抽象類裡的構造器並不是用於建立物件,而是讓其子類呼叫這些構造器來完成屬於抽象類的初始化操作。

  • 介面裡不能包含初始化塊,但抽象類可以包含初始化塊。

  • 介面裡只能定義靜態常量,抽象類既可以定義普通變數,也可以定義靜態常量。

  • 介面可以可以繼承多個介面,類只能繼承一個類。

  • 抽象類主要是用來抽象類別,介面主要是用來抽象方法功能。當關注事物的本質時,使用抽象類,當關注一種操作時,使用介面。

Java中的多重繼承

介面不僅僅是一種更加純粹的抽象類,它的目標比這更高。因為介面中根本沒有任何具體實現,所以沒有任何與介面相關的儲存,因此也就無法阻止多個介面的組合。在C++中,組合多個類的介面的行為叫做多重繼承,但這可能會帶來很多副作用,因為每個類都有一個具體實現。在Java中,可以執行一樣的行為,但是隻有一個類可以有具體實現,所以通過組合多個介面,C++的問題不會在Java中發生。

表達這樣一個意思:“ x 從屬於 a,也從屬於 b,也從屬於 c ”

在這裡插入圖片描述

使用介面的核心原因:

1).為了能夠向上轉型為多個基型別(以及由此帶來的靈活性);

2).防止客戶端程式設計師建立該類的物件,並確保這僅僅是建立一個介面(這與使用抽象基類原因相同)

這帶來的一個問題是,應該使用介面還是抽象類?

如果要建立不帶任何方法定義和成員變數的基類,那麼就應該選擇介面而不是抽象類。事實上,若知道某事物應該成為一個基類,那麼第一選擇應該是介面。

通過繼承來擴充套件介面

組合介面時的名字衝突

在實現多重繼承時,會碰到一個小陷阱,在前面的例子中,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.類中的介面

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 {
        void f();
    }

    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);
    }
}

總結

優先選擇類而不是介面。從類開始,如果介面的必需性變得非常明確,那麼就進行重構。