java基礎學習總結——接口

java基礎學習總結——接口

一、接口的概念

  

  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.接口舉例

package javastudy.summary;

/**
 * 這裏定義了接口:Painter。 在Painter接口裏面定義了paint()和eat()這兩個抽象方法。
 *
 * @author gacl
 *
 */
interface Painter {
    public void eat();

    public void paint();
}

/**
 * 這裏定義了兩個接口:Singer 在Singer接口裏面定義了sing()和sleep()這兩個抽象方法。
 *
 * @author gacl
 *
 */
interface Singer {
    public void sing();

    public void sleep();
}

/**
 * 類Student實現了Singer這個接口
 *
 * @author gacl
 *
 */
class Student implements Singer {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 實現接口中定義的sing方法
     */
    @Override
    public void sing() {
        System.out.println("student is singing");
    }

    /**
     * 實現接口中定義的sleep方法
     */
    @Override
    public void sleep() {
        System.out.println("student is sleeping");
    }

    public void study() {
        System.out.println("Studying...");
    }

}

/**
 * Teacher這個類實現了兩個接口:Singer和Painter。 這裏Teacher這個類通過實現兩個不相關的接口而實現了多重繼承。
 *
 * @author gacl
 *
 */
class Teacher implements Singer, Painter {

    private String name;

    public Teacher(String name) {
        this.name = name;
    }

    /**
     * 在Teacher類裏面重寫了這兩個接口裏面的抽象方法,
     * 通過重寫抽象方法實現了這兩個接口裏面的抽象方法。
     */
    @Override
    public void eat() {
        System.out.println("teacher is eating");
    }

    public String getName() {
        return name;
    }

    @Override
    public void paint() {
        System.out.println("teacher is painting");
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void sing() {
        System.out.println("teacher is singing");
    }

    @Override
    public void sleep() {
        System.out.println("teacher is sleeping");
    }

    public void teach() {
        System.out.println("teaching...");
    }
}

public class TestInterfaces {

    public static void main(String[] args) {
        /**
         * 這裏定義了一個接口類型的變量s1
         */
        Singer s1 = new Student("le");
        s1.sing();
        s1.sleep();
        Singer s2 = new Teacher("steven");
        s2.sing();
        s2.sleep();
        Painter p1 = (Painter)s2;
        p1.paint();
        p1.eat();
    }
}

這裏驗證了兩個規則,“一個類可以實現多個無關的接口”,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.通過下面這些代碼驗證接口更進一步的特性

package javastudy.summary;

/**
 * 把“值錢的東西”這個類定義成一個接口Valuable。在接口裏面定義了一個抽象方法getMoney()
 * @author gacl
 *
 */
interface Valuable {
    public double getMoney();
}

/**
 * 把“應該受到保護的東西”這個類定義成一個接口Protectable。
 * 在接口裏面定義了一個抽象方法beProtected();
 * @author gacl
 *
 */
interface Protectable {
    public void beProteced();
}

/**
 * 這裏是接口與接口之間的繼承,接口A繼承了接口Protectable,
 * 因此自然而然地繼承了接口Protectable裏面的抽象方法beProtected()。
 * 因此某一類去實現接口A時,除了要實現接口A裏面定義的抽象方法m()以外,
 * 還要實現接口A從它的父接口繼承下來的抽象方法beProtected()。
 * 只有把這兩個抽象方法都實現了纔算是實現了接口A。
 * @author gacl
 *
 */
interface A extends Protectable {
    void m();
}

/**
 * 這裏定義了一個抽象類Animal。
 * @author gacl
 *
 */
abstract class Animal {
    private String name;
    /**
     * 在Animal類裏面聲明瞭一個抽象方法enjoy()
     */
    abstract void enjoy();
}

/**
 * 這裏是爲了實現了我們原來的語義:
 * “金絲猴是一種動物”同時“他也是一種值錢的東西”同時“他也是應該受到保護的東西”。而定義的一個類GoldenMonKey。
 * 爲了實現上面的語義,這裏把“值錢的東西”這個類定義成了一個接口Valuable,
 * 把“應該受到保護的東西”這個類也定義成了一個接口Protectable。這樣就可以實現多繼承了。
 * GoldenMonKey類首先從Animal類繼承,然後GoldenMonKey類再去實現Valuable接口和Protectable接口,
 * 這樣就可以實現GoldenMonKey類同時從Animal類,Valuable類,Protectable類繼承了,即實現了多重繼承,
 * 實現了原來的語義。
 * @author gacl
 *
 */
class GoldenMonKey extends Animal implements Valuable,Protectable {

    /**
     * 在GoldenMoKey類裏面重寫了接口Protectable裏面的beProtected()這個抽象方法,
     * 實現了接口Protectable。
     */
    @Override
    public void beProteced() {
        System.out.println("live in the Room");
    }

    /**
     * 在GoldenMoKey類裏面重寫了接口Valuable裏面的getMoney()這個抽象方法,實現了接口Valuable。
     */
    @Override
    public double getMoney() {
        return 10000;
    }

    /**
     * 這裏重寫了從抽象類Animal繼承下來的抽象方法enjoy()。
     * 實現了這抽象方法,不過這裏是空實現,空實現也是一種實現。
     */
    @Override
    void enjoy() {

    }

    public static void test() {
        /**
         * 實際當中在內存裏面我們new的是金絲猴,在金絲猴裏面有很多的方法,
         * 但是接口的引用對象v能看到的就只有在接口Valuable裏面聲明的getMoney()方法,
         * 因此可以使用v.getMoney()來調用方法。而別的方法v都看不到,自然也調用不到了。
         */
        Valuable v = new GoldenMonKey();
        System.out.println(v.getMoney());
        /**
         * 把v強制轉換成p,相當於換了一個窗口,通過這個窗口只能看得到接口Protectable裏面的beProtected()方法
         */
        Protectable p = (Protectable)v;
        p.beProteced();
    }
}

/**
 * 這裏讓Hen類去實現接口A,接口A又是從接口Protectable繼承而來,接口A自己又定義了一個抽象方法m(),
 * 所以此時相當於接口A裏面有兩個抽象方法:m()和beProtected()。
 * 因此Hen類要去實現接口A,就要重寫A裏面的兩個抽象方法,實現了這兩個抽象方法後纔算是實現了接口A。
 * @author gacl
 *
 */
class Hen implements A {

    @Override
    public void beProteced() {

    }

    @Override
    public void m() {

    }

}

/**
 * java中定義接口
 */
public class JavaInterfacesTest {

    public static void main(String[] args) {
        GoldenMonKey.test();
    }
}

接口總結:接口和接口之間可以相互繼承,類和類之間可以相互繼承,類和接口之間,只能是類來實現接口

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章