Interface 接口詳解


簡介

接口主要用來描述類具有哪些功能,並不給出每個功能的具體實現方式。一個類可以實現一個或多個接口,並在需要接口的地方,隨時使用實現了響應接口的對象。

在 Java 程序設計語言中,接口不是類,而是對類的一組需求描述,這些類要遵循接口描述的統一格式進行定義。

接口中的所有方法自動屬於 public,所以在接口中聲明方法可以不用提供關鍵字 public 。

類實現接口,通常有兩個步驟:

  1. 將類聲明為實現指定的接口
  2. 對接口中的所有方法進行定義

要將類聲明為實現某個接口,需要使用關鍵字 implements :

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

接口特性

接口不是類,尤其不能使用 new 運算符實例化一個接口。但是可以聲明接口的變量,接口變量必須引用實現該接口的類對象。

X = new Comparable(...); //Error

Comparable x; // OK
x = new Employee(); // OK

與可以建立類的繼承關系一樣,接口也可以被擴展。這里允許存在多條從較高通用性的接口道較高專用型的忌口的鏈。

public interface Moveable{ void move(double x,double y);}
public interface Powerd extends Moveable{ double milesPerGallon();}

雖然在接口中不能包含實例域或靜態方法,但可以包含常量。

public interface Powerd extends Moveable{ 
    double milesPerGallon();
    double SPEED_LIMIT = 95;
    }

與接口中的方法都自動地被設置為 public 一樣,接口中的域將自動設為 public static final 。

接口與抽象類

使用抽象類表示通用屬性存在一個問題 :每個類只能擴展一個類。但是每個類可以實現多個接口。

靜態方法

在 Java SE 8 中,允許在接口中增加靜態方法。理論上,沒有任何理由認為這是不合適的,只是這有違於將接口最為抽象規范的初衷。

public interface Path{
    public static Path get(String first,String... more){
        return FileSystems.getDefault().getPath(first,more);
    }
    ...
}

默認方法

可以為接口方法提供一個默認實現。必須用 default 修飾符標記這樣的方法。

public interface Comparable<T>{
    default int compareTo(T other){ 
        return 0; // 默認情況下,所有的數據都相等
    }
}

這樣可能沒有什么用,因為 Comparable 的每一個實現都要覆蓋這個方法。但是在某些情況下,默認方法可能很有用。例如:

public interface Collection{
    int size();
    default boolean isEmpty(){
        return size() == 0;
    }
    ...
}

這樣在實現 Collection 的程序員就不用操心實現 isEmpty 方法了。

默認方法的一個重要用法是 “接口演化”。例, Collection 接口作為 Java 的一部分已經很多年了,在 Java SE 8中,為這個接口添加了一個 stream 方法。假設 stream 不是默認方法,那么引用 Collection 接口的類將不能編譯,因為沒有實現這個新方法。為接口增加一個非默認方法不能保證 “源代碼兼容”。

不過,假設不重新編譯這個類,而只是使用原先的一個包含這個類的 JAR 文件。這個類仍可以正常加載,盡管沒有這個新方法。但是,如果程序在一個該類的實例上調用 stream 方法,就會出現一個 AbstractMethodError。

默認方法可以解決這些問題。如果沒有重新編譯而直接加載這個類,並在一個實例上調用 stream 方法,將調用 Collection stream 方法。

解決默認方法沖突

如果現在一個接口中將一個方法定義為默認方法,然后又在超類或者另一個接口中定義了同樣的方法。會發生什么?Java對應的規則比較簡單:

  1. 超類優先。如果超類提供了一個具體方法,接口中同名並且有相同參數類型的默認方法會被忽略。
  2. 接口沖突。如果一個超接口提供了一個某人方法,另一個接口提供了一個同名而且參數類型相同的方法,必須覆蓋這個方法來解決沖突。
public interface Named{
    default String getName(){
        return getClass().getName()+"_"+hashCode();
    }
}

Class Student implements Person,Named{
    ...
}

類會繼承 Person 和 Named 接口提供的兩個不一致的 getName 方法。並不是從中選擇一個,Java 編譯器會報告一個錯誤,讓程序員來解決這個二義性。只需要在 Student 類中提供一個 getName 方法,在這個方法中,可以選擇兩個沖突方法中的一個。

Class Student implements Person,Named{
    public String getName(){ return Person.super.getName();}
    ...
}

接口示例

接口與回調

回調是一種常見的程序設計模式。在這種模式中,可以指出某個特定事件發生時應該采取的行動。

例如:程序中有一個時鍾,每隔十秒鍾報一次時。

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import javax.swing.*;

public class TimerTest {
    public static void main(String[] args) {
        ActionListener listener = new TimePrinter();

        Timer t = new Timer(10000, listener);
        t.start();
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);
    }
}

class TimePrinter implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone, the time is " + new Date());
        Toolkit.getDefaultToolkit().beep();
    }
}

對象克隆

Cloneable 接口指示一個類提供了一個安全的 clone 方法。clone 產生的對象是一個新對象,它的初始狀態與源對象相同,但是之后他們各自會有自己不同的狀態。

不過, clone 方法是 Object 的一個 protected 方法,這說明你的代碼不能直接調用這個方法。只有 Employee 類可以克隆 Employee 對象。

默認的克隆操作是“淺拷貝”,並沒有克隆對象中引用的的其他對象。如果源對象和淺克隆對象共享的子對象是不可變的,這種共享就是安全的。如果子對象屬於一個不可變的類,如 String ,這種共享是安全的。或者在對象的聲明周期中,子對象一只包含不變的常量,沒有更改器方法會改變它,也沒有方法會生成它的引用,這種情況下同樣是安全的。

![image-20201027204938433](file://C:/Users/YHL/Desktop/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF/img/%E6%8E%A5%E5%8F%A3/image-20201027204938433.png?lastModify=1603808898)

不過,通常子對象都是可變的,必須重新定義 clone 方法來建立一個深拷貝,同時克隆所有子對象。

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

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

實際上第 3 個選項是默認選項。如果選擇第 1 項或第 2 項,類必須:

  1. 實現 Cloneable 接口
  2. 重新定義 clone 方法,並指定 public 訪問修飾符

Tips:

Object 類中的 clone 方法聲明為 protected,子類只能調用受保護的 clone 方法來克隆自己的對象。必須重新定義 clone 為 public 才能允許所有方法克隆對象。

Cloneable 接口沒有指定 clone 方法,這個方法是從 Object 類繼承的。這個接口指示作為一個標記,指示類設計者了解克隆過程。標記接口不包含任何方法,它的唯一作用就是允許在類型查詢中使用 instanceof。

即使 clone 的默認(淺拷貝)實現能滿足要求,還是需要實現 Cloneable 接口,將 clone 重新定義為 public,再調用 super.clone()。

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

與 Object.clone 提供的淺拷貝相比,這個 clone 方法並沒有為它添加任何工嗯呢該。只是讓這個方法是公有的。要建立深拷貝,還需要做很多工作,克隆對象中可變的實例域。

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

如果在一個對象上調用 clone,但這個對象的類並沒有實現 Cloneable 接口,Object 類的 clone 方法就會拋出一個 CloneNotSupportedException。

捕捉這個異常是不是更好一些?這非常適合 final 類。否則,還是保留 throws 說明符。這樣就允許子類在不支持克隆時選擇拋出一個 CloneNotSupportedException。

要不要在自己的類中實現 clone 呢?如果你的客戶端需要建立深拷貝,可能就需要實現這個方法。

Tips:

所有數組類型都有一個 public 的 clone 方法,而不是 protected。可以用這個方法建立一個新數據,包含原數組所有元素的副本。


免責聲明!

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



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