Java基礎---多態、內部類

多態(Polymorphism)

多態的其他叫法:動態綁定,後期綁定,運行時綁定。

多態的前提:

1、類與類之間必須有關係,要麼繼承,要麼實現。

2、存在覆蓋。父類中有方法被子類重寫。

例子:

/***
 * 多態的實例
 * @author LQX
 *涉及:向上轉型,多態
 */
public class Test1 {

     //此處使用Animal類將使得調用更方便,如果寫他的子類,那麼顯得冗餘複雜,直接寫他們的父類將會顯得簡單。
     //此處就是動態綁定(多態),運行時根據對象類型進行綁定
    public static void choose(Animal a)
    {
        a.eat();
    }

    public static void main(String[] args) {

        //   choose(new Cat());等價於Animal a = new Cat();choose(a);  涉及了:向上轉型,Cat本質就是Animal類。 

        choose(new Cat());
        choose(new Dog());
    }

}
//動物類,父類
class Animal
{
    //動物都具有的行爲,吃
    public void eat()
    {
        System.out.println("Animal eat!");
    }
}
//貓
class Cat extends Animal
{
    //貓的行爲
    public void eat()
    {
        System.out.println("貓愛吃魚!");
    }
}
//狗
class Dog extends Animal
{
    //狗的行爲
    public void eat()
    {
        System.out.println("狗愛吃骨頭!");
    }
}

總結:

1.綁定:

    將一個方法調用同一個方法主體關聯起來被稱作綁定。程序在執行前進行綁定(由編譯器和連接程序實現)稱爲前期綁定。上述程序之所以迷惑,主要是因爲前期綁定。因爲只有一個Animal引用時,他無法知道調用Animal類還是Cat類還是Dog類的eat方法。

    解決辦法就是後期綁定(多態),意思就是運行時根據傳進去的對象類型進行綁定。

2.多態的體現

a. 父類的引用指向了自己子類的對象。 

b. 父類的引用也可以接收自己的子類對象。

如: Animal a = new Cat();

其中就將父類型的 a 引用指向了子類的對象。

3.多態的利與弊

    利:提高了程序的可擴展性和後期可以維護性。

    弊:只能使用父類中的引用訪問父類中的成員。也就是說使用了多態,父類型的引用在使用功能時,不能直接調用子類中的特有方法。如:Animal a = new Cat(); 這代碼就是多態的體現,假設子類Cat中有特有的抓老鼠功能,父類型的 a就不能直接調用。這上面的代碼中,可以理解爲Cat類型提升了,向上轉型。

    如果此時父類的引用想要調用Cat中特有的方法,就需要強制將父類的引用,轉成子類類型,向下轉型。如:Cat c = (Cat)a;

注:如果父類可以創建對象,如:Animal a = new Animal(); 此時,就不能向下轉型了,Cat c = (Cat)a; 這樣的代碼就變得不容許,編譯時會報錯。所以千萬不能出現這樣的操作,就是將父類對象轉成子類類型。

    我們能轉換的是父類引用指向了自己的子類對象時,該引用可以被提升,也可以被強制轉換。多態至始至終都是子類對象在做着變化。

4.多態的特點

Animal a = new Cat();爲例
引用類型是《Animal》
實際類型是《Cat》

a.成員函數(非靜態)

    在編譯的時候,編譯器會先檢查“引用類型”是否存在符合定義的“eat”的方法,如果沒有,不能編譯。
    在執行a.eat();的時候。檢查本類,也就是實際類型的類,有沒有符合的方法。如果沒有,就執行父類繼承過來的。如果有,就執行本類(子類)的。

    總結:編譯看左邊,運行看右邊。
public class Test3 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Father f = new Son();
        f.run();
    }

}
class Father
{
    //此方法註釋掉,編譯通不過
    public void run()
    {
        System.out.println("Father run");
    }
}

class Son extends Father
{
    //此方法註釋掉,將顯示父類該方法
    public void run()
    {
        System.out.println("Son run");
    }
}

這裏寫圖片描述

b.成員函數(靜態)

    調用當前引用類型變量類型中的方法。
    因爲靜態是屬於類的,由實例共享,所以只看當前引用變量所屬的類中的靜態方法。
    總結:編譯和運行看左邊
public class Test3 {

    public static void main(String[] args) {
        Father f = new Son();
        f.method();
    }

}
class Father
{
    public static void method()
    {
        System.out.println("Father static method");
    }
}

class Son extends Father
{
    public static void method()
    {
        System.out.println("Son static method");
    }
}

這裏寫圖片描述

c.成員變量

    無論編譯期還是運行期,都只參考引用類型變量所屬的類中是否有對象的成員變量。有,編譯或運行通過,沒有,編譯或運行失敗
    簡單說:編譯和運行都參考等號的左邊
public class Test2 {

    public static void main(String[] args) {
        //多態的成員變量看引用類型(左邊),右邊爲實際類型
        Father f = new Son();
        System.out.println(f.i);

        Son s = new Son();
        System.out.println(s.i);
    }

}

class Father
{
    int i = 1;
}

class Son extends Father
{
    int i = 4;
}

這裏寫圖片描述

小測驗:

/***
 * 需求:
 * 電腦的運行實例。電腦的運行由主板控制,假設主板只是提供電腦運行,但是沒有上網,
 * 聽歌等功能。而上網、聽歌需要硬件的支持。而現在主板上沒有網卡和聲卡,
 * 這時可以定義一個規則,叫PCI,只要符合這個規則的網卡和聲卡都可以在主板上使用,
 * 這樣就降低了主板和網卡、聲卡之間的耦合性。用程序體現。 
 */
public class Test4 {

    public static void main(String[] args) {
        Mainboard m1 = new Mainboard();
        m1.run();
        m1.usePCI(new AudioCard());
        m1.usePCI(new NetworkCard());
    }
}
//主板
class Mainboard
{
    public void run()
    {
        System.out.println("主板啓動");
    }
    //調用PCI接口
    public void usePCI(PCI p)
    {
        if(p!=null)
        {
            p.open();
            p.close();
        }
    }
}
//PCI接口
interface PCI
{
    void open();
    void close();
}
//聲卡實現PCI接口
class AudioCard implements PCI
{
    @Override
    public void open() {
        System.out.println("聲卡啓動!");
    }

    @Override
    public void close() {
        System.out.println("聲卡關閉!");
    }
}
//網卡實現PCI接口
class NetworkCard implements PCI
{
    @Override
    public void open() {
        System.out.println("網卡啓動!");
    }

    @Override
    public void close() {
        System.out.println("網卡關閉!");
    }
}

這裏寫圖片描述

內部類(Inner Class)

一、成員內部類

1、定義

  一個類的定義放在另一個類的內部,這個類就叫做內部類。 
  就好像,外部類的一個成員一樣,故稱成員內部類。
 public class First {
    public class Contents{
        public void f(){
        System.out.println("In Class First's inner Class Contents method f()");
        }
    }
  }
像這樣的,Contents就叫做內部類 
內部類瞭解外圍類,並能與之通信。

2、鏈接到外圍類

  創建了內部類對象時,它會與創造它的外圍對象有了某種聯繫,於是能訪問外圍類的所有成員,不需任何特殊條件。 
public class Test2 {
    //外部類定義的字符串
    String s = "abcdefg";
    class Innerclass
    {
        public void show()
        {
            //內部類可以訪問
            System.out.println(s);
        }
    }
    public static void main(String[] args) {
        Test2 t = new Test2();
        Innerclass i = t.new Innerclass();
        i.show();
    }
}
在內部類InnerClass中,可以使用外圍類成員變量s
實現方式:
    非靜態內部類對象的創建需要先創建外部類對象,此時的內部類會祕密的獲取到外部類對象的引用,通過這個引用可以來訪問外圍對象成員。
    通常,這些是由編譯器所做的,我們所關注的應該是:
    創建內部類對象時,與外部類對象的關聯。

3、使用關鍵字.this與.new

.this關鍵字用於在內部類中獲取當前外部類引用,注意與new的區別。
public class Test1 {
    int i = 2;
    public Test1(){
    }
    public Test1(int i)
    {
        this.i = i;
    }
    class inner
    {
        public Test1 getTest1()
        {
            //使用.this後,得到時創建該內部類時使用的外圍類對象的引用,
            return Test1.this;
        }
        public Test1 newTest1()
        {
            //new則是創建了一個新的引用。
            return new Test1();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Test1 t1 = new Test1(5);
        //  必須是外圍類對象.new(實例.new),而不能是外圍類.new
        Test1.inner inner1 = t1.new inner();

        Test1 t2 = inner1.getTest1();

        Test1 t3 = inner1.newTest1();
        System.out.println(t2.i);
        System.out.println(t3.i);
    }
}

這裏寫圖片描述

使用.this關鍵字,得到創建改內部類的外部類對象的引用,而new則是建立了一個新的引用。

使用.new關鍵字,會根據一個外部類對象來創建一個內部類對象。

形式是這樣的:

      OutClass.InnerClass obj = outClassInstance.new InnerClass();
  注意使用的是外部類對象來new,而不是外部類。

4、內部類與向上轉型

將內部類向上轉型爲基類型,尤其是接口時,內部類就有了用武之地。 
public class Mianboard {
    //內部類,網卡實現PCI接口
    private class NetworkCard implements PCI{
        @Override
        public void open() {
            System.out.println("Open NetworkCard!");
        }
    }
    //獲取PCI接口實例
    public PCI getPCI()
    {
        return new NetworkCard();
    }
    public static void main(String[] args) {
        Mianboard m1 = new Mianboard();
        PCI p1 = m1.getPCI();
        p1.open();
    }
}
//PCI接口
interface PCI
{
    void open();
}

通過內部類,可以對外隱藏一些類的細節,除了他的外部類可以訪問,其他人無法訪問。並且,由於寫在了內部,也降低了耦合性。

二、方法內部類

內部類定義在外部類中的某個方法中,創建了這個類型的對象時,且僅使用了一次,那麼可在這個方法中定義局部類。

    1)不可以被成員修飾符修飾。如public、private、static等修飾符修飾。它的作用域被限定在了聲明這個局部類的代碼塊中

    2)可以直接訪問外部類中的成員,因爲還持有外部類中的引用。

    3)方法內部的類也不是在調用方法時纔會創建的,它們一樣也被編譯了。

注意:內部類不可以訪問它所在的局部中非最終變量。只能訪問被final修飾的局部變量。
原因:http://blog.csdn.net/craigyang/article/details/4680506

三、匿名內部類

1.定義:

   就是內部類的簡化寫法。

2.定義匿名內部類的前提

內部類必須實現了某個接口或者繼承了某個類

3.格式

new外部類名或者接口名(){覆蓋類或者接口中的代碼,(也可以自定義內容。)};

4.實質

其實匿名內部類就是一個匿名子類對象。可以理解爲帶內容的對象。

5.什麼時候使用匿名內部類呢?

通常使用方法是接口類型參數,並且該接口中的方法不超過三個,可以將匿名內部類作爲參數傳遞

6.匿名內部類的利與弊

優:簡化書寫
弊:1.匿名內部類沒有名字,也就不能調用自己的方法,且只使用一次。
2.沒有引用,也就不可以做強轉動作。
3.與正規的繼承相比有些受限,因爲匿名內部類既可以擴展類,也可以實現接口,但不能兩者兼備。切如果實現接口,也只能實現一個。
4.內部類方法不宜過多,否則影響閱讀性,一般不超過3個。
//方法一:
public class Mianboard2 {
    //匿名內部類,網卡實現PCI接口
    //獲取PCI接口實例
    public PCI1 getPCI1()
    {
        return new PCI1(){
            public void open() {
                System.out.println("Open NetworkCard!");
            }
        };
    }
    public static void main(String[] args) {
        Mianboard2 m1 = new Mianboard2();
        PCI1  p = m1.getPCI1();
        p.open();
    }
}
//PCI接口
interface PCI1
{
    void open();
}
/////////////////////////////////////
//方法二:
public class Mianboard2 {
    //內部類,網卡實現PCI接口
    //獲取PCI接口實例
    public void getPCI1(PCI1 p)
    {
        p.open();
    }
    public static void main(String[] args) {
        Mianboard2 m1 = new Mianboard2();
        m1.getPCI1(new PCI1(){
            public void open() {
                System.out.println("Open NetworkCard!");
            }
        });
    }
}
//PCI接口
interface PCI1
{
    void open();
}

7.匿名內部類中如果需要傳遞參數的話,那麼這個參數就必須是final的。

/***
 * 匿名內部類傳參必須是final的 在這個例子中,
 * 可以爲A的構造方法傳入一個參數 在匿名內部類中,並沒有使用到這個參數。
 * 如果使用到了這個參數,那麼這個參數就必須是final的。
 */
public class Test4 {

    public A getA(final int num) {
        return new A(num) {
            public int getNum() {
                return num;
            }
        };
    }
}

class A {
    private int num;

    public A(int num) {
        this.num = num;
    }
}

8. 由於類是匿名的,自然沒有構造器,如果想模仿構造器,可以採用實例初始化({})

//匿名內部類想模仿構造器
public class Test5 {

    public B getB() {
        return new B() {
            void c() {
                System.out.println("dsds");
            }

            String s;
            int n;

            // 模仿構造器,採用實例初始化
            {
                s = "abcdefg";
                System.out.println("實例初始化");
            }

            public void show() {
                System.out.println(s);
            }
        };
    }

    public static void main(String[] args) {
        Test5 t = new Test5();
        B b1 = t.getB();
        b1.show();
    }
}

class B {

    public void show() {
    }
}

這裏寫圖片描述

四、static內部類(嵌套類)

使用嵌套類時有兩點需要注意:

   a、創建嵌套類對象時,不需要外圍類 
   b、在嵌套類中,不能像普通內部類一樣訪問外圍類的非static成員 
   c、被static修飾的內部類只能訪問外部類中的靜態成員
   d、如果內部類中定義了靜態成員,該內部類也必須是靜態的

嵌套類還有特殊之處,就是嵌套類中可以有static方法,static字段與嵌套類,而普通內部類中不能有這些。

//嵌套類(static內部類)總結
public class Test6 {

    static int i = 8;
    String s = "haha";

    static class C {
        //靜態類中定義非靜態方法是沒有意義的,因爲靜態類是不可以被實例化的
        public void show() {
            // 嵌套類不可以獲取外部類的非靜態成員
            System.out.println("show" + new Test6().s);
            //非靜態方法可以調用靜態方法,反之不行,因爲非靜態方法需要實例
            take();
        }

        static void take() {
            // 嵌套類可以直接獲取外部類的靜態成員
            System.out.println("take" + i);
        }
    }

    public static void main(String[] args) {
        //嵌套類的使用
        Test6.C.take();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章