首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 內部類與匿名類深度解析:從封裝到多實現的設計藝術

     編輯:Java 相關知識

初識內部類:不只是類中定義類

提到 Java 內部類,許多開發者可能感到陌生。其實類似概念在 C++ 中以「嵌套類」形式存在。乍看之下,內部類似乎多餘,尤其對初學者而言用處不明顯。但深入研究後會發現,Java 設計者在內部類上確實用心良苦。掌握內部類是進階 Java 程式設計的關鍵一環,能讓程式結構更優雅。

第一次見面:封裝性的體現

public interface Contents { int value(); }
public interface Destination { String readLabel(); }

public class Goods {
    private class Content implements Contents {
        private int i = 11;
        public int value() { return i; }
    }
    
    protected class GDestination implements Destination {
        private String label;
        private GDestination(String whereTo) { label = whereTo; }
        public String readLabel() { return label; }
    }
    
    public Destination dest(String s) { return new GDestination(s); }
    public Contents cont() { return new Content(); }
}

class TestGoods {
    public static void main(String[] args) {
        Goods p = new Goods();
        Contents c = p.cont();
        Destination d = p.dest("Beijing");
    }
}

在這個例子中,Content 和 GDestination 定義在 Goods 內部,分別用 private 和 protected 控制存取層級。它們實現了介面 Contents 和 Destination。在 main 方法中,直接透過介面型別操作,甚至看不見內部類的名字!這就是內部類的第一大優勢——隱藏實作細節,強化封裝性。

同時,我們也發現了在外部類作用域外取得內部類物件的第一種方法:利用外部類方法建立並回傳。例子中的 cont() 和 dest() 就是這樣做的。還有其他方法嗎?當然有,語法如下:

OuterClass outerObject = new OuterClass(Constructor Parameters);
OuterClass.InnerClass innerObject = outerObject.new InnerClass(Constructor Parameters);

注意:建立非靜態內部類物件時,必須先建立對應的外部類物件。原因引出下一個主題——

非靜態內部類持有外部類引用

修改前例,給 Goods 增加 private int valueRate = 2;,內部類 Content 的 value() 計算時乘上此係數。我們發現 value() 可以存取 valueRate,這是內部類的第二大優勢——內部類物件能存取建立它的外部類物件的所有成員,包含私有變數。這極大豐富了設計思路。

為實現此功能,Java 編譯器在建立內部類物件時,隱式傳入並保存外部類物件的引用。這也是為什麼在外部類作用域外建立內部類物件,必須先建立外部類物件的原因。

若內部類成員變數與外部類同名(遮蔽),可用 OuterClass.this 表示外部類引用,輕鬆解決遮蔽問題。

靜態內部類:沒有外部引用的嵌套類

內部類也可以是靜態的。與非靜態內部類最大區別在於:靜態內部類沒有指向外部類的引用。這點上它類似 C++ 的嵌套類。Java 內部類與 C++ 嵌套類最大不同,就在於是否持有外部引用。

此外,非靜態內部類中不能有靜態資料、靜態方法或靜態內部類;但靜態內部類卻可以擁有這一切。這是兩者的第二個區別。

局部內部類:定義在方法或程式碼區塊中

Java 內部類也可以是局部的,定義在方法甚至程式碼區塊內。例如在方法 dest 中定義內部類,最後由方法回傳該內部類物件。若僅需建立一個物件並回傳,這樣做非常適合。當然,定義在方法中的內部類用途遠不止於此。

public class Goods2 {
    private void internalTracking(boolean b) {
        if (b) {
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) { id = s; }
                String getSlip() { return id; }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
        }
    }
    // ...
}

你不能在 if 區塊外建立此內部類物件,因為超出作用域。但在編譯時,TrackingSlip 和其他類一樣被編譯,只是有自己的作用域,超出則無效。

匿名內部類:簡潔的即時實作

匿名內部類語法看似古怪,但如同匿名陣列,當你只需建立一個類的物件且不需要其名字時,使用它能讓程式碼簡潔清楚。語法規則:

new InterfaceName() { ... };
// 或
new SuperClassName() { ... };

例子:

public class Goods3 {
    public Contents cont() {
        return new Contents() {
            private int i = 11;
            public int value() { return i; }
        };
    }
}

方法 cont() 直接用匿名內部類回傳實現 Contents 介面的物件,確實簡潔。

在 Java 事件處理中,匿名內部類被大量使用。例如關閉視窗:

frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
});

注意事項:匿名內部類沒有名字,所以沒有建構子(若繼承的父類只有帶參數建構子,建立時必須帶上參數並用 super 調用)。初始化成員變數有三種方法:

  1. 若在方法中,可利用方法傳入參數(參數必須宣告為 final)
  2. 改成有名字的局部內部類,即可擁有建構子
  3. 在匿名內部類中使用初始化區塊

為什麼需要內部類?

首先,若你想實現一個介面,但介面中的某方法與你構想的類中某方法同名同參數,此時可建一個內部類實現該介面。因為內部類能存取外部類所有內容,所以能完成所有直接實現介面的功能。

但你可能質疑:更改方法名不就行了?確實,以此作為設計內部類的理由,說服力不足。

真正原因是: Java 內部類配合介面,能解決 C++ 程式設計師常抱怨的 Java 問題——沒有多重繼承。C++ 多重繼承設計複雜,而 Java 透過內部類加介面,能很好實現多重繼承的效果。

本文旨在介紹內部類概念與用法,後續將舉更多具體例子,並介紹如何用內部類建構 Application Framework。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved