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


一、不需要實例化的原因

看一個示例:

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是抽象的父類,其實現實中並不存在一種叫動物的實際對象,而動物僅僅是一個被抽象的概念。

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

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

一、不能實例化的類型

從上面的概念中可以得知有些類型是不應該實例化的,沒有意義。

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

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

2.當又有一個具有相似的組件產生時,只需要實現該抽象類就可以獲得該抽象類的那些屬性和方法。

在面向對象方法中,抽象類主要用來進行類型隱藏。構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。模塊可以操作一個抽象體。由於模塊依賴於一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。為了能夠實現面向對象設計的一個最核心的原則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.1、語法定義

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

2.2、用途

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

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

2.3、意義

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

2.4、特點

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

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

3,抽象類不可以用new創建對象。因為調用抽象方法沒意義4,抽象類中的抽象方法要被使用,必須由子類復寫起所有的抽象方法后,建立子類對象調用。

如果子類只覆蓋了部分抽象方法,那么該子類還是一個抽象類。

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

示例:

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.1、為什么需要接口

繼承:描述事物的自然屬性和行為的復用。

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

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

2、簡單、規范性:如果一個項目比較龐大,那么就需要一個能理清所有業務的架構師來定義一些主要的接口,這些接口不僅告訴開發人員你需要實現那些業務,而且也將命名規范限制住了(防止一些開發人員隨便命名導致別的程序員無法看明白)。

3、維護、拓展性:比如你要做一個畫板程序,其中里面有一個面板類,主要負責繪畫功能,然后你就這樣定義了這個類。

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

因為類具有“單根性”,所有的類只能有一個直接父類,通過可以實現一個類有多個父類,可以實現多重繼承

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.2、接口的特點

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

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

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

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

5)、當實現一個接口時,需要將被定義的方法聲明為public類型的,否則為默認訪問類型,Java編譯器不允許這種情況。

6)、如果沒有實現接口中所有方法,那么創建的仍然是一個接口。子類必須實現接口中未實現的方法,除非子類也是接口。

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

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

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

四、Final(最終的)

4.1、final修飾類

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

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

4.2、final修飾方法

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

4.3、final修飾變量

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

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

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

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

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

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

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

五、內部類

5.1、什么是內部類

  內部類是指在一個外部類的內部再定義一個類。內部類作為外部類的一個成員,並且依附於外部類而存在的。內部類可為靜態,可用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.2、內部類的共性

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

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

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

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

 

  內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否是private的。

  因為當某個外圍類的對象創建內部類的對象時,此內部類會捕獲一個隱式引用,它引用了實例化該內部對象的外圍類對象。通過這個指針,可以訪問外圍類對象的全部狀態。

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

5.3、為什么需要內部類

其主要原因有以下幾點:

  • 內部類方法可以訪問該類定義所在的作用域的數據,包括私有的數據

  • 內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 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.4、成員內部類

  即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什么區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有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.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.7、靜態內部類(嵌套類)

  如果你不需要內部類對象與其外圍類對象之間有聯系,那你可以將內部類聲明為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.8、匿名內部類

簡單地說:匿名內部類就是沒有名字的內部類。什么情況下需要使用匿名內部類?如果滿足下面的一些條件,使用匿名內部類是比較合適的:
  • 只用到類的一個實例。
  • 類在定義后馬上用到。
  • 類非常小(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.9、內部類的重載問題

  如果你創建了一個內部類,然后繼承其外圍類並重新定義此內部類時,會發生什么呢?也就是說,內部類可以被重載嗎?這看起來似乎是個很有用的點子,但是“重載”內部類就好像它是外圍類的一個方法,其實並不起什么作用:
 
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.10、內部類的繼承問題

  因為內部類的構造器要用到其外圍類對象的引用,所以在你繼承一個內部類的時候,事情變得有點復雜。問題在於,那個“秘密的”外圍類對象的引用必須被初始化,而在被繼承的類中並不存在要聯接的缺省對象。要解決這個問題,需使用專門的語法來明確說清它們之間的關聯:
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變量

六、視頻與示例下載

上課示例下載

B站視頻在線觀看

七、面試題

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

2、抽象類有何特點?

3、接口有何特點?

八、面向對象綜合應用

8.1、面向對象5大設計原則

8.2、設計模式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM