Java 面向對象:接口


禁止碼迷,布布扣,豌豆代理,碼農教程,愛碼網等第三方爬蟲網站爬取!

接口

接口用於描述類應該做什么,而不是指定類具體要怎么實現,一個類中可以實現多個接口。在有些情況下,我們的需求符合這些接口的描述,就可以使用實現這個接口的類的對象。
例如 Arrays 類中的 sort 方法可以對對象數組進行排序,但是 sort 應該按照什么規則進行排序呢?這個時候就可以使用 Comparable 接口,這個接口能夠用於描述某個類比較大小的規則。

public interface Comparable{
      int compareTo(Object other);
}

任何實現 Comparable 接口的類都需要包含 compareTo 方法,該方法需要傳入一個 Object 參數且返回一個整數。接口中的方法默認都是 public,因此聲明的時候可以省略不寫,不過在具體實現方法時還是要加上 “public”。接口絕對不能有實例字段,因為實例字段和方法的實現應該有實現接口的類實現。
Comparable 接口的 compareTo() 方法需要實現比較大小的任務,調用 “x.compareTo(y)” 的時候,若 x > y 時方法返回一個正數,若 x < y 時方法返回一個負數,相等就返回 0。例如希望使用 Arrays 類的 sort 方法對 Employee 對象數組排序,所以 Employee 類必須實現 Comparable 接口,實現的步驟為:

  1. 將類聲明為要實現的接口;
  2. 定義接口內的所有方法。

將類聲明為實現某種接口,需要用到 implements 關鍵字,定義時可以為泛型接口提供個參數。

public class Employee implements Comparable<Employee>{
   public int compareTo(Employee other){
      return Double.compare(salary, other.salary);
   }
}

不過為什么不能在 Employee 類中實現 compareTo 方法,而必須實現 Comparable 接口呢?這是因為 Java 是一個強類型語言,調用方法時需要判斷這個方法確實存在,如果是一個 Comparable 對象數組就可以保證有 compareTo 方法。

自定義接口

有的時候僅靠現有的接口是無法完成需求的,或者說希望得到某種接口實現自己的特殊需求,這時就需要自定義接口interface 關鍵字可以用來聲明一個接口,自定義接口的代碼框架為:

[可見度] interface 接口名稱 [extends 其他的接口名] {
        // 聲明變量
        // 抽象方法
}

接口是隱式抽象的,且接口中每一個方法也是隱式抽象的,因此聲明時不需要 abstract 關鍵字,同時接口中的方法都是公有的。下面自定義一個棧接口:

interface IntgerStack{
      //如果 item 為 null,則不入棧直接返回 null;如果棧滿,也返回 null;如果插入成功,返回 item
      public Integer push(Integer item);
	
      //出棧,如果為空,則返回 null
      public Integer pop();   

      //獲得棧頂元素,如果為空,則返回 null
      public Integer peek();

      //棧空判斷,如果為空返回 true
      public boolean empty();

      //棧容量判斷,返回棧中元素個數 
      public int size();
}

接口特性

接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)。接口中可以含有變量,但是接口中的變量會被隱式的指定為 public static final 變量(並且只能是 public,用 private 修飾會報編譯錯誤)。接口中的方法是不能在接口中實現的,只能由實現接口的類來實現接口中的方法。
接口和類的不同之處有:

  1. 接口不能用於實例化對象。
  2. 接口沒有構造方法。
  3. 接口中所有的方法必須是抽象方法。
  4. 接口不能包含成員變量,除了 static 和 final 變量。
  5. 接口支持多繼承。

接口和繼承的不同之處有:

  1. 抽象類中的方法可以有方法體,就是能實現方法的具體功能,但是接口中的方法不行。
  2. 抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是 public static final 類型的。
  3. 接口中不能含有靜態代碼塊以及靜態方法(用 static 修飾的方法),而抽象類是可以有靜態代碼塊和靜態方法。
  4. 一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。

默認方法

接口可以默認地提供一種實現方式,默認方法使用 default 關鍵字標注。

public interface Cokparable<T>{
      default int compareTo(T other){
            return 0;
      }
}

默認方法的重要用途是實現接口演化,即你之前使用的接口若引入的新方法。若該方法不是默認方法,當你沒有為新方法寫上新代碼的話就無法編譯,而如果將新引入的方法實現為默認方法就可以實現兼容。
如果在一個接口中將一個方法定義為默認方法,有在超類或另一個接口中又定義了同樣的方法,這種二義性要怎么解決?規則如下:

  1. 超類優先,如果超類提供了具體的方法,則同名同參數類型的默認方法會被忽略;
  2. 接口沖突,如果出現 2 個接口都有一個同名同參數的方法,則編譯器返回一個錯誤,讓程序員自己處理這個問題。

如果是一個類擴展了一個超類,同時也實現了一個接口,子類從超類和接口繼承了相同的方法怎么辦?此時會優先考慮超類的方法,接口的默認方法會被忽略。

  • 不要讓一個默認方法重新定義為 Object 類中的某個方法。

回調

回調是一種程序設計模式,該模式可以在指定的某個特定事件發生時,自動采取相應的動作。例如 Timer 類可以用於構造定時器,可以實現每隔一定的時間間隔完成系列操作。Java 中實現時可以向定時器傳入某個類的對象,定時器自動調用這個對象的方法,而且類可以附帶更多的信息。
定時器需要明確調用類的哪一個方法,因此可以通過 ActionListener 接口實現,每隔一定的時間間隔就自動調用 actionPerformed 方法

public interface ActionListener{
      void actionPerformed(ActionEvent event);
}

其中 ActionEvent 參數提供了事件的相關信息,例如發生這個時間的時間。例如:

class TimePrinter implements ActionListener{  
   public void actionPerformed(ActionEvent event){  
      System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen()));
      Toolkit.getDefaultToolkit().beep();
   }
}

使用這個方法時,就需要先構造這個類的對象,然后再傳遞給 Timer 的構造器並啟動。

TimePrinter listener = new TimePrinter();

Timer timer = new Timer(1000, listener);
timer.start();

對象克隆

clone 方法

當一個包含對象引用的變量建立副本時,原變量和副本都是對同一個對象的引用,也就是說任何一個對象的改變都會影響另一個變量。

var original = new Employee("John Public", 50000);
Employee copy = original;


如果希望復制一個新對象,它的初始狀態和舊對象相同,但是對新對象的修改不會影響舊對象,這就需要使用 clone 方法。

Employee copy = original.clone();


不過 clone 方法是 Object 類的 protected 方法,不能被直接調用,因此只有同類的對象可以克隆。

深拷貝、淺拷貝

對象克隆是逐個字段進行拷貝,若對象包含子對象的引用,拷貝字段就會得到相同子對象的另一個引用,此時原對象和克隆對象還是會共享一些信息。

默認的克隆是淺拷貝,淺拷貝沒有克隆對象中引用的其他對象。如果原對象和淺克隆對象共享的子對象是不變的,則這種共享是安全的。但是通常情況下子對象都是可變的,因此必須重新定義 clone 方法建立一個深拷貝,以克隆所有子對象。

Cloneable 接口

對於每一個類,需要確定:

  1. 默認的 clone 方法是否滿足需求;
  2. 是否可以在可變的子對象上調用 clone 來修補默認的 clone 方法;
  3. 是否不需要使用 clone。

如果確定默認的 clone 方法不符合需求,則需要實現 Cloneable 接口,並且用 public 重新定義新的 clone 方法。Cloneable 接口並沒有指定 clone 方法,因為這個方法是從 Object 類繼承的,該接口起到一個標記作用,指示這個類覆蓋了 clone 方法。
即使是默認的淺拷貝,也需要實現 Cloneable 接口,將 clone 定義為 public 再來調用。

class Employee implements Cloneable{
      public Employee clone() throws CloneNotSupportedException{
            return (Employee) super.clone();
      }
}

如果是深拷貝,就需要在 clone 方法中克隆對象的可變實例字段。

class Employee implements Cloneable{
      private String name;
      private double salary;
      private Date hireDay;

      public Employee clone() throws CloneNotSupportedException{
            Employee cloned = (Employee) super.clone();
            cloned.hireDay = (Date) hireDay.clone();
            return cloned;
      }
}

參考資料

菜鳥教程
《Java 核心技術 卷Ⅰ》,[美]Cay S.Horstmann 著,林琪 蘇鈺涵 等譯,機械工業出版社


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM