12.27 你說你是高工,匿名內部類有我玩得6嗎?

1

基礎知識

匿名內部類大家肯定都很熟悉,如果你是做Android開發的一定再熟悉不過了,因為你學Android的時候寫的第二行代碼一定是setOnClickListener,第一行代碼一般是findViewById。

寫個簡單的匿名內部類:

你說你是高工,匿名內部類有我玩得6嗎?

作為一個初級工程師,一般能使用就ok了。

匿名內部類,顧名思義就是不知道名字的內部類。它真的就沒有名字嗎?有想過這個問題嗎?如果你想過,那證明你是一個不甘於做初級工程師,想往上拔高的人。事實上匿名內部類在被編譯成字節碼的時候會被定義一個類名,只是類名呢不是那麼的被人類所容易閱讀,假設上面那個匿名內部類的外部類為OuterClass,編譯器編譯後就會將上面的匿名內部類定義為:"包名.OuterClass$1",其中裡面的'$1'指的是OutterClass類裡面的第一個匿名內部類。如果你懷疑它的正確性,可以驗證下:

<code>Class testClass = Class.forName("包名.OuterClass$1");
System.out.println(testClass);/<code>

2

繼承結構

以上面舉的例子來說,匿名內部類的父類是Test,或者我們常用的setOnclickListener(new OnclickListener{})來說,就是實現OnClickListener接口的匿名內部類。


那麼我們能不能同時繼承一個類和實現一個接口呢?像這樣:

<code>Test|OnClickListener testListener = new Test() implements OnClickListener{
...
}/<code>

這種可以嗎?在有些語言是支持的,但是呢Java是不支持的。我們知道Java10支持類型推導了,那上面的例子可不可以寫成這樣呢?

<code>var testListener = new Test() implements OnClickListener{
...
}/<code>

可不可以呢?其實也是不可以的,那有同學就講了,你在這瞎折騰了半天,都是不可以那還講幹啥?聰明的同學可能能從上面也能吸取到一些知識。比如,你可以去查一下哪些語言支持第一種方式,這裡只是給你拋磚引玉用的。從第二種方式中我講到了Java 10支持了類型推導,那你也可以再去查下Java 10到底新增了哪些新特性是不是?那到底能不能實現呢?當然是可以的,你可以使用Java的local class。感興趣的讀者自行查閱一下哦。

如果你是中級工程師掌握到這裡就蠻不錯的了。

3

構造方法

匿名內部的構造方法是誰定義的?很顯然開發者並沒有機會去定義,是由編譯器給我們編譯的,在非靜態區裡面,我們寫匿名內部類時會持有外部類的引用,那這個引用編譯器會幫我們作為構造方法的參數傳進去。舉個例子:


你說你是高工,匿名內部類有我玩得6嗎?


由於我們的內部類是非靜態的所以是需要持有內部類InnerClass的外部類的實例(OuterClass的實例),而我們的匿名內部類也是在非靜態的方法區中,那麼就會持有匿名內部類$1的外部類的實例(Client的實例),所以編譯器給我們的匿名內部類定義的構造方法中帶上了兩個實例的參數。


那如果我們將內部類換成Interface呢?Interface跟靜態內部類的效果一樣,就不會引用外部類的實例,所以這時候編譯器在定義匿名內部類的構造方法時只會將匿名內部類的外部類實例帶入,而不會將內部類的外部類實例帶入:


你說你是高工,匿名內部類有我玩得6嗎?


如果我們將匿名內部類放在靜態的方法中,那麼編譯器就不會將任何外部類的實例作為構造方法的參數傳入了。


還有一個我們在匿名內部類訪問局部變量時,需要將局部變量聲明為final的。原因是什麼呢?因為如果你在匿名內部類訪問局部變量的時候,編譯器一樣會在匿名內部類的構造方法中將其作為參數傳進去,不過呢,傳進去的時候是當時的一個拷貝,如果不是final的,那麼你的代碼在後面對變量進行更改的話,那麼在匿名內部類中使用的還是舊的值,這樣處理顯然會有問題,所以Java要求必須使用final來聲明。如圖:


你說你是高工,匿名內部類有我玩得6嗎?


所以,綜上我們知道匿名內部類的構造方法的定義是:

  • 由編譯器定義
  • 構造方法的參數
    • 外部類的對象(定義在非靜態方法區)
    • 父類的外部類的對象(父類是非靜態的)
    • 父類的構造方法參數
    • 外部捕獲的變量(方法體引用的局部final變量)


到這裡為止,如果你都知道的話,我覺你已經有了高工的思維高度了。


分享到:


相關文章: