1. 程式人生 > >java基礎學習總結(十二):深入理解java內部類

java基礎學習總結(十二):深入理解java內部類

內部類

內部類也是語法糖,是因為它僅僅是一個編譯時的概念,outer.java裡面定義了一個內部類inner,一旦編譯成功,就會生成兩個完全不同的.class檔案了,分別是outer.class和outer$inner.class。所以內部類的名字完全可以和它的外部類名字相同

內部類分為四種:成員內部類、區域性內部類、匿名內部類、靜態內部類。先逐一瞭解下,再看下使用內部類有什麼好處。

成員內部類

成員內部類是最常見的內部類,就是在外部類的基礎上按照一般定義類的方式定義類罷了,看一個例子:

public class Outer
{
    private int i;
    
    public Outer(int i)
    {
        this.i = i;
    }
    
    public void privateInnerGetI()
    {
        new PrivateInner().printI();
    }
    
    private class PrivateInner
    {
        public void printI()
        {
            System.out.println(i);
        }
    }
    
    public class PublicInner
    {
        private int i = 2;
        
        public void printI()
        {
            System.out.println(i);
        }
    }
}

主函式為:

public static void main(String[] args)
{
    Outer outer = new Outer(0);
    outer.privateInnerGetI();
    Outer.PublicInner publicInner = outer.new PublicInner();
    publicInner.printI();
}

執行結果為:

0
2

通過這個例子總結幾點:

1、成員內部類是依附其外部類而存在的,如果要產生一個成員內部類,比如有一個其外部類的例項

2、成員內部類中沒有定義靜態方法,不是例子不想寫,而是成員內部類中不可以定義靜態方法

3、成員內部類可以宣告為private的,宣告為private的成員內部類對外不可見,外部不能呼叫私有成員內部類的public方法

4、成員內部類可以宣告為public的,宣告為public的成員內部類對外可見,外部也可以呼叫共有成員內部類的public方法

5、成員內部類可以訪問其外部類的私有屬性,如果成員內部類的屬性和其外部類的屬性重名,則以成員內部類的屬性值為準

區域性內部類

區域性內部類是定義在一個方法或者特定作用域裡面的類,看一下區域性內部類的使用:

public static void main(String[] args)
{
    final int i = 0;
    class A
    {
        public void print()
            {
            System.out.println("AAA, i = " + i);
        }
    }
    
    A a = new A();
    a.print();
}

注意一下區域性內部類沒有訪問修飾符,另外區域性內部類要訪問外部的變數或者物件,該變數或物件的引用必須是用final修飾的

匿名內部類

這個應該是用得最多的,因為方便,在多執行緒模組中的程式碼示例中大量使用了匿名內部類,隨便找一段:

public static void main(String[] args) throws InterruptedException
{
    final ThreadDomain44 td = new ThreadDomain44();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            td.testMethod();
        }
    };
    Thread[] threads = new Thread[10];
    for (int i = 0; i < 10; i++)
        threads[i] = new Thread(runnable);
    for (int i = 0; i < 10; i++)
        threads[i].start();
    Thread.sleep(2000);
    System.out.println("有" + td.lock.getQueueLength()  "個執行緒正在等待!");
}

匿名內部類是唯一沒有構造器的類,其使用範圍很有限,一般都用於繼承抽象類或實現介面(注意只能繼承抽象類,不能繼承普通類),匿名內部類Java自動為之起名為XXX$1.classs。另外,和區域性內部類一樣,td必須是用final修飾的。

靜態內部類

用static修飾的內部類就是靜態內部類,看下例子:

public class Outer
{
    private static final int i = 1;
    
    public static class staticInner
    {
        public void notStaticPrint()
        {
            System.out.println("Outer.staticInner.notStaticPrint(), i = " + i);
        }
        
        public static void staticPrint()
        {
            System.out.println("Outer.staticInner.staticPrint()");
        }
    }
}
public static void main(String[] args)
{
    Outer.staticInner os = new Outer.staticInner();
    os.notStaticPrint();
    Outer.staticInner.staticPrint();
}

執行結果為:

Outer.staticInner.notStaticPrint(), i = 1
Outer.staticInner.staticPrint()

通過這個例子總結幾點:

1、靜態內部類中可以有靜態方法,也可以有非靜態方法

2、靜態內部類只能訪問其外部類的靜態成員與靜態方法

3、和普通的類一樣,要訪問靜態內部類的靜態方法,可以直接"."出來不需要一個類例項;要訪問靜態內部類的非靜態方法,必須拿到一個靜態內部類的例項物件

4、注意一下例項化成員內部類和例項化靜態內部類這兩種不同的內部類時寫法上的差別

(1)成員內部類:外部類.內部類 XXX = 外部類.new 內部類();

(2)靜態內部類:外部類.內部類 XXX = new 外部類.內部類();

區域性內部類和匿名內部類只能訪問final區域性變數的原因

      1.這裡所說的“匿名內部類”主要是指在其外部類的成員方法內定義的同時完成例項化的類,若其訪問該成員方法中的區域性變數,區域性變數必須要被final修飾。原因是編譯器實現上的困難:內部類物件的生命週期很有可能會超過區域性變數的生命週期。
  2. 區域性變數的生命週期:當該方法被呼叫時,該方法中的區域性變數在棧中被建立,當方法呼叫結束時,退棧,這些區域性變數全部死亡。而內部類物件生命週期與其它類物件一樣:自建立一個匿名內部類物件,系統為該物件分配記憶體,直到沒有引用變數指向分配給該物件的記憶體,它才有可能會死亡(被JVM垃圾回收)。所以完全可能出現的一種情況是:成員方法已呼叫結束,區域性變數已死亡,但匿名內部類的物件仍然活著。
  3. 如果匿名內部類的物件訪問了同一個方法中的區域性變數,就要求只要匿名內部類物件還活著,那麼棧中的那些它要所訪問的區域性變數就不能“死亡”。
  4. 解決方法:匿名內部類物件可以訪問同一個方法中被定義為final型別的區域性變數。定義為final後,編譯器會把匿名內部類物件要訪問的所有final型別區域性變數,都拷貝一份作為該物件的成員變數。(被final修飾的變數相當於是一個常量,編譯時就可以確定並放入常量池)這樣,即使棧中區域性變數已經死亡,匿名內部類物件照樣可以拿到該區域性變數的值,因為它自己拷貝了一份,且與原區域性變數的值始終保持一致(final型別不可變)。
 最後,Java 8更加智慧:如果區域性變數被匿名內部類訪問,那麼該區域性變數相當於自動使用了final修飾。

使用內部類的好處

最後來總結一下使用內部類的好處:

1、Java允許實現多個介面,但不允許繼承多個類,使用成員內部類可以解決Java不允許繼承多個類的問題。在一個類的內部寫一個成員內部類,可以讓這個成員內部類繼承某個原有的類,這個成員內部類又可以直接訪問其外部類中的所有屬性與方法,是不是相當於多繼承了呢?

2、成員內部類可以直接訪問其外部類的private屬性,而新起一個外部類則必須通過setter/getter訪問類的private屬性

3、有些類明明知道程式中除了某個固定地方都不會再有別的地方用這個類了,為這個只用一次的類定義一個外部類顯然沒必要,所以可以定義一個區域性內部類或者成員內部類,寫一段程式碼用用就好了

4、內部類某種程度上來說有效地對外隱藏了自己,比如我們常用的開發工具Eclipse、MyEclipse,看程式碼一般用的都是Packge這個導航器,Package下只有.java檔案,我們是看不到定義的內部類的.java檔案的

5、使用內部類可以讓類與類之間的邏輯上的聯絡更加緊密