JavaSE學習總結(六)——接口、抽象類、內部類

1、不須要實例化的緣由

看一個示例:java

package com.zhangguo.chapter5.s1;

/**動物園*/
public class Zoo {
    public static void main(String[] args) {
        Animal animal=new Animal();
        animal.eat();
        
        /**new誰調誰*/
        /**LSP*/
        Animal dog=new Dog();
        dog.eat();
    }
}

/**動物*/
class Animal {
    /***/
    public void eat(){
        System.out.println("動物吃東西");
    }
}

class Cat extends Animal{
    /**重寫吃*/
    public void eat(){
        System.out.println("貓吃魚");
    }
}

class Dog extends Animal{
    /**重寫吃*/
    public void eat(){
        System.out.println("狗吃骨頭");
    }
}

結果:程序員

問題:面試

從上面的示例能夠看出Animal是抽象的父類,其實現實中並不存在一種叫動物的實際對象,而動物僅僅是一個被抽象的概念。express

既然這樣,Animal就不該該實例化,只能做爲父類,在面向對象中(OOP)充當這種角色的類型有:抽象類,接口設計模式

抽象類與接口是一種比類更加抽象的類型。安全

1、不能實例化的類型

從上面的概念中能夠得知有些類型是不該該實例化的,沒有意義。多線程

java中抽象類更利於代碼的維護和重用。架構

1.由於抽象類不能實例化對象,因此必需要有子類來實現它以後才能使用。這樣就能夠把一些具備相同屬性和方法的組件進行抽象,這樣更有利於代碼和程序的維護。app

2.當又有一個具備類似的組件產生時,只須要實現該抽象類就能夠得到該抽象類的那些屬性和方法。less

在面向對象方法中,抽象類主要用來進行類型隱藏。構造出一個固定的一組行爲的抽象描述,可是這組行爲卻可以有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現爲全部可能的派生類。模塊能夠操做一個抽象體。因爲模塊依賴於一個固定的抽象體,所以它能夠是不容許修改的;同時,經過從這個抽象體派生,也可擴展此模塊的行爲功能。爲了可以實現面向對象設計的一個最核心的原則OCP(Open-Closed Principle),抽象類是其中的關鍵所在。

(1)、接口

(2)、抽象類

(3)、構造方法的訪問權限爲私有

package com.zhangguo.chapter5.s1;

/** 吃 接口 */
interface Ieatable {
    void eat();
}

/** 動物 抽象類 */
abstract class Animal {
    /** 吃 抽象方法 */
    public abstract void eat();
}

/** 學生 普通類 */
class Student {
    /** 私有構造方法 */
    private Student() {
    }
}

public class NoInstance {

    public static void main(String[] args) {
        Ieatable obj1 = new Ieatable(); // 錯誤 不能實例化接口
        Animal obj2 = new Animal(); // 錯誤 不能實例化抽象類
        Student obj3 = new Student(); // 錯誤 不能實例化私有構造方法類
    }
}

有些語言中靜態類也不能實例化,如C#

意義:越抽象,越穩定。抽象的能夠定義上層結構,規範頂層設計。抽象不會也不該該隨意變化。

2、抽象類

2.一、語法定義

抽象類定義,抽象類前使用abstract關鍵字修飾,則該類爲抽象類。

2.二、用途

a、在某些狀況下,某個父類只是知道其子類應該包含怎樣的方法,但沒法準確知道這些子類如何實現這些方法
(抽象類約束子類必須有哪些方法,但並不關注子類怎麼去實現這些方法。)

b、從多個具備相同特徵的類中抽象出一個抽象類,以這個抽象類做爲子類的模板,從而避免了子類設計的隨意性。

2.三、意義

限制規定子類必須實現某些方法,但不關注實現細節。

2.四、特色

1,抽象方法必定在抽象類中

2,抽象方法和抽象類都必須被abstract關鍵字修飾

3,抽象類不能夠用new建立對象。由於調用抽象方法沒意義4,抽象類中的抽象方法要被使用,必須由子類複寫起全部的抽象方法後,創建子類對象調用。

若是子類只覆蓋了部分抽象方法,那麼該子類仍是一個抽象類。

五、抽象方法沒有方法體,以分號結束

示例:

package com.zhangguo.chapter5.s2;

import java.util.Scanner;

/** 動物 */
public abstract class Animal {
    /** 名稱 */
    public String name;

    /** 抽象方法,無方法體,必須被子類實現(重寫) */
    public abstract void eat();
    
    /**測試*/
    public static void main(String[] args) {
        //LSP 里氏替換原則
        Animal dog=new Dog();
        dog.name="博美";
        //int i=1;
        //Scanner input=new Scanner(System.in);
        dog.eat();
    }
    
    /**抽象類中能夠有非抽象方法,能夠有靜態方法*/
    public void show(){};
}

/**抽象類動物(Animal)的子類,必須實現父類未實現的方法*/
class Dog extends Animal {
    //註解
    @Override
    public void eat() {
        System.out.println(this.name+"狗在吃骨頭");
    }
}

運行結果:

3、接口

接口是一組沒有實例的標準與規範。

沒有接口的電腦是怎樣的?

3.一、爲何須要接口

繼承:描述事物的天然屬性和行爲的複用。

接口:描述事物的社會屬性和行爲的複用。

一、重要性:在Java語言中, abstract class 和interface 是支持抽象類定義的兩種機制。正是因爲這兩種機制的存在,才賦予了Java強大的 面向對象能力。

二、簡單、規範性:若是一個項目比較龐大,那麼就須要一個能理清全部業務的架構師來定義一些主要的接口,這些接口不只告訴開發人員你須要實現那些業務,並且也將命名規範限制住了(防止一些開發人員隨便命名致使別的程序員沒法看明白)。

三、維護、拓展性:好比你要作一個畫板程序,其中裏面有一個面板類,主要負責繪畫功能,而後你就這樣定義了這個類。

四、安全、嚴密性:接口是實現軟件鬆耦合的重要手段,它描敘了系統對外的全部服務,而不涉及任何具體的實現細節。這樣就比較安全、嚴密一些(通常軟件服務商考慮的比較多)。

由於類具備「單根性」,全部的類只能有一個直接父類,經過能夠實現一個類有多個父類,能夠實現多重繼承

package com.zhangguo.chapter5.s2;

/**usb接口*/
public interface IUSB {
    /**未實現的方法,發送數據*/
    void sendData();
}

/**網線接口*/
interface IRJ45
{
    /**未實現的方法,接收數據*/
    void receiveData();
}

/**設備*/
class Device{
    
}

/**電腦*/
/**一個類只能繼承一個類,但能夠實現多個接口*/
class Computer extends Device implements IUSB,IRJ45{

    @Override
    public void receiveData() {
        System.out.println("接收數據");
    }

    @Override
    public void sendData() {
        System.out.println("發送數據");
    }
    
    
    interface IA{}
    interface IB{}
    /**接口能夠繼承其它他口*/
    interface IC extends IA,IB{}
    
    class CC{}
    /**繼承須要寫在實現接口前*/
    class DD extends CC implements IC {}
}

測試:

package com.zhangguo.chapter5.s2;

public class ComputerClient {

    public static void main(String[] args) {
        Computer ln=new Computer();
        ln.sendData();
        ln.receiveData();
        
        /**接口是一種類型*/
        IUSB usb=new Computer();
        
        /**一個對象能夠有多個不一樣的類型*/
        
    }

}

3.二、接口的特色

1)、接口中的方法能夠有參數列表和返回類型,但不能有任何方法體

2)、接口中能夠包含字段,可是會被隱式的聲明爲static和final。

3)、接口中的字段只是被存儲在該接口的靜態存儲區域內,而不屬於該接口。

4)、接口中的方法能夠被聲明爲public或不聲明,但結果都會按照public類型處理。

5)、當實現一個接口時,須要將被定義的方法聲明爲public類型的,不然爲默認訪問類型,Java編譯器不容許這種狀況。

6)、若是沒有實現接口中全部方法,那麼建立的仍然是一個接口。子類必須實現接口中未實現的方法,除非子類也是接口。

7)、擴展一個接口來生成新的接口應使用關鍵字extends,實現一個接口使用implements。

8)、接口中的方法是抽象方法(abstract),不能是靜態方法(static))、接口的全部方法都是抽象的,而抽象方法是沒有static,有static的方法是不能override的,因此這樣定義接口才有意義。

接口中的字段是默認爲:static final ,通俗說就是常量

4、Final(最終的)

4.一、final修飾類

  final修飾的類不容許被繼承。

  一個類不能既是final的,又是abstract的。由於abstract的主要目的是定義一種約定,讓子類去實現這種約定,而final表示該類不能被繼承,二者矛盾。

4.二、final修飾方法

  final修飾方法,表示該方法不能被子類中的方法覆寫Override。不能被重寫

4.三、final修飾變量

  final成員變量表示常量,只能被賦值一次,賦值後值再也不改變

  當final修飾一個原生數據類型時,表示該原生數據類型的值不能發生變化;

  若是final修飾一個引用類型時,表示該引用類型不能再指向其餘對象了,但該引用所指向的對象的內容是能夠發生變化的。

  本質上是一回事,由於引用的值是一個地址,final要求值,即地址的值不發生變化。

  final修飾一個成員變量(屬性),必需要顯示初始化。

  這裏有兩種初始化方式,一種是在變量聲明的時候初始化;第二種方法是在聲明變量的時候不賦初值,可是要在這個變量所在的類的全部的構造函數中對這個變量賦初值。

  當函數的參數類型聲明爲final時,說明該參數是隻讀型的。

5、內部類

5.一、什麼是內部類

  內部類是指在一個外部類的內部再定義一個類。內部類做爲外部類的一個成員,而且依附於外部類而存在的。內部類可爲靜態,可用protected和private修飾(而外部類只能使用public和缺省的包訪問權限)。內部類主要有如下幾類:內部、局部內部、靜內部、匿名內部

示例:

package com.zhangguo.innnerclass;

public class InnerDemo2 {

    public static void main(String[] args) {
        //實例化的方法一
        Box box=new Box();
        box.height=286;
        Box.InBox inbox=box.new InBox();
        inbox.show();
        
        //實例化的方法二
        Box.InBox box2=new Box().new InBox();
        box2.show();
    }

}

//外部類
class Box
{
    //外部類的成員變量
    public int height=198;
    
    //成員內部類
    class InBox{
        //內部類的成員
        public void show(){
            System.out.println("外部類的高度:"+height);
        }
    }
}

結果:

外部類的高度:286
外部類的高度:198

從上面的例子不難看出,內部類其實嚴重破壞了良好的代碼結構,但爲何還要使用內部類呢?
由於內部類能夠隨意使用外部類的成員變量(包括私有)而不用生成外部類的對象,這也是內部類的惟一優勢程序編譯事後會產生兩個.class文件,分別是Out.class和Out$In.class其中$表明了上面程序中Out.In中的那個。

Out.In in = new Out().new In()能夠用來生成內部類的對象,這種方法存在兩個小知識點須要注意

1.開頭的Out是爲了標明須要生成的內部類對象在哪一個外部類當中

2.必須先有外部類的對象才能生成內部類的對象,由於內部類的做用就是爲了訪問外部類中的成員變量

示例:

package com.zhangguo.innnerclass;

public class InnerDemo3 {

    public static void main(String[] args) {
        new OutBox().new InBox().show();
    }
}

//外部類
class OutBox
{
    //外部類的成員變量
    private int height=197;
    //成員內部類
    class InBox{
        //內部類的成員變量
        private int height=198;
        //內部類的成員
        public void show(){
            //內部類的局部變量
            int height=199;
            System.out.println("內部類的局部變量:"+height);
            System.out.println("內部類的成員變量:"+this.height);
            System.out.println("外部類的成員變量:"+OutBox.this.height);
        }
    }
}

結果:

內部類的局部變量:199
內部類的成員變量:198
外部類的成員變量:197

內部類在沒有同名成員變量和局部變量的狀況下,內部類會直接訪問外部類的成員變量,而無需指定Out.this.屬性名

不然,內部類中的局部變量會覆蓋外部類的成員變量

而訪問內部類自己的成員變量可用this.屬性名,訪問外部類的成員變量須要使用Out.this.屬性名

5.二、內部類的共性

(1)、內部類仍然是一個獨立的類,在編譯以後內部類會被編譯成獨立的.class文件,可是前面冠之外部類的類名和$符號 。

(2)、內部類不能用普通的方式訪問。

(3)、內部類聲明成靜態的,就不能隨便的訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變量 。

(4)、外部類不能直接訪問內部類的的成員,但能夠經過內部類對象來訪問

 

  內部類是外部類的一個成員,所以內部類能夠自由地訪問外部類的成員變量,不管是不是private的。

  由於當某個外圍類的對象建立內部類的對象時,此內部類會捕獲一個隱式引用,它引用了實例化該內部對象的外圍類對象。經過這個指針,能夠訪問外圍類對象的所有狀態。

經過反編譯內部類的字節碼,分析以後主要是經過如下幾步作到的: 
  1 編譯器自動爲內部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用; 
  2 編譯器自動爲內部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數爲1中添加的成員變量賦值; 
  3 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。

5.三、爲何須要內部類

其主要緣由有如下幾點:

  • 內部類方法能夠訪問該類定義所在的做用域的數據,包括私有的數據

  • 內部類能夠對同一個包中的其餘類隱藏起來,通常的非內部類,是不容許有 private 與protected權限的,但內部類能夠

  • 能夠實現多重繼承

  • 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名內部類比較便捷

使用內部類最吸引人的緣由是:

  每一個內部類都能獨立地繼承自一個(接口的)實現,因此不管外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。你們都知道Java只能繼承一個類,它的多重繼承在咱們沒有學習內部類以前是用接口來實現的。但使用接口有時候有不少不方便的地方。好比咱們實現一個接口就必須實現它裏面的全部方法。而有了內部類就不同了。它可使咱們的類繼承多個具體類或抽象類。

你們看下面的例子:

 

package com.zhangguo.innnerclass;

public class InnerDemo4 {

    public static void main(String[] args) {
        Person person=new Person();
        person.bark();
    }
}

abstract class Fruit{
    public String name="水果";
}

abstract class Animal{
    public void bark(){
        System.out.println("呱呱...");
    }
}

class Person{
    
    class FruitAttr extends Fruit{
    }
    
    class AnimalAttr extends Animal{
        @Override
        public void bark() {
            super.bark();
            System.out.println(getName()+"嘎嘎...");
        }
    }
    
    public String getName(){
        return new FruitAttr().name;
    }
    
    public void bark(){
        new AnimalAttr().bark();
    }
}

結果:

呱呱...
水果嘎嘎...

5.四、成員內部類

  即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,能夠與普通成員同樣進行修飾和限制。成員內部類不能含有static的變量和方法。

public class Outer { private static int i = 1; private int j = 10; private int k = 20; public static void outer_f1() {} public void outer_f2() {} // 成員內部類中,不能定義靜態成員 // 成員內部類中,能夠訪問外部類的全部成員
    class Inner { // static int inner_i = 100;//內部類中不容許定義靜態變量
        int j = 100; // 內部類和外部類的實例變量能夠共存
        int inner_i = 1; void inner_f1() { System.out.println(i); // 在內部類中訪問內部類本身的變量直接用變量名
 System.out.println(j); // 在內部類中訪問內部類本身的變量也能夠用this.變量名
            System.out.println(this.j); // 在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
            System.out.println(Outer.this.j); // 若是內部類中沒有與外部類同名的變量,則能夠直接用變量名訪問外部類變量
 System.out.println(k); outer_f1(); outer_f2(); } } // 外部類的非靜態方法訪問成員內部類
    public void outer_f3() { Inner inner = new Inner(); inner.inner_f1(); } // 外部類的靜態方法訪問成員內部類,與在外部類外部訪問成員內部類同樣
    public static void outer_f4() { // step1 創建外部類對象
        Outer out = new Outer(); // step2 根據外部類對象創建內部類對象
        Inner inner = out.new Inner(); // step3 訪問內部類的方法
 inner.inner_f1(); } public static void main(String[] args) { //outer_f4();//該語句的輸出結果和下面三條語句的輸出結果同樣 // 若是要直接建立內部類的對象,不能想固然地認爲只需加上外圍類Outer的名字, // 就能夠按照一般的樣子生成內部類的對象,而是必須使用此外圍類的一個對象來 // 建立其內部類的一個對象: // Outer.Inner outin = out.new Inner() // 所以,除非你已經有了外圍類的一個對象,不然不可能生成內部類的對象。由於此 // 內部類的對象會悄悄地連接到建立它的外圍類的對象。若是你用的是靜態的內部類, // 那就不須要對其外圍類對象的引用。
        Outer out = new Outer(); Outer.Inner outin = out.new Inner(); outin.inner_f1(); } }

示例:
class Person{
    
    static class MyClass{
        double PI=3.14;  //容許
    }
    
    class FruitAttr extends Fruit{
        //The field PI cannot be declared static in a non-static inner type, unless initialized with a constant expression
        //默認狀況下內部類的成員不能是靜態的,除非內部類也是靜態的,或者將成員聲明爲常量表達式如(static final)
        //static double PI=3.14;
        static final double PI=3.14;
    }
} 

5.五、局部內部類

  在方法中定義的內部類稱爲局部內部類。與局部變量相似,局部內部類不能有訪問說明符,由於它不是外圍類的一部分,可是它能夠訪問當前代碼塊內的常量,和此外圍類全部的成員。

須要注意的是:

  (1)、局部內部類只能在定義該內部類的方法內實例化,不能夠在此方法外對其實例化。

  (2)、局部內部類對象不能使用該內部類所在方法的非final局部變量。

具體緣由等下再說

public class Outer { private int s = 100; private int out_i = 1; public void f(final int k) { final int s = 200; int i = 1; final int j = 10; // 定義在方法內部
        class Inner { int s = 300;// 能夠定義與外部類同名的變量 // static int m = 20;//不能夠定義靜態變量
            Inner(int k) { inner_f(k); } int inner_i = 100; void inner_f(int k) { // 若是內部類沒有與外部類同名的變量,在內部類中能夠直接訪問外部類的實例變量
 System.out.println(out_i); // 能夠訪問外部類的局部變量(即方法內的變量),可是變量必須是final的
 System.out.println(j); // System.out.println(i); // 若是內部類中有與外部類同名的變量,直接用變量名訪問的是內部類的變量
 System.out.println(s); // 用this.變量名訪問的也是內部類變量
                System.out.println(this.s); // 用外部類名.this.內部類變量名訪問的是外部類變量
                System.out.println(Outer.this.s); } } new Inner(k); } public static void main(String[] args) { // 訪問局部內部類必須先有外部類對象
        Outer out = new Outer(); out.f(3); } }

5.七、靜態內部類(嵌套類)

  若是你不須要內部類對象與其外圍類對象之間有聯繫,那你能夠將內部類聲明爲static。這一般稱爲嵌套類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類對象隱含地保存了一個引用,指向建立它的外圍類對象。然而,當內部類是static的時,就不是這樣了。嵌套類意味着:

  1. 要建立嵌套類的對象,並不須要其外圍類的對象。

  2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。

public class Outer { private static int i = 1; private int j = 10; public static void outer_f1() {} public void outer_f2() {} // 靜態內部類能夠用public,protected,private修飾 // 靜態內部類中能夠定義靜態或者非靜態的成員
    private static class Inner { static int inner_i = 100; int inner_j = 200; static void inner_f1() { // 靜態內部類只能訪問外部類的靜態成員(包括靜態變量和靜態方法)
            System.out.println("Outer.i" + i); outer_f1(); } void inner_f2() { // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變量和非靜態方法) // System.out.println("Outer.i"+j); // outer_f2();
 } } public void outer_f3() { // 外部類訪問內部類的靜態成員:內部類.靜態成員
 System.out.println(Inner.inner_i); Inner.inner_f1(); // 外部類訪問內部類的非靜態成員:實例化內部類便可
        Inner inner = new Inner(); inner.inner_f2(); } public static void main(String[] args) { new Outer().outer_f3(); } }

生成一個靜態內部類不須要外部類成員:這是靜態內部類和成員內部類的區別。靜態內部類的對象能夠直接生成:Outer.Inner in = new Outer.Inner();而不須要經過生成外部類對象來生成。這樣實際上使靜態內部類成爲了一個頂級類(正常狀況下,你不能在接口內部放置任何代碼,但嵌套類能夠做爲接口的一部分,由於它是static 的。只是將嵌套類置於接口的命名空間內,這並不違反接口的規則)

5.八、匿名內部類

簡單地說:匿名內部類就是沒有名字的內部類。什麼狀況下須要使用匿名內部類?若是知足下面的一些條件,使用匿名內部類是比較合適的:
  • 只用到類的一個實例。
  • 類在定義後立刻用到。
  • 類很是小(SUN推薦是在4行代碼如下)
  • 給類命名並不會致使你的代碼更容易被理解。
在使用匿名內部類時,要記住如下幾個原則:
  •   匿名內部類不能有構造方法。

  •   匿名內部類不能定義任何靜態成員、方法和類。

  •   匿名內部類不能是public,protected,private,static。

  •   只能建立匿名內部類的一個實例。

  •    一個匿名內部類必定是在new的後面,用其隱含實現一個接口或實現一個類。

  •   因匿名內部類爲局部內部類,因此局部內部類的全部限制都對其生效。

下面的代碼展現的是,若是你的基類須要一個有參數的構造器,應該怎麼辦:
public class Parcel7 {  public Wrapping wrap(int x) {  // Base constructor call:
 return new Wrapping(x) { // Pass constructor argument.
 public int value() {  return super.value() * 47; } }; // Semicolon required
 }  public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } }
只需簡單地傳遞合適的參數給基類的構造器便可,這裏是將x 傳進new Wrapping(x)。在匿名內部類末尾的分號,並非用來標記此內部類結束(C++中是那樣)。實際上,它標記的是表達式的結束,只不過這個表達式正巧包含了內部類罷了。所以,這與別的地方使用的分號是一致的。
 
若是在匿名類中定義成員變量或者使用帶參數的構造函數,你一樣可以對其執行初始化操做:
public class Parcel8 { // Argument must be final to use inside // anonymous inner class:
    public Destination dest(final String name, String city) { return new Destination(name, city) { private String label = name; public String getName() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania", "gz"); } abstract class Destination { Destination(String name, String city) { System.out.println(city); } abstract String getName(); } }
注意這裏的形參city,因爲它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,因此沒必要定義爲final。

匿名內部類也就是沒有名字的內部類

正由於沒有名字,因此匿名內部類只能使用一次,它一般用來簡化代碼編寫

但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口

示例:

package com.zhangguo.innnerclass;

public class InnerDemo5 {

    public static void main(String[] args) {
        IFly bird=new Bird();
        bird.fly();
        
        IFly pig=new IFly() {
            @Override
            public void fly() {
                System.out.println("豬在飛...");
            }
        };
        pig.fly();
        
        Display(new Man(){
            @Override
            public void show() {
                super.show();
                System.out.println("我是一個男人!");
            }
        });
        
        Man woman=new Man(){
            @Override
            public void show() {
                super.show();
                System.out.println("我是一個女人!");
            }
        };
        woman.show();
    }
    
    public static void Display(Man man){
        man.show();
    }
}

interface IFly{
    void fly();
}

class Man{
    public void show(){
        System.out.println("我是一我的");
    }
}

Bird類:

package com.zhangguo.innnerclass;

public class Bird implements IFly {
    @Override
    public void fly() {
        System.out.println("鳥在飛...");
    }
}

 

結果:

鳥在飛...
豬在飛...
我是一我的
我是一個男人!
我是一我的
我是一個女人!

實例1:不使用匿名內部類來實現抽象方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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類只使用一次,那麼將其編寫爲獨立的一個類豈不是很麻煩?

這個時候就引入了匿名內部類

實例2:匿名內部類的基本實現

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中的方法在大括號中實現了

這樣即可以省略一個類的書寫

而且,匿名內部類還能用於接口上

實例3:在接口上使用匿名內部類

?
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接口

實例4: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

實例5:Runnable接口的匿名內部類實現

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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

5.九、內部類的重載問題

  若是你建立了一個內部類,而後繼承其外圍類並從新定義此內部類時,會發生什麼呢?也就是說,內部類能夠被重載嗎?這看起來彷佛是個頗有用的點子,可是「重載」內部類就好像它是外圍類的一個方法,其實並不起什麼做用:
 
class Egg { private Yolk y; protected class Yolk { public Yolk() { System.out.println("Egg.Yolk()"); } } public Egg() { System.out.println("New Egg()"); y = new Yolk(); } } public class BigEgg extends Egg { public class Yolk { public Yolk() { System.out.println("BigEgg.Yolk()"); } } public static void main(String[] args) { new BigEgg(); }
}
輸出結果爲:
New Egg()
Egg.Yolk()
 
缺省的構造器是編譯器自動生成的,這裏是調用基類的缺省構造器。你可能認爲既然建立了BigEgg 的對象,那麼所使用的應該是被「重載」過的Yolk,但你能夠從輸出中看到實際狀況並非這樣的。
這個例子說明,當你繼承了某個外圍類的時候,內部類並無發生什麼特別神奇的變化。這兩個內部類是徹底獨立的兩個實體,各自在本身的命名空間內。固然,明確地繼承某個內部類也是能夠的:
class Egg2 { protected class Yolk { public Yolk() { System.out.println("Egg2.Yolk()"); } public void f() { System.out.println("Egg2.Yolk.f()"); } } private Yolk y = new Yolk(); public Egg2() { System.out.println("New Egg2()"); } public void insertYolk(Yolk yy) { y = yy; } public void g() { y.f(); } } public class BigEgg2 extends Egg2 { public class Yolk extends Egg2.Yolk { public Yolk() { System.out.println("BigEgg2.Yolk()"); } public void f() { System.out.println("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } }
輸出結果爲:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
 
如今BigEgg2.Yolk 經過extends Egg2.Yolk 明確地繼承了此內部類,而且重載了其中的方法。Egg2 的insertYolk()方法使得BigEgg2 將它本身的Yolk 對象向上轉型,而後傳遞給引用y。因此當g()調用y.f()時,重載後的新版的f()被執行。第二次調用Egg2.Yolk()是BigEgg2.Yolk 的構造器調用了其基類的構造器。能夠看到在調用g()的時候,新版的f()被調用了。

5.十、內部類的繼承問題

  由於內部類的構造器要用到其外圍類對象的引用,因此在你繼承一個內部類的時候,事情變得有點複雜。問題在於,那個「祕密的」外圍類對象的引用必須被初始化,而在被繼承的類中並不存在要聯接的缺省對象。要解決這個問題,需使用專門的語法來明確說清它們之間的關聯:
class WithInner { class Inner { Inner(){ System.out.println("this is a constructor in WithInner.Inner"); }; } } public class InheritInner extends WithInner.Inner { // ! InheritInner() {} // Won't compile
 InheritInner(WithInner wi) { wi.super(); System.out.println("this is a constructor in InheritInner"); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
輸出結果爲:
this is a constructor in WithInner.Inner
this is a constructor in InheritInner
 
能夠看到,InheritInner 只繼承自內部類,而不是外圍類。可是當要生成一個構造器時,缺省的構造器並不算好,並且你不能只是傳遞一個指向外圍類對象的引用。此外,你必須在構造器內使用以下語法:
enclosingClassReference.super();
這樣才提供了必要的引用,而後程序才能編譯經過。

爲何非靜態內部類中不能有static修飾的屬性,但卻能夠有常量?

如:

public class InnerClassDemo{ int x; class A{ static  int a = 0;//這樣寫是不合法的.
        static final int b=0;//這樣寫是合法的
 } }

定義一個靜態的域或者方法,要求在靜態環境或者頂層環境,即若是加上 static class A變成靜態內部類就ok非靜態內部類 依賴於一個外部類對象,而靜態域/方法是不依賴與對象——僅與類相關(細說了,就是加載靜態域時,根本沒有外部類對象)所以,非靜態內部類中不能定義靜態域/方法,編譯過不了。


而常量之因此能夠(不論有無static),由於java在編譯期就肯定全部常量,放到所謂的常量池當中。常量的機制和普通變量不同

匿名內部類和局部內部類只能訪問final變量

6、視頻與示例下載

上課示例下載

B站視頻在線觀看

7、面試題

一、Java中有那些不能實例化的類型?

二、抽象類有何特色?

三、接口有何特色?

8、面向對象綜合應用

8.一、面向對象5大設計原則

8.二、設計模式

相關文章
相關標籤/搜索