前言java
說到java內部類,想必你們首先會想到比較經常使用的「匿名內部類」,但實際上,這只是內部類的其中一種使用方式而已。內部類的使用方式實際上總共包括:成員內部類, 方法局部類,匿名內部類,下面,我就給你們來一一介紹:mysql
爲何要使用內部類面試
有的時候你可能有這樣一種需求:對一個類(假設它爲MyClass.java)建立一個和它相關的類(假設它是Part.java),但由於Part.java和MyClass之間的聯繫「緊密」且「單一」,致使咱們在這種狀況下,不但願像下面這樣增長一個額外的兄弟類算法
├─MyClass └─Part
而但願能將Part.java的數據隱藏在MyClass.java內部,因而這個時候內部類就冠冕堂皇地出現了spring
那麼,這個不請自來的內部類到底給咱們上述的局面形成了怎樣的改變呢? 讓咱們來看看:sql
增長一個額外的兄弟類Part:數組
使用內部類,將Part類的定義寫入MyClass內部jvm
成員內部類ide
故名思議,成員內部類嘛~ 使用固然和成員變量很類似咯函數
你能夠像
private String data
這樣定義一個「平行的」成員內部類:
private class Inner
具體看下面的例子:
Outter.java:
public class Outter { // 成員變量data private String data = "外部數據"; //定義一個內部類 private class Inner { public void innerPrint () { System.out.println(data); } } // 外部類的方法, new一個內部類的實例並調用其innerPrint方法 public void outterPrint () { Inner i = new Inner(); i.innerPrint(); } }
Test.java:
public class Test { public static void main (String [] args) { Outter o = new Outter(); o.outterPrint(); } }
結果輸出:
外部數據
看來這仍是能達到咱們預期的效果的:因爲將Inner內部類設爲private,它變得只對咱們當前的外部類Outter類可見,咱們成功地把它"隱藏"在了Outter類內部,與此同時,它還自由地訪問到了Outter類的私有成員變量data
兩個this
雖然上面的例子看起來挺簡單的,但實際上內部類的做用機制仍是比較複雜的。
首先要考慮的是「this」的問題,外部類和內部類各有一個this,關鍵在於內部類中咱們如何對這兩個this做出區分:
咱們假設上面的例子中的Inner類內部有一個方法fn:
private class Inner { public void fn () { Outter.this // 指向Outter實例對象的this引用 this // 指向Inner實例對象的this引用 } }
在這個方法fn裏,Outter.this是指向Outter實例對象的this的引用, 而this是指向Inner實例對象的this的引用
咱們訪問類中成員變量有兩種方式: 隱式訪問(不加this)和顯式訪問(加this)
隱式訪問類中成員變量
讓咱們對上面的Outter.java作一些改動,增長一行代碼:
public class Outter { // 成員變量data private String data = "外部數據"; //定義一個內部類 private class Inner { // 增長Inner類對data成員變量的聲明 private String data = "內部數據" public void innerPrint () { System.out.println(data); } } // 外部類的方法, new一個內部類的實例並調用其innerPrint方法 public void outterPrint () { Inner i = new Inner(); i.innerPrint(); } }
結果輸出:
內部數據
如此可見,內部類內聲明的數據會覆蓋外部類的同名數據。或者說, 在上述例子中,對於data成員變量,它會首先在Inner的this中查找有無這個成員變量,而後沒有,那麼就再在Outter.this中查找
顯式訪問類中成員變量
但有的時候咱們但願既能訪問外部類的成員變量,同時也能訪問內部類的成員變量,這個時候咱們就要使用到this了,可是如何區份內部類和外部類的this呢?你能夠這樣:
以上述例子爲例:
訪問外部類定義的成員變量:Outter.this.data
訪問內部類定義的成員變量:this.data
以下圖所示
public class Outter { // 外部類的成員變量data private String data = "外部數據"; //定義一個內部類 private class Inner { // 內部類的成員變量data private String data = "內部數據"; public void innerPrint () { System.out.println(Outter.this.data); System.out.println(this.data); } } // 外部類的方法, new一個內部類的實例並調用其innerPrint方法 public void outterPrint () { Inner i = new Inner(); i.innerPrint(); } }
局部內部類
局部內部類是內部類的第二種形式,它讓內部類的「隱藏」得更深一層——寫在外部類的方法內部,而不是處於和外部類方法平行的位置。
讓咱們對上面成員內部類處理的場景作些思考:咱們的Inner內部類僅僅只在outterPrint方法中使用了一次:
public void outterPrint () { Inner i = new Inner(); i.innerPrint(); }
那麼咱們能不能把Inner內部類直接定義在outterPrint的內部呢?這樣的話,它就能更好地隱藏起來,即便是類Outter中除outterPrint外的方法,也不能訪問到它:
如今的Outter的類看起來像這樣:
public class Outter { public void outterPrint () {// 外部類方法 class LocalInner { // 局部內部類 public void innerPrint () { } } LocalInner i = new LocalInner(); // 實例化局部內部類 i.innerPrint(); } }
相比於成員內部類,局部內部類多了一項能訪問的數據,那就是局部變量(由外部類方法提供)
成員內部類:外部類數據,內部類數據
局部內部類: 外部類數據,內部類數據, 局部數據
具體示例以下:
Outter.java
public class Outter { private String data = "外部數據"; // 外部類數據 public void outterPrint (final String localData) { // 局部數據 class LocalInner { private String data = "內部數據"; // 內部類數據 public void innerPrint () { System.out.println(Outter.this.data); // 打印外部類數據 System.out.println(this.data); // 打印內部類數據 System.out.println(localData); // 打印局部數據 } } LocalInner i = new LocalInner(); i.innerPrint(); } }
Test.java:
public class Test { public static void main (String [] args) { Outter o = new Outter(); o.outterPrint("局部數據"); } }
結果輸出:
外部數據 內部數據 局部數據
局部類所使用的外部類方法的形參必須用final修飾
這裏要注意一點, 局部類所使用的外部類方法的形參必須用final修飾,不然會編譯不經過,也就是說傳入後不準改變
爲何這個方法形參必定要用final修飾?
(僅我的理解,若有不一樣的意見或者更好的理解歡迎在評論區討論)
若是不用final修飾會怎樣? 且聽我慢慢道來:
首先要說一下:
1.內部類和外部類在編譯以後形式上是同樣的,不會有內外之分
2.局部內部類對於使用的外部方法的值會用構造函數作一個拷貝(編譯後)
例如對於下面outterPrint方法中的LocalInner
public void outterPrint (final String data) { class LocalInner { public void innerPrint () { // 使用 data } } }/./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天
編譯以後大概長這樣:
public class Outter$LocalInner{ public LocalInner(String data){ this.LocalInner$data = data; // 對於使用的data作了一次拷貝 } public void innerPrint (){ /* 使用 data */ } }
這裏要注意的是:
OK,如今的狀況是:
方法內的局部類對data拷貝了兩次:外部方法outterPrint值傳遞時的拷貝,和LocalInner構造函數的拷貝
方法內除了局部類外的做用域只拷貝了data一次: 外部方法outterPrint值傳遞時的拷貝
拷貝兩次和拷貝一次,致使在outterPrint方法內部, 局部類內部的data和局部類外部的data是不一樣步的! 也即你在局部類內部改了data不影響局部類外部的data,在局部類外部改了data也不影響局部類內部的data(注意一個前提,值是基本類型的,若是是對象的話由於拷貝的是引用仍然能夠「同步」)
圖示一:
圖示二:
因而java說: 哎呀媽呀, 這都data都不一樣步了, 要是讓你修改這還了得!!! 因而就強行要求咱們加上final
【注意】所謂的不一樣步主要是針對基本類型來講的,若是是對象之類的話由於拷貝的是引用因此仍然能夠「同步」
如何突破必須用final的限制
咱們上面說到,局部內部類所使用的方法形參必須用final修飾的限制。
例如
public void outterPrint (String data) {// 沒加上final class LocalInner { public void changeData () { data = "我想修改data的值"; // 在這一行編譯報錯 } } }
提示:
Cannot refer to a non-final variable data inside an inner class defined in a different method
那麼,若是咱們有對該形參必須能修改的硬性需求怎麼辦?
你能夠經過一種有趣的方式繞開它:使用一個單元素數組。由於用final修飾的基本類型的變量不容許修改值,可是卻容許修改final修飾的單元素數組裏的數組元素, 由於存放數組的變量的值只是一個引用,咱們修改數組元素的時候是不會修改引用指向的地址的,在這點上final並不會妨礙咱們:
Outter.java
public class Outter { public void outterPrint (final String [] data) { class LocalInner { public void innerPrint () { data[0] = "冠冕堂皇地修改它!!"; // 修改數據 System.out.print(data[0]); // 輸出修改後的數據 } } LocalInner i = new LocalInner(); i.innerPrint(); } }
Test.java:
public class Test { public static void main (String [] args) { Outter o = new Outter(); String [] data = new String [1]; data[0] = "我是數據"; o.outterPrint(data); // 修改數據而且輸出 } }
結果輸出:
冠冕堂皇地修改它!!
【注意】局部類不能用public或private訪問符進行聲明!!
匿名內部類
假若咱們再把局部內部類再深化一下, 那就是匿名內部類
匿名內部類的使用方式
new [超類/接口] { /* 類體 */ }
讓咱們看看下面這個例子:
Other.java:
public class Other { }
Outter.java:
public class Outter { public void outterPrint (String data) { Other o = new Other() { }; // 匿名內部類 } }
何謂之匿名?
「誒,不是說好的匿名嗎? 那麼爲何還有個Other的類名呢?」
Other o = new Other() { /* 匿名內部類的類體 */ };
實際上,這裏的Other並非咱們的匿名內部類,而是咱們匿名內部類的超類,上面一行代碼其實至關於(用成員內部類來表示的話)
// annoymous翻譯爲匿名 public class Outter { private class annoymous extends Other{ } public void outterPrint () { Other a = new annoymous(); } }
同時要注意,咱們在使用匿名內部類的方式,是在定義一個內部類的同時實例化該內部類:
new Other() { /* 匿名內部類的類體 */ }; // new操做和定義類的代碼是牢牢結合在一塊兒的
匿名函數的做用
用匿名函數的做用在於在一些特定的場景下寫起來很簡單,例如事件監聽器:
ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent e) { } };
避免了再建立另一個類文件
講的有點亂, 對匿名內部類作個總結:
【注意】匿名類不能有構造器, 由於構造器和類同名,而匿名類沒有類名,因此匿名類不能有構造器
文章總結
咱們使用內部類的緣由主要有三點:
1.實現數據隱藏, 避免多餘的可見性
2.自由訪問外部類的變量
關於成員內部類, 方法局部類,匿名內部類的關係
從成員內部類,方法局部類到匿名內部類是一個不斷深刻的關係, 成員內部類進一步隱藏可見性就成爲了方法局部類, 方法局部類省去類名,並將類的定義和實例化操做合併到一塊兒,就是匿名內部類。所以,匿名內部類沿襲了成員內部類和方法局部類的基本特特性
內部類的一些特殊的要求
1.局部類不能用public或private訪問符進行聲明
2.局部類所使用的外部類方法的形參必須用final修飾
最新2020整理收集的一些高頻面試題(都整理成文檔),有不少乾貨,包含mysql,netty,spring,線程,spring cloud、jvm、源碼、算法等詳細講解,也有詳細的學習規劃圖,面試題整理等,須要獲取這些內容的朋友請加Q君樣:909038429/./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天