一文搞定Java內部類

一、前言

內部類多而繁雜,互訪情況下,不管是內訪外,還是外訪內,靜態(類,方法,成員變量)與非靜態(類,方法,成員變量)之間的訪問也錯綜複雜。還有各種令人頭疼的編譯問題,匿名內部類使用的形參爲何必須爲final修飾等都是面試喜歡問的點。如果您對內部類還有疑惑,讀完本文,說不定能讓面試官膜拜您。
本文將從內部類的種類,命名規則,匿名內部類編譯,內外互訪等角度來闡述。

二、內部類的種類

內部類分爲4種:

  • 成員內部類
  • 局部內部類
  • 靜態內部類
  • 匿名內部類

成員內部類
與成員變量類似,寫在類裏。如果內部類持有外部類的引用,可能導致內部類沒有執行完,外部類也無法釋放,有內存泄漏風險。想要避免這種現象,需要在外部類對象生命週期結束時手動將內部類對象生命週期結束。除非延遲迴收會成爲系統的運維瓶頸,一般不需要特別關注,GC機制通常足以應付。

public class Outter {
    String s;
    class Inner{
    }
}

局部內部類
局部內部類是定義在一個方法或者一個作用域裏的類。Inner2和Inner都是Outter的內部類。
 

public class Outter {
    public Inner getInner2(){
        class Inner2 extends Inner{
            String s = "Inner2";
        }
        return new Inner2();
    }
}
class Inner{
    String s;
}

 靜態內部類
好吧,就是成員內部類前面加了static。聲明爲static的類不會持有外部類的引用,可以通過軟引用的方式保存外部類的了引用,只有靜態內部類不可能造成內存泄漏。

public class Outter {
    String s;
    static class Inner{
    }
}

匿名內部類
匿名內部類應該是我們用得最多的,在編寫事件監聽的代碼時使用匿名內部類不但方便,而且使代碼更加容易維護。

public class Outter {
    void test() {
        new Thread(new Runnable(){
            String S;
            @Override
            public void run(){}
        }).start();
    }
}

三、內部類編譯後的命名規則

非匿名內部類類名規則爲 OutClass$InnerClass(外部類類名與內部類類名用$連接) 匿名內部類類名則爲OutClass$數字(OutClass$1,OutClass$2,OutClass$3)

public class A {
	C c = new C(){
		String s="i am c";
		@Override
		public void demo() {
		}
	};
	C c2 = new C(){
		String s="i am c2";
		@Override
		public void demo() {
		}
	};
}
interface C{
	public void demo();
}

編譯之後:顯然,符合上述命名規則

四、關於匿名內部類的編譯問題

先看如下代碼:

public class Test {
   public void test(final int b) { 
    final int a = 10;
 
    new Thread(){  
    public void run() {
         System.out.println(a);
    }
    }.start();
  }
}
  • 當方法執行完成後,局部變量a聲明週期結束,此時Thread對象的聲明週期可能沒有結束,在run方法中繼續訪問a變量變成不可能,怎麼解決?
    java採用的是拷貝的套路。如果局部變量的值在編譯期間就可以確定,則直接在匿名內部裏面創建一個拷貝。如果局部變量的值無法在編譯期間確定,則通過構造方法傳參的方式來對拷貝進行初始化賦值(可以反編譯查看,發現Test$1的構造方法有兩參數,一個是指向外部類對象的引用,一個是int型變量。很顯然,int型變量就是Test成員變量a的拷貝。)
  • 在run方法中訪問的變量a和test方法中的變量a不是同一個變量,當在run方法中改變變量a的值的話,會出現什麼情況?
    會造成數據不一致性。爲了解決這個問題,java編譯器就限定必須將變量a限制爲final變量,不允許對變量a進行更改(對於引用類型的變量,是不允許指向新的對象),數據不一致的問題就解決了。
    這就是必須使用final修飾的原因。

五、內部類,外部類互訪

  • 內部類訪問外部類

裏面的可以自由訪問外面的,訪問非靜態成員變量時必須先創建對象。
以下從內部類的狀態開始闡述。

1.非靜態內部類的非靜態方法
直接訪問

public class Outter {
    int i = 5;
    static String word = "Hello";

    class Inner {
        void Test() {
            System.out.println(i);
            System.out.println(word);
        }
    }
}

2.非靜態內部類的靜態方法
非靜態內部類不能有靜態方法,編譯報錯。

3.靜態內部類的非靜態方法
訪問非靜態成員變量必須先new外部類。

public class Outter {
    int i = 5;
    static String word = "Hello";

    static class Inner {
        void Test() {
            System.out.println(new Outter().i);
            System.out.println(word);
        }
    }
}

4.靜態內部類的靜態方法
和第三種情況類似,靜態方法訪問外部類非靜態成員變量必須先new外部類。

public class Outter {
    int i = 5;
    static String word = "Hello";

    static class Inner {
        static void Test() {
            System.out.println(new Outter().i);
            System.out.println(word);
        }
    }
}
  • 外部類訪問內部類

    可以將內部類理解爲外部類的一個普通成員,所以外面的訪問裏面的需先new一個對象。
    Outter.Inner inner = new Outter.Inner();

    1.非靜態方法訪問非靜態內部類成員
    先new內部類

public class Outter {
    void Test() {
        System.out.println(new Inner().i);
    }
    class Inner {
        int i = 5;
//      static String word = "hello";  編譯報錯!
    }
}

2.非靜態方法訪問靜態內部類的成員
先new內部類,再訪問非靜態的成員變量
靜態成員變量可外部類.內部類.變量名直接訪問

public class Outter {
    void Test() {
        Outter.Inner inner = new Outter.Inner();
        System.out.println(inner.i);
        System.out.println(inner.string);
        System.out.println(Outter.Inner.string);
    }
    static class Inner {
        int i = 5;
        static String string = "Hello";
    }
}

3.靜態方法訪問非靜態內部類的成員

public class Outter {
    static void Test() {
        System.out.println(new Outter().new Inner().i);
    }
    class Inner {
        int i = 5;
//	static String word = "Hello";  編譯報錯!
    }
}

4.靜態方法訪問靜態內部類的成員
訪問非靜態成員需先new 內部類
new Inner().i
與第2種情況相似

public class Outter {
    static void Test() {
        Outter.Inner inner = new Outter.Inner();
        System.out.println(new Inner().i);
        System.out.println(inner.i);
        System.out.println(Outter.Inner.word);
    }
    static class Inner {
        int i = 5;
        static String word = "Hello";
    }
}

5.匿名內部類
匿名內部類訪問外部成員變量時,成員變量前應加final關鍵字。

至此,內部類完結,感覺身體被掏空~
博主常年在線,如有疑問或錯誤,歡迎評論指出
如果喜歡我的文章,歡迎關注知乎專欄Java修仙道路~
參考博文【Java】內部類與外部類的互訪使用小結匿名內部類 類名規則 定位$1

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章