Java基礎(chǔ)之接口與抽象類及多態(tài)、內(nèi)部類

final關(guān)鍵字

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

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

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

抽象類

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

public abstract class Animal {
  // 抽象類也有構(gòu)造函數(shù),給子類初始化用
    public Animal() {
        System.out.println("我們都是動物");
    }
    public abstract void eat();
    }
// 也可以存在非抽象函數(shù)
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();
       // 打印如下內(nèi)容
       // 我們都是動物
       // 我是一只小貓咪
       // 小貓吃魚
       // 睡覺了zzz~
  }
}

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

抽象類的特點

  1. 方法只有聲明沒有實現(xiàn),為抽象方法,修飾符abstract,抽象方法必須在抽象類中。
  2. 抽象類不可以實例化,即不能new。因為調(diào)用抽象方法沒有意義。
  3. 子類必須覆蓋抽象類的所有抽象方法后,該子類才能實例化,否則還是抽象類。

Q:抽象類有沒有構(gòu)造函數(shù)?

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

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

A;可以。但是接口interface必須全是抽象方法

abstract關(guān)鍵字不能和哪些關(guān)鍵字共存

  • private:因為子類要覆蓋抽象類的抽象方法,私有后不可訪問,怎么覆蓋?
  • static:抽象類中只是聲明了,子類需要重寫抽象類的方法。而靜態(tài)方法不能被重寫,所有矛盾了。
  • final:final修飾的方法不能被覆蓋,子類要覆蓋啊,所以不能加。

接口-interface

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

  • 變量規(guī)定為:public static final,即使寫成int a,也被認為是public static final int a;
  • 方法規(guī)定為:public abstract
  • 接口中的成員,都是public的

接口和抽象類一樣,不可實例化,用來給其他類擴展功能,接口與接口之間為繼承關(guān)系,接口可以多繼承。Java不支持直接多繼承,改成了多實現(xiàn),即一個子類多個接口,解決了單繼承的局限性。

抽象類和接口

  • 抽象類只能被單繼承,接口可以背多實現(xiàn)。

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

  • 抽象類定義基本的共性內(nèi)容,接口是定義額外的功能。

多態(tài)

對于Animal a = new Cat(),new出子類,卻讓父類指向子類,就叫多態(tài)。

public void method(Animal a) {
  a.eat(); // 運行時根據(jù)具體傳入的實參,來調(diào)用對應的方法
  // 若Dog和Cat都繼承了Animal,參數(shù)傳入Dog就執(zhí)行dog.eat(),傳入Cat就執(zhí)行cat.eat()
  // 這有點像C++中的動態(tài)綁定
}

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

多態(tài)好處

從上例可以看出,多態(tài)的好處:前期定義的代碼可以用于后期,比如再來一個Wolf類繼承Animal,就可以傳入wolf調(diào)用以上函數(shù),傳入的就是wolf了。

多態(tài)缺點

前期定義的內(nèi)容,不可調(diào)用后期子類的特有內(nèi)容。舉個例子

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

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

Animal a = new Cat();

  • 若子類沒有覆蓋父類的方法,就調(diào)用父類自己的方法。
  • 若子類調(diào)用了自己特有的方法,父類并沒有定義該方法,則編譯不通過。Cat類型提升為Animal后,Cat特有的方法會被舍棄,與Animal同名的函數(shù)被覆蓋

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

  • 對于成員變量,和靜態(tài)函數(shù):編譯和運行都是看父類是否具有該變量和靜態(tài),若a調(diào)用了Cat獨有的變量,則報錯。
  • 對于成員函數(shù),編譯時候檢查Animal是否定義該方法,運行時候執(zhí)行子類重寫的方法,若子類沒有重寫,自然執(zhí)行父類的。

內(nèi)部類-嵌套類

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

如果內(nèi)部類用到了靜態(tài)成員,則內(nèi)部類必須是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 
    }
    
  }
}

方法內(nèi)部的內(nèi)部類

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

    }
}

方法里的內(nèi)部類不能訪問外部類方法中的局部變量,除非變量被聲明為final類型,因為內(nèi)部類對象的生命周期超過局部變量的生命周期。有可能出現(xiàn)成員方法已調(diào)用結(jié)束,局部變量已死亡,但匿名內(nèi)部類的對象仍然活著。

Java 8中:如果局部變量被匿名內(nèi)部類訪問,那么該局部變量相當于自動使用了final修飾。

匿名內(nèi)部類

若一個程序的某個類只使用了一次,則可定義為匿名內(nèi)部類,用完就消亡。條件是必須繼承或者實現(xiàn)一個外部類或者接口。

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

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

下面內(nèi)容轉(zhuǎn)自Nerxious-博客園,總得得不錯,特地搬過來。

// 不使用匿名內(nèi)部類
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();
    }
}
//運行結(jié)果:eat something

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

 

// 匿名內(nèi)部類的基本實現(xiàn)
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();
    }
}
//運行結(jié)果:eat something

// 可以看到,我們直接將抽象類Person中的方法在大括號中實現(xiàn)了這樣便可以省略一個類的書寫并且,匿名內(nèi)部類還能用于接口上

 
// 在接口上使用匿名內(nèi)部類
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();
    }
}
//運行結(jié)果:eat something

 

// 由上面的例子可以看出,只要一個類是抽象的或是一個接口,那么其子類中的方法都可以使用匿名內(nèi)部類來實現(xiàn)。最常用的情況就是在多線程的實現(xiàn)上,因為要實現(xiàn)多線程必須繼承Thread類或是繼承Runnable接口

// Thread類的匿名內(nèi)部類實現(xiàn)
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();
    }
}
// 運行結(jié)果:1 2 3 4 5

// Runnable接口的匿名內(nèi)部類實現(xiàn)
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();
    }
}
// 運行結(jié)果:1 2 3 4 5

by @sunhiayu

2016.12.11

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 一:java概述:1,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境,java的開發(fā)工...
    ZaneInTheSun閱讀 2,688評論 0 11
  • 一、繼承 當兩個事物之間存在一定的所屬關(guān)系,即就像孩子從父母那里得到遺傳基因一樣,當然,java要遺傳的更完美,這...
    玉圣閱讀 1,072評論 0 2
  • 你很清楚的知道什么時候用抽象類,什么時候用接口么?p.s. 多文字預警! 1 抽象類和接口簡介 1.1 抽象類 ...
    Sharember閱讀 2,375評論 9 55
  • 1.import static是Java 5增加的功能,就是將Import類中的靜態(tài)方法,可以作為本類的靜態(tài)方法來...
    XLsn0w閱讀 1,263評論 0 2
  • 面向?qū)ο笾饕槍γ嫦蜻^程。 面向過程的基本單元是函數(shù)。 什么是對象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,091評論 0 4