前言
說到JAVA內(nèi)部類,想必大家首先會想到比較常用的“匿名內(nèi)部類”,但實際上,這只是內(nèi)部類的其中一種使用方式而已。內(nèi)部類的使用方式實際上總共包括:成員內(nèi)部類, 方法局部類,匿名內(nèi)部類,下面,我就給大家來一一介紹:
為什么要使用內(nèi)部類
有的時候你可能有這樣一種需求:對一個類(假設(shè)它為MyClass.java)創(chuàng)建一個和它相關(guān)的類(假設(shè)它是Part.java),但因為Part.java和MyClass之間的聯(lián)系“緊密”且“單一”,導(dǎo)致我們在這種情況下,不希望像下面這樣增加一個額外的兄弟類
├─MyClass
└─Part
復(fù)制
而希望能將Part.java的數(shù)據(jù)隱藏在MyClass.java內(nèi)部,于是這個時候內(nèi)部類就堂而皇之地出現(xiàn)了
那么,這個不請自來的內(nèi)部類到底給我們上述的局面造成了怎樣的改變呢? 讓我們來看看:
增加一個額外的兄弟類Part:
1. 對一些沒有關(guān)聯(lián)的類可見(如果protected則對同一包內(nèi)類可見,如果public則對所有類可見)
2. 不能完全自由的訪問MyClass中的私有數(shù)據(jù)(必須經(jīng)過訪問器方法)
3. 新增了一個java文件
使用內(nèi)部類,將Part類的定義寫入MyClass內(nèi)部
1. 可以減少多余的可見性,例如可把Part在MyClass內(nèi)部定義為私有,這樣對同一包內(nèi)其他類也不可見了
2. 內(nèi)部類(Part)可以自由訪問外圍類的所有數(shù)據(jù)(MyClass),包括私有數(shù)據(jù)
3. 減少了一個java文件,使得類結(jié)構(gòu)更簡潔
成員內(nèi)部類
故名思議,成員內(nèi)部類嘛~ 使用當(dāng)然和成員變量很相似咯
你可以像
private String data
復(fù)制
這樣定義一個“平行的”成員內(nèi)部類:
private class Inner
復(fù)制
具體看下面的例子:
Outter.java:
public class Outter {
// 成員變量data
private String data = "外部數(shù)據(jù)";
//定義一個內(nèi)部類
private class Inner {
public void innerPrint () {
System.out.println(data);
}
}
// 外部類的方法, new一個內(nèi)部類的實例并調(diào)用其innerPrint方法
public void outterPrint () {
Inner i = new Inner();
i.innerPrint();
}}
復(fù)制
Test.java:
public class Test {
public static void main (String [] args) {
Outter o = new Outter();
o.outterPrint();
}
}
復(fù)制
結(jié)果輸出:
外部數(shù)據(jù)
復(fù)制
看來這還是能達(dá)到我們預(yù)期的效果的:由于將Inner內(nèi)部類設(shè)為private,它變得只對我們當(dāng)前的外部類Outter類可見,我們成功地把它"隱藏"在了Outter類內(nèi)部,與此同時,它還自由地訪問到了Outter類的私有成員變量data
兩個this
雖然上面的例子看起來挺簡單的,但實際上內(nèi)部類的作用機(jī)制還是比較復(fù)雜的。
首先要考慮的是“this”的問題,外部類和內(nèi)部類各有一個this,關(guān)鍵在于內(nèi)部類中我們?nèi)绾螌@兩個this作出區(qū)分:
我們假設(shè)上面的例子中的Inner類內(nèi)部有一個方法fn:
private class Inner {
public void fn () {
Outter.this // 指向Outter實例對象的this引用
this // 指向Inner實例對象的this引用
}
}
復(fù)制
在這個方法fn里,Outter.this是指向Outter實例對象的this的引用, 而this是指向Inner實例對象的this的引用
我們訪問類中成員變量有兩種方式: 隱式訪問(不加this)和顯式訪問(加this)
隱式訪問類中成員變量
讓我們對上面的Outter.java做一些改動,增加一行代碼:
public class Outter {
// 成員變量data
private String data = "外部數(shù)據(jù)";
//定義一個內(nèi)部類
private class Inner {
// 增加Inner類對data成員變量的聲明
private String data = "內(nèi)部數(shù)據(jù)" public void innerPrint () {
System.out.println(data);
}
}
// 外部類的方法, new一個內(nèi)部類的實例并調(diào)用其innerPrint方法
public void outterPrint () {
Inner i = new Inner();
i.innerPrint();
}
}
復(fù)制
結(jié)果輸出:
內(nèi)部數(shù)據(jù)
復(fù)制
如此可見,內(nèi)部類內(nèi)聲明的數(shù)據(jù)會覆蓋外部類的同名數(shù)據(jù)。或者說, 在上述例子中,對于data成員變量,它會首先在Inner的this中查找有無這個成員變量,然后沒有,那么就再在Outter.this中查找
顯式訪問類中成員變量
但有的時候我們希望既能訪問外部類的成員變量,同時也能訪問內(nèi)部類的成員變量,這個時候我們就要使用到this了,但是如何區(qū)分內(nèi)部類和外部類的this呢?你可以這樣:
以上述例子為例:
訪問外部類定義的成員變量:Outter.this.data
訪問內(nèi)部類定義的成員變量:this.data
如下圖所示
public class Outter {
// 外部類的成員變量data
private String data = "外部數(shù)據(jù)";
//定義一個內(nèi)部類
private class Inner {
// 內(nèi)部類的成員變量data
private String data = "內(nèi)部數(shù)據(jù)";
public void innerPrint () {
System.out.println(Outter.this.data);
System.out.println(this.data);
}
}
// 外部類的方法, new一個內(nèi)部類的實例并調(diào)用其innerPrint方法
public void outterPrint () {
Inner i = new Inner();
i.innerPrint();
}
}
復(fù)制
局部內(nèi)部類
局部內(nèi)部類是內(nèi)部類的第二種形式,它讓內(nèi)部類的“隱藏”得更深一層——寫在外部類的方法內(nèi)部,而不是處于和外部類方法平行的位置。
讓我們對上面成員內(nèi)部類處理的場景做些思考:我們的Inner內(nèi)部類僅僅只在outterPrint方法中使用了一次:
public void outterPrint () {
Inner i = new Inner();
i.innerPrint();
}
復(fù)制
那么我們能不能把Inner內(nèi)部類直接定義在outterPrint的內(nèi)部呢?這樣的話,它就能更好地隱藏起來,即使是類Outter中除outterPrint外的方法,也不能訪問到它:
現(xiàn)在的Outter的類看起來像這樣:
public class Outter {
public void outterPrint () {// 外部類方法
class LocalInner { // 局部內(nèi)部類
public void innerPrint () { }
}
LocalInner i = new LocalInner(); // 實例化局部內(nèi)部類
i.innerPrint();
}
}
復(fù)制
相比于成員內(nèi)部類,局部內(nèi)部類多了一項能訪問的數(shù)據(jù),那就是局部變量(由外部類方法提供)
成員內(nèi)部類:外部類數(shù)據(jù),內(nèi)部類數(shù)據(jù)
局部內(nèi)部類: 外部類數(shù)據(jù),內(nèi)部類數(shù)據(jù), 局部數(shù)據(jù)
具體示例如下:
Outter.java
public class Outter {
private String data = "外部數(shù)據(jù)"; // 外部類數(shù)據(jù)
public void outterPrint (final String localData) { // 局部數(shù)據(jù)
class LocalInner {
private String data = "內(nèi)部數(shù)據(jù)"; // 內(nèi)部類數(shù)據(jù)
public void innerPrint () {
System.out.println(Outter.this.data); // 打印外部類數(shù)據(jù)
System.out.println(this.data); // 打印內(nèi)部類數(shù)據(jù)
System.out.println(localData); // 打印局部數(shù)據(jù)
}
}
LocalInner i = new LocalInner();
i.innerPrint();
}
}
復(fù)制
Test.java:
public class Test {
public static void main (String [] args) {
Outter o = new Outter();
o.outterPrint("局部數(shù)據(jù)");
}
}
復(fù)制
結(jié)果輸出:
外部數(shù)據(jù)
內(nèi)部數(shù)據(jù)
局部數(shù)據(jù)
復(fù)制
局部類所使用的外部類方法的形參必須用final修飾
這里要注意一點, 局部類所使用的外部類方法的形參必須用final修飾,否則會編譯不通過,也就是說傳入后不許改變
為什么這個方法形參一定要用final修飾?
(僅個人理解,如有不同的意見或者更好的理解歡迎在評論區(qū)討論)
如果不用final修飾會怎樣? 且聽我慢慢道來:
首先要說一下:
1.內(nèi)部類和外部類在編譯之后形式上是一樣的,不會有內(nèi)外之分
2.局部內(nèi)部類對于使用的外部方法的值會用構(gòu)造函數(shù)做一個拷貝(編譯后)
例如對于下面outterPrint方法中的LocalInner
public void outterPrint (final String data) {
class LocalInner {
public void innerPrint () {
// 使用 data
}
}
}
復(fù)制
編譯之后大概長這樣:
public class Outter$LocalInner{
public LocalInner(String data){
this.LocalInner$data = data; // 對于使用的data做了一次拷貝
}
public void innerPrint (){ /* 使用 data */ }
}
復(fù)制
這里要注意的是:
1. 編譯后,LocalInner并非直接使用data,而是用構(gòu)造器拷貝一份后再使用
2. java是值傳遞的,所以包裹 LocalInner的外部方法outterPrint也會對傳入的data參數(shù)做一次拷貝(基本類型數(shù)據(jù)拷貝副本,對象等則拷貝引用)
OK,現(xiàn)在的情況是:
方法內(nèi)的局部類對data拷貝了兩次:外部方法outterPrint值傳遞時的拷貝,和LocalInner構(gòu)造函數(shù)的拷貝
方法內(nèi)除了局部類外的作用域只拷貝了data一次: 外部方法outterPrint值傳遞時的拷貝
拷貝兩次和拷貝一次,導(dǎo)致在outterPrint方法內(nèi)部, 局部類內(nèi)部的data和局部類外部的data是不同步的! 也即你在局部類內(nèi)部改了data不影響局部類外部的data,在局部類外部改了data也不影響局部類內(nèi)部的data(注意一個前提,值是基本類型的,如果是對象的話因為拷貝的是引用仍然可以“同步”)
圖示一:
圖示二:
于是java說: 哎呀媽呀, 這都data都不同步了, 要是讓你修改這還了得!!! 于是就強行要求我們加上final
【注意】所謂的不同步主要是針對基本類型來說的,如果是對象之類的話因為拷貝的是引用所以仍然可以“同步”
如何突破必須用final的限制
我們上面說到,局部內(nèi)部類所使用的方法形參必須用final修飾的限制。
例如
public void outterPrint (String data) {// 沒加上final
class LocalInner {
public void changeData () {
data = "我想修改data的值"; // 在這一行編譯報錯
}
}
}
復(fù)制
提示:
Cannot refer to a non-final variable data inside an inner class defined in a different method
復(fù)制
那么,如果我們有對該形參必須能修改的硬性需求怎么辦?
你可以通過一種有趣的方式繞開它:使用一個單元素數(shù)組。因為用final修飾的基本類型的變量不允許修改值,但是卻允許修改final修飾的單元素數(shù)組里的數(shù)組元素, 因為存放數(shù)組的變量的值只是一個引用,我們修改數(shù)組元素的時候是不會修改引用指向的地址的,在這點上final并不會妨礙我們:
Outter.java
public class Outter {
public void outterPrint (final String [] data) {
class LocalInner {
public void innerPrint () {
data[0] = "堂而皇之地修改它!!"; // 修改數(shù)據(jù)
System.out.print(data[0]); // 輸出修改后的數(shù)據(jù)
}
}
LocalInner i = new LocalInner();
i.innerPrint();
}
}
復(fù)制
Test.java:
public class Test {
public static void main (String [] args) {
Outter o = new Outter();
String [] data = new String [1];
data[0] = "我是數(shù)據(jù)";
o.outterPrint(data); // 修改數(shù)據(jù)并且輸出
}
}
復(fù)制
結(jié)果輸出:
堂而皇之地修改它!!
復(fù)制
【注意】局部類不能用public或private訪問符進(jìn)行聲明!!
匿名內(nèi)部類
倘若我們再把局部內(nèi)部類再深化一下, 那就是匿名內(nèi)部類
匿名內(nèi)部類的使用方式
new [超類/接口] { /* 類體 */ }
復(fù)制
讓我們看看下面這個例子:
Other.java:
public class Other { }
復(fù)制
Outter.java:
public class Outter {
public void outterPrint (String data) {
Other o = new Other() { }; // 匿名內(nèi)部類
}
}
復(fù)制
何謂之匿名?
“誒,不是說好的匿名嗎? 那么為什么還有個Other的類名呢?”
Other o = new Other() { /* 匿名內(nèi)部類的類體 */ };
復(fù)制
實際上,這里的Other并不是我們的匿名內(nèi)部類,而是我們匿名內(nèi)部類的超類,上面一行代碼其實相當(dāng)于(用成員內(nèi)部類來表示的話)
// annoymous翻譯為匿名
public class Outter {
private class annoymous extends Other{ }
public void outterPrint () {
Other a = new annoymous();
}
}
復(fù)制
同時要注意,我們在使用匿名內(nèi)部類的方式,是在定義一個內(nèi)部類的同時實例化該內(nèi)部類:
new Other() { /* 匿名內(nèi)部類的類體 */ }; // new操作和定義類的代碼是緊緊結(jié)合在一起的
復(fù)制
匿名函數(shù)的作用
用匿名函數(shù)的作用在于在一些特定的場景下寫起來很簡單,例如事件監(jiān)聽器:
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) { }
};
復(fù)制
避免了再創(chuàng)建另外一個類文件
講的有點亂, 對匿名內(nèi)部類做個總結(jié):
1. 省略被定義的類的類名
2. 必須結(jié)合超類或者接口使用,即 new [超類/接口] { /* 類體 */ }
3. 在定義該匿名類的同時實例化該匿名類
4. 在一些場景下能簡化代碼
【注意】匿名類不能有構(gòu)造器, 因為構(gòu)造器和類同名,而匿名類沒有類名,所以匿名類不能有構(gòu)造器
文章總結(jié)
我們使用內(nèi)部類的原因主要有三點:
1.實現(xiàn)數(shù)據(jù)隱藏, 避免多余的可見性
2.自由訪問外部類的變量
3. 在使用監(jiān)聽器等場景的時候使用匿名內(nèi)部類,避免增加的大量代碼
關(guān)于成員內(nèi)部類, 方法局部類,匿名內(nèi)部類的關(guān)系
從成員內(nèi)部類,方法局部類到匿名內(nèi)部類是一個不斷深入的關(guān)系, 成員內(nèi)部類進(jìn)一步隱藏可見性就成為了方法局部類, 方法局部類省去類名,并將類的定義和實例化操作合并到一起,就是匿名內(nèi)部類。因此,匿名內(nèi)部類沿襲了成員內(nèi)部類和方法局部類的基本特特性
內(nèi)部類的一些特殊的要求
1.局部類不能用public或private訪問符進(jìn)行聲明
2.局部類所使用的外部類方法的形參必須用final修飾
3. 匿名內(nèi)部類不能有構(gòu)造器
參考資料:
《java核心技術(shù) 卷1》—— Cay S. Horstmann, Gary Cornell






