小編給大家分享一下在Java中什么是內(nèi)部類,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)長期為上千客戶提供的網(wǎng)站建設服務,團隊從業(yè)經(jīng)驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為調(diào)兵山企業(yè)提供專業(yè)的成都網(wǎng)站制作、網(wǎng)站設計,調(diào)兵山網(wǎng)站改版等技術服務。擁有十載豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
在Java中,可以將一個類的定義放在另外一個類的定義內(nèi)部,這就是內(nèi)部類。內(nèi)部類本身就是類的一個屬性,與其他屬性 定義方式一致。
一個內(nèi)部類的例子:
public class Outer { private int radius = 1 ; public static int count = 2 ; public Outer () { } class inner { public void visitOuter () { System.out.println( "visit outer private member variable:" + radius); System.out.println( "visit outer static variable:" + count); } } }
內(nèi)部類可以分為四種:成員內(nèi)部類、局部內(nèi)部類、匿名內(nèi)部類和靜態(tài)內(nèi)部類。
靜態(tài)內(nèi)部類
定義在類內(nèi)部的靜態(tài)類,就是靜態(tài)內(nèi)部類。
public class Outer { private static int radius = 1; static class StaticInner { public void visit() { System.out.println("visit outer static variable:" + radius); } } }
靜態(tài)內(nèi)部類可以訪問外部類所有的靜態(tài)變量,而不可訪問外部類的非靜態(tài)變量;靜態(tài)內(nèi)部類的創(chuàng)建方式, new外部類.靜態(tài)內(nèi)部類(),如下:
Outer.StaticInner inner = new Outer.StaticInner(); inner.visit();
成員內(nèi)部類
定義在類內(nèi)部,成員位置上的非靜態(tài)類,就是成員內(nèi)部類。
public class Outer { private static int radius = 1; private int count =2; class Inner { public void visit() { System.out.println("visit outer static variable:" + radius); System.out.println("visit outer variable:" + count); } } }
成員內(nèi)部類可以訪問外部類所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。成員內(nèi)部類依賴于外部類的實例,它的創(chuàng)建方式 外部類實例.new內(nèi)部類(),如下:
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.visit();
局部內(nèi)部類
定義在方法中的內(nèi)部類,就是局部內(nèi)部類。
public class Outer { private int out_a = 1; private static int STATIC_b = 2; public void testFunctionClass(){ int inner_c =3; class Inner { private void fun(){ System.out.println(out_a); System.out.println(STATIC_b); System.out.println(inner_c); } } Inner inner = new Inner(); inner.fun(); } public static void testStaticFunctionClass(){ int d =3; class Inner { private void fun(){ // System.out.println(out_a); 編譯錯誤,定義在靜態(tài)方法中的局部類不可以訪問外部類的實例變量 System.out.println(STATIC_b); System.out.println(d); } } Inner inner = new Inner(); inner.fun(); } }
定義在實例方法中的局部類可以訪問外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法。局部內(nèi)部類的創(chuàng)建方式,在對應方法內(nèi), new內(nèi)部類(),如下:
public static void testStaticFunctionClass(){ class Inner { } Inner inner = new Inner(); }
匿名內(nèi)部類
匿名內(nèi)部類就是沒有名字的內(nèi)部類,日常開發(fā)中使用的比較多。
public class Outer { private void test(final int i) { new Service() { public void method() { for (int j = 0; j < i; j++) { System.out.println("匿名內(nèi)部類" ); } } }.method(); } } //匿名內(nèi)部類必須繼承或?qū)崿F(xiàn)一個已有的接口 interface Service{ void method(); }
除了沒有名字,匿名內(nèi)部類還有以下特點:
匿名內(nèi)部類必須繼承一個抽象類或者實現(xiàn)一個接口。
匿名內(nèi)部類不能定義任何靜態(tài)成員和靜態(tài)方法。
當所在的方法的形參需要被匿名內(nèi)部類使用時,必須聲明為 final。
匿名內(nèi)部類不能是抽象的,它必須要實現(xiàn)繼承的類或者實現(xiàn)的接口的所有抽象方法。
匿名內(nèi)部類創(chuàng)建方式:
new 類/接口{ //匿名內(nèi)部類實現(xiàn)部分 }
我們?yōu)槭裁匆褂脙?nèi)部類呢?因為它有以下優(yōu)點:
一個內(nèi)部類對象可以訪問創(chuàng)建它的外部類對象的內(nèi)容,包括私有數(shù)據(jù)!
內(nèi)部類不為同一包的其他類所見,具有很好的封裝性;
內(nèi)部類有效實現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
匿名內(nèi)部類可以很方便的定義回調(diào)。
一個內(nèi)部類對象可以訪問創(chuàng)建它的外部類對象的內(nèi)容,包括私有數(shù)據(jù)!
public class Outer { private int radius = 1; protected void test(){ System.out.println("我是外部類方法"); } class Inner { public void visit() { System.out.println("訪問外部類變量" + radius); test(); } } }
我們可以看到,內(nèi)部類Inner是可以訪問外部類Outer的私有變量radius或者方法test的。
內(nèi)部類不為同一包的其他類所見,具有很好的封裝性
當內(nèi)部類使用 private修飾時,這個類就對外隱藏了。當內(nèi)部類實現(xiàn)某個接口,并且進行向上轉(zhuǎn)型,對外部來說,接口的實現(xiàn)已經(jīng)隱藏起來了,很好體現(xiàn)了封裝性。
//提供的接口 interface IContent{ String getContents(); } public class Outer { //私有內(nèi)部類屏蔽實現(xiàn)細節(jié) private class PContents implements IContent{ @Override public String getContents() { System.out.println("獲取內(nèi)部類內(nèi)容"); return "內(nèi)部類內(nèi)容"; } } //對外提供方法 public IContent getIContent() { return new PContents(); } public static void main(String[] args) { Outer outer=new Outer(); IContent a1=outer.getIContent(); a1.getContents(); } }
我們可以發(fā)現(xiàn),Outer外部類對外提供方法getIContent,用內(nèi)部類實現(xiàn)細節(jié),再用private修飾內(nèi)部類,屏蔽起來,把Java的封裝性表現(xiàn)的淋漓盡致。
內(nèi)部類有效實現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
我們知道Java世界中,一個類只能有一個直接父類,即以單繼承方式存在。但是內(nèi)部類讓“多繼承”成為可能:
一般來說,內(nèi)部類繼承某個類或者實現(xiàn)某個接口,內(nèi)部類的代碼操作創(chuàng)建它的外圍類的對象。內(nèi)部類提供了某種進入其外圍類的窗口。
每個內(nèi)部類都可以隊里的繼承自一個(接口的)實現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(接口的)實現(xiàn),對于內(nèi)部類沒有影響
接口解決了部分問題,一個類可以實現(xiàn)多個接口,內(nèi)部類允許繼承多個非接口類型(類或抽象類)。
一份來自Java編程思想,內(nèi)部類實現(xiàn)“多繼承”的溫暖如下:
class D {} abstract class E{} class Z extends D { E makeE(){ return new E() {}; } } public class MultiImplementation { static void takesD(D d) {} static void takesE(E e) {} public static void main(String[] args){ Z z = new Z(); takesD(z); takesE(z.makeE()); } }
代碼中出現(xiàn)了一個類D,一個抽象類E。然后,用類Z繼承D,內(nèi)部類構造返回E。因此,當你不管要的是D還是E,Z都可以應付,“多繼承”的特點完美表現(xiàn)出來。
匿名內(nèi)部類可以很方便的定義回調(diào)。
什么是回調(diào)?假設有兩個類A和B,在A中調(diào)用B的一個方法b,而b在執(zhí)行又調(diào)用了A的方法c,則c就稱為回調(diào)函數(shù)。
當然,回調(diào)函數(shù)也可以是a函數(shù),這就是同步回調(diào),最簡單的回調(diào)方式?;卣{(diào)應用場景挺多的,如android中的事件監(jiān)聽器。匿名內(nèi)部類可以很方便的定義回調(diào),看個例子
//定義一個CallBack接口 public interface CallBack { void execute(); } public class TimeTools { /** * 測試函數(shù)調(diào)用時長,通過定義CallBack接口的execute方法 * @param callBack */ public void testTime(CallBack callBack) { long beginTime = System.currentTimeMillis(); //記錄起始時間 callBack.execute(); ///進行回調(diào)操作 long endTime = System.currentTimeMillis(); //記錄結(jié)束時間 System.out.println("[use time]:" + (endTime - beginTime)); //打印使用時間 } public static void main(String[] args) { TimeTools tool = new TimeTools(); tool.testTime(new CallBack(){ //匿名內(nèi)部類,定義execute方法 public void execute(){ TestTimeObject testTimeObject = new TestTimeObject(); testTimeObject.testMethod(); } }); } }
在調(diào)用testTime()測時間的時候,用匿名內(nèi)部類實現(xiàn)一個方法execute(),在該方法內(nèi)搞事情(執(zhí)行目標函數(shù)),執(zhí)行完后,又回到testTime方法,很好了實現(xiàn)測試函數(shù)調(diào)用時長的功能。顯然,匿名內(nèi)部類讓回調(diào)實現(xiàn)變得簡單。
內(nèi)部類標志符
每個內(nèi)部類都會產(chǎn)生一個.class文件,其中包含了如何創(chuàng)建該類型的對象的全部信息。內(nèi)部類也必須生成一個.class文件以包含它們的Class對象信息。內(nèi)部類文件的命名有嚴格規(guī)則:外圍類的名字+$+內(nèi)部類的名字。
一個簡單例子:
public class Outer { class Inner{ } }
javac Outer.java編譯完成后, 生成的class文件如下:
如果內(nèi)部類是匿名的,編譯器會簡單地產(chǎn)生一個數(shù)字作為其標識符。如果內(nèi)部類是嵌套在別的內(nèi)部類之中(靜態(tài)內(nèi)部類),只需直接將它們的名字加在其外圍類標志符與“$”的后面。
為什么內(nèi)部類可以訪問外部類的成員,包括私有數(shù)據(jù)?
由上一小節(jié),我們知道內(nèi)部類可以訪問外部類的成員,包括私有數(shù)據(jù)。那么它是怎么做到的呢?接下來揭曉答案。
先看這個簡單地例子:
public class Outer { private int i = 0; class Inner{ void method(){ System.out.println(i); } } }
一個外部類Outer,一個外部類私有屬性i,一個內(nèi)部類Inner,一個內(nèi)部類方法method。內(nèi)部類方法訪問了外部類屬性i。
先編譯,javac Outer.java,生成.class文件,如下:
用命令 javap-classpath.-vOuter$Inner,反編譯Outter$Inner.class文件得到以下信息:
我們可以看到這一行,它是一個指向外部類對象的指針:
final innerclass.Outer this$0;
雖然編譯器在創(chuàng)建內(nèi)部類時為它加上了一個指向外部類的引用, 但是這個引用是怎樣賦值的呢?編譯器會為內(nèi)部類的構造方法添加一個參數(shù),進行初始化, 參數(shù)的類型就是外部類的類型,如下:
innerclass.Outer$Inner(innerclass.Outer);
成員內(nèi)部類中的Outter this&0 指針便指向了外部類對象,因此可以在成員內(nèi)部類中隨意訪問外部類的成員。
局部內(nèi)部類和匿名內(nèi)部類訪問局部變量的時候,為什么變量必須要加上final?
局部內(nèi)部類和匿名內(nèi)部類訪問局部變量的時候,為什么變量必須要加上final呢?它內(nèi)部原理是什么呢?
先看這段代碼:
public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } }
反編譯(Outer$1Inner)得到以下信息:
我們在內(nèi)部類innerMethod方法中,可以看到以下這條指令:
3:bipush 10
它表示將常量10壓入棧中,表示使用的是一個本地局部變量。
其實,如果一個變量的值在編譯期間可以確定(demo中確定是10了),則編譯器會默認在匿名內(nèi)部類(局部內(nèi)部類)的常量池中添加一個內(nèi)容相等的字面量或直接將相應的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中。
醬紫可以確保局部內(nèi)部類使用的變量與外層的局部變量區(qū)分開,它們只是值相等而已。
以上例子,為什么要加final呢?是因為生命周期不一致, 局部變量直接存儲在棧中,當方法執(zhí)行結(jié)束后,非final的局部變量就被銷毀。而局部內(nèi)部類對局部變量的引用依然存在,如果局部內(nèi)部類要調(diào)用局部變量時,就會出錯。加了final,可以確保局部內(nèi)部類使用的變量與外層的局部變量區(qū)分開,解決了這個問題。
我們再來看一段代碼,其實就是把變量a挪到傳參方式進來。
public class Outer { void outMethod(final int a){ class Inner { void innerMethod(){ System.out.println(a); } } } }
反編譯可得:
我們看到匿名內(nèi)部類Outer$1Inner的構造器含有兩個參數(shù),一個是指向外部類對象的引用,一個是int型變量,很顯然,這里是將變量innerMethod方法中的形參a以參數(shù)的形式傳進來對匿名內(nèi)部類中的拷貝(變量a的拷貝)進行賦值初始化。
那么,新的問題又來了,既然在innerMethod方法中訪問的變量a和outMethod方法中的變量a不是同一個變量,當在innerMethod方法中修改a會怎樣?那就會造成數(shù)據(jù)不一致的問題了。
怎么解決呢?使用final修飾符,final修飾的引用類型變量,不允許指向新的對象,這就解決數(shù)據(jù)不一致問題。注意: 在Java8 中,被局部內(nèi)部類引用的局部變量,默認添加final,所以不需要添加final關鍵詞。
一般我們在哪些場景下使用內(nèi)部類呢?
場景之一:一些多算法場合
一些算法多的場合,也可以借助內(nèi)部類,如:
Arrays.sort(emps,new Comparator(){ Public int compare(Object o1,Object o2) { return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears(); } });
場景二:解決一些非面向?qū)ο蟮恼Z句塊。
如果一些語句塊,包括if…else語句,case語句等等比較多,不好維護擴展,那么就可以借助內(nèi)部類+設計模式解決。
場景之三:適當使用內(nèi)部類,使得代碼更加靈活和富有擴展性。
適當?shù)氖褂脙?nèi)部類,可以使得你的代碼更加靈活和富有擴展性。如JDK的lamda表達式,用內(nèi)部類非常多,代碼優(yōu)雅很多。如下:
// JDK8 Lambda表達式寫法 new Thread(() -> System.out.println("Thread run()")).start();
場景四:當某個類除了它的外部類,不再被其他的類使用時。
如果一個類,不能為其他的類使用;或者出于某種原因,不能被其他類引用。那我們就可以考慮把它實現(xiàn)為內(nèi)部類。數(shù)據(jù)庫連接池就是這樣一個典型例子。
最后,我們來看一道經(jīng)典內(nèi)部類面試題吧。
public class Outer { private int age = 12; class Inner { private int age = 13; public void print() { int age = 14; System.out.println("局部變量:" + age); System.out.println("內(nèi)部類變量:" + this.age); System.out.println("外部類變量:" + Outer.this.age); } } public static void main(String[] args) { Outer.Inner in = new Outer().new Inner(); in.print(); } }
運行結(jié)果:
以上是“在Java中什么是內(nèi)部類”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
分享名稱:在Java中什么是內(nèi)部類
文章分享:http://chinadenli.net/article12/ppccgc.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導航、品牌網(wǎng)站建設、網(wǎng)站排名、外貿(mào)建站、搜索引擎優(yōu)化、網(wǎng)站設計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)