Java基礎之介面與抽象類及多型、內部類

weixin_33936401發表於2016-12-11

final關鍵字

  • 被其修飾的類,不能被繼承。
  • 被其修飾的方法,不能被覆蓋。
  • 被其修飾的變數,是一個常量,不能被修改,所以定義時必須初始化(和C++的const類似)。

一般有final,會搭配static使用。如final static double PI = 3.14;

  • 常量的命名規則 --> 所有字母大寫,多個單詞,中間用下劃線連線。

抽象類

貓和狗有共性,將共性抽取出來,放入Animal中,Animal是抽象的(想象不出實體是什麼)。

public abstract class Animal {
  // 抽象類也有建構函式,給子類初始化用
    public Animal() {
        System.out.println("我們都是動物");
    }
    public abstract void eat();
    }
// 也可以存在非抽象函式
public void sleep() {
        System.out.println("睡覺了zzz~");
    }
}

class Cat extends Animal {
    Cat() {
        System.out.println("我是一隻小貓咪");
    }
    @Override
    public void eat() {
        System.out.println("小貓吃魚");
    }
}

class Dog extends Animal {
    Dog() {
        System.out.println("我是一條小狗狗");
    }

    @Override
    public void eat() {
        System.out.println("狗狗啃骨頭");
    }
}

public class Demo {
  public static void main(String args[]) {
     public static void main(String[] args) {
        Animal a = new Cat();
        a.eat();
        a.sleep();
       // 列印如下內容
       // 我們都是動物
       // 我是一隻小貓咪
       // 小貓吃魚
       // 睡覺了zzz~
  }
}

因為類Cat、類Dog或者其他類要繼承類Animal,且類Animal的show()是抽象的未被定義的,子類必須覆蓋父類的方法,定義自己特有的eat()方法(貓和狗吃的東西不全一樣),故在抽象類中的方法只是宣告

抽象類的特點

  1. 方法只有宣告沒有實現,為抽象方法,修飾符abstract,抽象方法必須在抽象類中。
  2. 抽象類不可以例項化,即不能new。因為呼叫抽象方法沒有意義。
  3. 子類必須覆蓋抽象類的所有抽象方法後,該子類才能例項化,否則還是抽象類。

Q:抽象類有沒有建構函式?

A:有,用來給子類初始化,如上例,最先列印我們都是動物

Q:可以沒有抽象方法嗎,或者說,可以不全是抽象方法嗎?

A;可以。但是介面interface必須全是抽象方法

abstract關鍵字不能和哪些關鍵字共存

  • private:因為子類要覆蓋抽象類的抽象方法,私有後不可訪問,怎麼覆蓋?
  • static:抽象類中只是宣告瞭,子類需要重寫抽象類的方法。而靜態方法不能被重寫,所有矛盾了。
  • final:final修飾的方法不能被覆蓋,子類要覆蓋啊,所以不能加。

介面-interface

當一個抽象類中全部是抽象方法時,可將其定義為interface。

  • 變數規定為:public static final,即使寫成int a,也被認為是public static final int a;
  • 方法規定為:public abstract
  • 介面中的成員,都是public的

介面和抽象類一樣,不可例項化,用來給其他類擴充套件功能,介面與介面之間為繼承關係,介面可以多繼承。Java不支援直接多繼承,改成了多實現,即一個子類多個介面,解決了單繼承的侷限性。

抽象類和介面

  • 抽象類只能被單繼承,介面可以背多實現。

  • 抽象類中也可以定義非抽象方法,子類可以直接用非抽象方法。介面中只能定義抽象方法。共同點是,其抽象方法都必須被重寫。

  • 抽象類定義基本的共性內容,介面是定義額外的功能。

多型

對於Animal a = new Cat(),new出子類,卻讓父類指向子類,就叫多型。

public void method(Animal a) {
  a.eat(); // 執行時根據具體傳入的實參,來呼叫對應的方法
  // 若Dog和Cat都繼承了Animal,引數傳入Dog就執行dog.eat(),傳入Cat就執行cat.eat()
  // 這有點像C++中的動態繫結
}

// 和以下函式過載比較起來,是不是方便了
public void method(Dog a) {
  a.eat()
}
public void method(Cat a) {
  a.eat();
}

多型好處

從上例可以看出,多型的好處:前期定義的程式碼可以用於後期,比如再來一個Wolf類繼承Animal,就可以傳入wolf呼叫以上函式,傳入的就是wolf了。

多型缺點

前期定義的內容,不可呼叫後期子類的特有內容。舉個例子

public void method(Animal a) {
  a.eat(); // 父類定義了該方法
  a.catchMouse(); // Animal沒有定義“抓老鼠”的方法,若是傳入的引數不是Cat,則報錯
}

// 怎麼解決?強轉回來!如下
public void method(Animal a) {
  a.eat();
  // 先判斷傳入的實參,若是Cat,進行強轉回Cat後,再執行“抓老鼠”
  if (a instanceof Cat){
    Cat c = (Cat)a;,
    c.catchMouse();
  }
  
  // 若傳入的是狗,就執行“叫”
  if (a instanceof Dog) {
    Dog d = (Dog)a;
    d.bark();
  }
}

Animal a = new Cat();

  • 若子類沒有覆蓋父類的方法,就呼叫父類自己的方法。
  • 若子類呼叫了自己特有的方法,父類並沒有定義該方法,則編譯不通過。Cat型別提升為Animal後,Cat特有的方法會被捨棄,與Animal同名的函式被覆蓋

換個說法,a是Animal,它只能用Animal定義過的方法和變數,但執行函式時實際執行的是Cat的重寫方法。總的來說,如下

  • 對於成員變數,和靜態函式:編譯和執行都是看父類是否具有該變數和靜態,若a呼叫了Cat獨有的變數,則報錯。
  • 對於成員函式,編譯時候檢查Animal是否定義該方法,執行時候執行子類重寫的方法,若子類沒有重寫,自然執行父類的。

內部類-巢狀類

public class Out {
  private String out;
  
  private class In {
    private String in;
  }
  
}
  • 內部類可以直接訪問外部類的成員(private的也行)
  • 但是外部類要訪問內部類,必須建立內部類的物件。
  • 內部類也可以是static的,這是,內部類隨著外部類的載入而載入,相當於是外部類。
Out.In in = new Out().new In(); // 非static內部類的例項化
Out.In in = new Out.In(); // static內部類可以這樣例項化
In in = new In(); // static內部類也可以這樣例項化,相當於外部類了,僅在本例有效

如果內部類用到了靜態成員,則內部類必須是static的。

同名變數的訪問。

public class Out {
  private int num = 1;
  
  private class In {
    int num= 2;
    public void show() {
      int num = 3;
      System.out.println(num); // 列印3
      System.out.println(this.num); // 列印2
      System.out.println(Out.this.num); // 列印1 
    }
    
  }
}

方法內部的內部類

public class Out {
    public void func() {
      // num是func方法中的區域性變數,func中又定義了內部類,用到這個num,該num必須是final的
        final int num = 1;
        // java8中,會預設加上final,所以可以直接int num = 1;
        class In {
            public void show() {
                System.out.println(num);
            }
        }

    }
}

方法裡的內部類不能訪問外部類方法中的區域性變數,除非變數被宣告為final型別,因為內部類物件的生命週期超過區域性變數的生命週期。有可能出現成員方法已呼叫結束,區域性變數已死亡,但匿名內部類的物件仍然活著。

Java 8中:如果區域性變數被匿名內部類訪問,那麼該區域性變數相當於自動使用了final修飾。

匿名內部類

若一個程式的某個類只使用了一次,則可定義為匿名內部類,用完就消亡。條件是必須繼承或者實現一個外部類或者介面。

abstract class Person {
    public abstract void eat();
}
// 不用內部類的情況,需要再寫一個類繼承Person覆蓋方法
class Child extends Person {
  @Override
  public abstract void eat() {
    System.out.println("Child eat");
  }
}

public class Out {
    public static void main(String[] args) {
      // 匿名內部類,這裡相當於繼承了抽象類Person,新寫了一個無名的子類,並覆蓋了eat方法
        new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        }.eat();
    }
}

下面內容轉自Nerxious-部落格園,總得得不錯,特地搬過來。

// 不使用匿名內部類
abstract class Person {
    public abstract void eat();
}
 
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}
//執行結果:eat something

// 可以看到,我們用Child繼承了Person類,然後實現了Child的一個例項,將其向上轉型為Person類的引用但是,如果此處的Child類只使用一次,那麼將其編寫為獨立的一個類豈不是很麻煩?這個時候就引入了匿名內部類

 

// 匿名內部類的基本實現
abstract class Person {
    public abstract void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
//執行結果:eat something

// 可以看到,我們直接將抽象類Person中的方法在大括號中實現了這樣便可以省略一個類的書寫並且,匿名內部類還能用於介面上

 
// 在介面上使用匿名內部類
interface Person {
    public void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
//執行結果:eat something

 

// 由上面的例子可以看出,只要一個類是抽象的或是一個介面,那麼其子類中的方法都可以使用匿名內部類來實現。最常用的情況就是在多執行緒的實現上,因為要實現多執行緒必須繼承Thread類或是繼承Runnable介面

// Thread類的匿名內部類實現
public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.print(i + " ");
                }
            }
        };
        t.start();
    }
}
// 執行結果:1 2 3 4 5

// Runnable介面的匿名內部類實現
public class Demo {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.print(i + " ");
                }
            }
        };
        Thread t = new Thread(r);
        t.start();
    }
}
// 執行結果:1 2 3 4 5

by @sunhiayu

2016.12.11

相關文章