概述
最近看ThreadLocal的實(shí)現(xiàn)原理的過程中了解到ThreadLocalMap是定義在ThreadLocal中的靜態(tài)內(nèi)部類敬惦,默默的問了問自己為什么要定義為靜態(tài)內(nèi)部類捻脖?定義為普通內(nèi)部類有沒有關(guān)系?發(fā)現(xiàn)自己對java的內(nèi)部類知識不是很了解输虱,于是惡補(bǔ)了下內(nèi)部類的用法和區(qū)別音念,希望這邊文章可以對跟我有相似問題的同學(xué)有所幫助掂碱。
內(nèi)部類介紹
? 顧名思義,內(nèi)部類就是在一個類的內(nèi)部定義另一個類春感。就像我們在類中聲明成員變量一樣砌创,變量可以使用訪問控制符,static鲫懒,可以聲明為成員變量或者函數(shù)內(nèi)部的局部變量嫩实。內(nèi)部類也可以有上述的用法,java中的內(nèi)部類可以分為普通內(nèi)部類(成員內(nèi)部類)窥岩,靜態(tài)內(nèi)部類甲献,局部內(nèi)部類和匿名內(nèi)部類。
成員內(nèi)部類
?成員內(nèi)部類就是像普通的成員函數(shù)一樣聲明的內(nèi)部類颂翼,下面我們先給出一個簡單的示例晃洒,InnerClass是OutClass的成員內(nèi)部類,可以訪問OutClass的成員變量朦乏。
/**
* Created by yuanqiongqiong on 2019/6/14.
*/
public class OutClass {
private int i;
public OutClass(int i) {
this.i = i;
}
public class InnerClass {
private int j;
public InnerClass(int j) {
this.j = j;
}
public void print() {
System.out.println("OutClass.i = " + i);
System.out.println("InnerClass.j = " + j);
}
}
}
//測試
public class OutClassTest {
public static void main(String [] args) {
OutClass outClass = new OutClass(1);
OutClass.InnerClass innerClass = outClass.new InnerClass(2);
innerClass.print();
}
}
我認(rèn)為主要有以下幾點(diǎn)用法:
(1)可以使用public锥累,private,protected或者默認(rèn)的訪問權(quán)限控制符聲明集歇,表示該內(nèi)部類的訪問權(quán)限桶略;
(2)可以訪問外部類的成員變量和方法;
(3)成員內(nèi)部類不能聲明靜態(tài)成員;
? 對于(2)际歼,我們可以認(rèn)為在內(nèi)部類持有外部類的引用惶翻,這樣內(nèi)部類就可以通過這個引用訪問外部類的所有的成員的方法和變量。同時鹅心,這也解釋為什么要通過外部類對象來創(chuàng)建內(nèi)部類吕粗。為了驗(yàn)證這個問題,我們對OutClass.java編譯得到OutClass.class和OutClass$InnerClass.class旭愧,然后我們通過idea直接打開內(nèi)部類class文件(我們之所以用idea自帶的反編譯器颅筋,是因?yàn)橄啾扔趈avap,idea反編譯的文件可讀性更高)输枯,具體如下:
java在編譯內(nèi)部類時會在其構(gòu)造函數(shù)中默認(rèn)添加外部類引用的參數(shù)议泵,從而持有外部類的引用。
對于(3)桃熄,成員內(nèi)部類就相當(dāng)于外部類的一個普通的成員變量先口,當(dāng)jvm加載外部類的時候并不會加載非靜態(tài)變量,因此也就不會加載內(nèi)部類瞳收。如果內(nèi)部類可以聲明靜態(tài)變量碉京,那么就會出現(xiàn)類還沒有加載卻要初始化靜態(tài)變量的現(xiàn)象,因此java不會允許這種情況通過編譯螟深。
靜態(tài)內(nèi)部類
? 靜態(tài)內(nèi)部類就是有static修飾的內(nèi)部類谐宙,類似靜態(tài)變量或者靜態(tài)函數(shù)。相比于成員內(nèi)部類對外部類的依賴界弧,靜態(tài)內(nèi)部類基本不依賴外部類凡蜻。通過其官方名稱"static nested classes"(靜態(tài)嵌套類),更能說明其與外部類沒有關(guān)系夹纫,只是自己的類聲明嵌套在外部類的java文件中咽瓷。靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)成員和方法,這點(diǎn)非常好理解舰讹,因?yàn)殪o態(tài)內(nèi)部類和外部類沒關(guān)系茅姜。靜態(tài)內(nèi)部類和靜態(tài)變量或方法一樣可以使用public,private月匣,protected或者默認(rèn)的訪問權(quán)限控制符聲明钻洒,這點(diǎn)也很好理解。下面我們給出個靜態(tài)內(nèi)部類的示例:
public class OutClass {
private static int k=3;
private int i;
public OutClass(int i) {
this.i = i;
}
static class InnerClass {
private int j;
public InnerClass(int j) {
this.j = j;
}
public void print() {
System.out.println("OutClass.k = " + k);
System.out.println("InnerClass.j = " + j);
}
}
}
//測試類
public class OutClassTest {
public static void main(String [] args) {
OutClass.InnerClass innerClass = new OutClass.InnerClass(2);
innerClass.print();
}
}
示例很簡單锄开,從測試類可以看出內(nèi)部類的聲明不再依賴外部類素标,可以完全獨(dú)立聲明對象,其實(shí)靜態(tài)內(nèi)部類不再持有外部類的引用萍悴,下面我們看下反編譯的class文件:
匿名內(nèi)部類
?對于匿名內(nèi)部類头遭,大家肯定在日常開發(fā)中都有使用但卻不知道叫法寓免,比如函數(shù)回調(diào),線程聲明等都會使用匿名內(nèi)部類计维。如下面這段常用的代碼:
public class ThreadTest {
public static void main(String []args) {
final User user= new User("chenxiaosuo");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("user = " + user);
}
}, "threadtest");
thread.start();
}
static class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
Runable是一個接口袜香,我們通過new來實(shí)現(xiàn)了一個匿名內(nèi)部類,這個類沒有類名(其實(shí)真實(shí)編譯后會以"外部類$數(shù)字"的形式作為類名)鲫惶。
?不知道大家有沒有這個困惑蜈首,在匿名內(nèi)部類中使用的變量必須聲明為final類型,表示變量不可變欠母,否則編譯報錯欢策,如上的變量user。我們先分析下上述代碼赏淌,變量user是個局部變量踩寇,當(dāng)函數(shù)testAnonymousInnerClass結(jié)束后會回收,而此時threadtest可能還在繼續(xù)執(zhí)行猜敢,如果訪問變量user應(yīng)該會報錯姑荷,而實(shí)際卻不會出現(xiàn)這種情況盒延。我們看下編譯后的類文件發(fā)生了什么缩擂,匿名內(nèi)部會復(fù)制一份訪問的變量到內(nèi)部,這樣就解決上述的聲明周期問題添寺。因?yàn)檫@種拷貝胯盯,如果java允許這個變量發(fā)生改變,那么肯定就造成了數(shù)據(jù)不一致的問題计露,這也就解釋為什么匿名內(nèi)部類的訪問的變量必須為final類型了博脑。
局部內(nèi)部類
可以把局部內(nèi)部類與我們函數(shù)里的局部變量作為類比,局部內(nèi)部類就是聲明在一個函數(shù)或者某個作用域中的內(nèi)部類票罐〔嫒ぃ可以看出局部內(nèi)部類只作用于其所聲明的函數(shù)或者局部作用域中,因此在局部內(nèi)部類中不能使用public该押,private和protected疗杉。由于局部內(nèi)部類比較簡單,這里不再舉例說明蚕礼。
內(nèi)部類作用
以上介紹了內(nèi)部的使用及相關(guān)分析烟具,最后我們再思考一個問題,為什么要使用內(nèi)部類奠蹬?在java編程思想中講到朝聋,使用內(nèi)部類可以使得java類繼承多個類,使得java多繼承的方案更加的完善囤躁。是不是很抽象冀痕?下面我這邊總結(jié)兩個我感覺使用內(nèi)部類的理由:
(1)使用成員內(nèi)部類可以使得內(nèi)部類訪問外部類的成員變量荔睹;
(2)使用匿名內(nèi)部類使得我們代碼變得更簡潔,不需要定義一些只是用一次的類言蛇;
原文
袁瓊瓊的技術(shù)博客应媚,歡迎指針
http://yuanqiongqiong.cn/2019/06/18/%E8%B0%88%E8%B0%88java%E4%B8%AD%E7%9A%84%E5%86%85%E9%83%A8%E7%B1%BB/