天天看點

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

閱讀文本大概需要 5 分鐘。

1

基礎知識

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

寫個簡單的匿名内部類:

public abstract class Test {
    abstract void onClick();
}
    
Test test = new Test() {
    @Override
    void onClick() {  
    }
};           

複制

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

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

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

複制

2

繼承結構

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

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

Test|OnClickListener testListener = new Test() implements OnClickListener{
    ...
}           

複制

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

var testListener = new Test() implements OnClickListener{
    ...
}           

複制

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

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

3

構造方法

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

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

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

那如果我們将内部類換成Interface呢?Interface跟靜态内部類的效果一樣,就不會引用外部類的執行個體,是以這時候編譯器在定義匿名内部類的構造方法時隻會将匿名内部類的外部類執行個體帶入,而不會将内部類的外部類執行個體帶入:

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

如果我們将匿名内部類放在靜态的方法中,那麼編譯器就不會将任何外部類的執行個體作為構造方法的參數傳入了。

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

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

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

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

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