您好,登錄后才能下訂單哦!
小編給大家分享一下在Java中什么是內(nèi)部類,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
在Java中,可以將一個(gè)類的定義放在另外一個(gè)類的定義內(nèi)部,這就是內(nèi)部類。內(nèi)部類本身就是類的一個(gè)屬性,與其他屬性 定義方式一致。
一個(gè)內(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)部類、局部?jī)?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)部類可以訪問(wèn)外部類所有的靜態(tài)變量,而不可訪問(wèn)外部類的非靜態(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)部類可以訪問(wèn)外部類所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。成員內(nèi)部類依賴于外部類的實(shí)例,它的創(chuàng)建方式 外部類實(shí)例.new內(nèi)部類(),如下:
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.visit();
局部?jī)?nèi)部類
定義在方法中的內(nèi)部類,就是局部?jī)?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); 編譯錯(cuò)誤,定義在靜態(tài)方法中的局部類不可以訪問(wèn)外部類的實(shí)例變量 System.out.println(STATIC_b); System.out.println(d); } } Inner inner = new Inner(); inner.fun(); } }
定義在實(shí)例方法中的局部類可以訪問(wèn)外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問(wèn)外部類的靜態(tài)變量和方法。局部?jī)?nèi)部類的創(chuàng)建方式,在對(duì)應(yīng)方法內(nèi), new內(nèi)部類(),如下:
public static void testStaticFunctionClass(){ class Inner { } Inner inner = new Inner(); }
匿名內(nèi)部類
匿名內(nèi)部類就是沒(méi)有名字的內(nèi)部類,日常開(kā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)一個(gè)已有的接口 interface Service{ void method(); }
除了沒(méi)有名字,匿名內(nèi)部類還有以下特點(diǎn):
匿名內(nèi)部類必須繼承一個(gè)抽象類或者實(shí)現(xiàn)一個(gè)接口。
匿名內(nèi)部類不能定義任何靜態(tài)成員和靜態(tài)方法。
當(dāng)所在的方法的形參需要被匿名內(nèi)部類使用時(shí),必須聲明為 final。
匿名內(nèi)部類不能是抽象的,它必須要實(shí)現(xiàn)繼承的類或者實(shí)現(xiàn)的接口的所有抽象方法。
匿名內(nèi)部類創(chuàng)建方式:
new 類/接口{ //匿名內(nèi)部類實(shí)現(xiàn)部分 }
我們?yōu)槭裁匆褂脙?nèi)部類呢?因?yàn)樗幸韵聝?yōu)點(diǎn):
一個(gè)內(nèi)部類對(duì)象可以訪問(wèn)創(chuàng)建它的外部類對(duì)象的內(nèi)容,包括私有數(shù)據(jù)!
內(nèi)部類不為同一包的其他類所見(jiàn),具有很好的封裝性;
內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
匿名內(nèi)部類可以很方便的定義回調(diào)。
一個(gè)內(nèi)部類對(duì)象可以訪問(wèn)創(chuàng)建它的外部類對(duì)象的內(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("訪問(wèn)外部類變量" + radius); test(); } } }
我們可以看到,內(nèi)部類Inner是可以訪問(wèn)外部類Outer的私有變量radius或者方法test的。
內(nèi)部類不為同一包的其他類所見(jiàn),具有很好的封裝性
當(dāng)內(nèi)部類使用 private修飾時(shí),這個(gè)類就對(duì)外隱藏了。當(dāng)內(nèi)部類實(shí)現(xiàn)某個(gè)接口,并且進(jìn)行向上轉(zhuǎn)型,對(duì)外部來(lái)說(shuō),接口的實(shí)現(xiàn)已經(jīng)隱藏起來(lái)了,很好體現(xiàn)了封裝性。
//提供的接口 interface IContent{ String getContents(); } public class Outer { //私有內(nèi)部類屏蔽實(shí)現(xiàn)細(xì)節(jié) private class PContents implements IContent{ @Override public String getContents() { System.out.println("獲取內(nèi)部類內(nèi)容"); return "內(nèi)部類內(nèi)容"; } } //對(duì)外提供方法 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外部類對(duì)外提供方法getIContent,用內(nèi)部類實(shí)現(xiàn)細(xì)節(jié),再用private修飾內(nèi)部類,屏蔽起來(lái),把Java的封裝性表現(xiàn)的淋漓盡致。
內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
我們知道Java世界中,一個(gè)類只能有一個(gè)直接父類,即以單繼承方式存在。但是內(nèi)部類讓“多繼承”成為可能:
一般來(lái)說(shuō),內(nèi)部類繼承某個(gè)類或者實(shí)現(xiàn)某個(gè)接口,內(nèi)部類的代碼操作創(chuàng)建它的外圍類的對(duì)象。內(nèi)部類提供了某種進(jìn)入其外圍類的窗口。
每個(gè)內(nèi)部類都可以隊(duì)里的繼承自一個(gè)(接口的)實(shí)現(xiàn),所以無(wú)論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類沒(méi)有影響
接口解決了部分問(wèn)題,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,內(nèi)部類允許繼承多個(gè)非接口類型(類或抽象類)。
一份來(lái)自Java編程思想,內(nèi)部類實(shí)現(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)了一個(gè)類D,一個(gè)抽象類E。然后,用類Z繼承D,內(nèi)部類構(gòu)造返回E。因此,當(dāng)你不管要的是D還是E,Z都可以應(yīng)付,“多繼承”的特點(diǎn)完美表現(xiàn)出來(lái)。
匿名內(nèi)部類可以很方便的定義回調(diào)。
什么是回調(diào)?假設(shè)有兩個(gè)類A和B,在A中調(diào)用B的一個(gè)方法b,而b在執(zhí)行又調(diào)用了A的方法c,則c就稱為回調(diào)函數(shù)。
當(dāng)然,回調(diào)函數(shù)也可以是a函數(shù),這就是同步回調(diào),最簡(jiǎn)單的回調(diào)方式?;卣{(diào)應(yīng)用場(chǎng)景挺多的,如android中的事件監(jiān)聽(tīng)器。匿名內(nèi)部類可以很方便的定義回調(diào),看個(gè)例子
//定義一個(gè)CallBack接口 public interface CallBack { void execute(); } public class TimeTools { /** * 測(cè)試函數(shù)調(diào)用時(shí)長(zhǎng),通過(guò)定義CallBack接口的execute方法 * @param callBack */ public void testTime(CallBack callBack) { long beginTime = System.currentTimeMillis(); //記錄起始時(shí)間 callBack.execute(); ///進(jìn)行回調(diào)操作 long endTime = System.currentTimeMillis(); //記錄結(jié)束時(shí)間 System.out.println("[use time]:" + (endTime - beginTime)); //打印使用時(shí)間 } 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()測(cè)時(shí)間的時(shí)候,用匿名內(nèi)部類實(shí)現(xiàn)一個(gè)方法execute(),在該方法內(nèi)搞事情(執(zhí)行目標(biāo)函數(shù)),執(zhí)行完后,又回到testTime方法,很好了實(shí)現(xiàn)測(cè)試函數(shù)調(diào)用時(shí)長(zhǎng)的功能。顯然,匿名內(nèi)部類讓回調(diào)實(shí)現(xiàn)變得簡(jiǎn)單。
內(nèi)部類標(biāo)志符
每個(gè)內(nèi)部類都會(huì)產(chǎn)生一個(gè).class文件,其中包含了如何創(chuàng)建該類型的對(duì)象的全部信息。內(nèi)部類也必須生成一個(gè).class文件以包含它們的Class對(duì)象信息。內(nèi)部類文件的命名有嚴(yán)格規(guī)則:外圍類的名字+$+內(nèi)部類的名字。
一個(gè)簡(jiǎn)單例子:
public class Outer { class Inner{ } }
javac Outer.java編譯完成后, 生成的class文件如下:
如果內(nèi)部類是匿名的,編譯器會(huì)簡(jiǎn)單地產(chǎn)生一個(gè)數(shù)字作為其標(biāo)識(shí)符。如果內(nèi)部類是嵌套在別的內(nèi)部類之中(靜態(tài)內(nèi)部類),只需直接將它們的名字加在其外圍類標(biāo)志符與“$”的后面。
為什么內(nèi)部類可以訪問(wèn)外部類的成員,包括私有數(shù)據(jù)?
由上一小節(jié),我們知道內(nèi)部類可以訪問(wèn)外部類的成員,包括私有數(shù)據(jù)。那么它是怎么做到的呢?接下來(lái)揭曉答案。
先看這個(gè)簡(jiǎn)單地例子:
public class Outer { private int i = 0; class Inner{ void method(){ System.out.println(i); } } }
一個(gè)外部類Outer,一個(gè)外部類私有屬性i,一個(gè)內(nèi)部類Inner,一個(gè)內(nèi)部類方法method。內(nèi)部類方法訪問(wèn)了外部類屬性i。
先編譯,javac Outer.java,生成.class文件,如下:
用命令 javap-classpath.-vOuter$Inner,反編譯Outter$Inner.class文件得到以下信息:
我們可以看到這一行,它是一個(gè)指向外部類對(duì)象的指針:
final innerclass.Outer this$0;
雖然編譯器在創(chuàng)建內(nèi)部類時(shí)為它加上了一個(gè)指向外部類的引用, 但是這個(gè)引用是怎樣賦值的呢?編譯器會(huì)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù),進(jìn)行初始化, 參數(shù)的類型就是外部類的類型,如下:
innerclass.Outer$Inner(innerclass.Outer);
成員內(nèi)部類中的Outter this&0 指針便指向了外部類對(duì)象,因此可以在成員內(nèi)部類中隨意訪問(wèn)外部類的成員。
局部?jī)?nèi)部類和匿名內(nèi)部類訪問(wèn)局部變量的時(shí)候,為什么變量必須要加上final?
局部?jī)?nèi)部類和匿名內(nèi)部類訪問(wèn)局部變量的時(shí)候,為什么變量必須要加上final呢?它內(nèi)部原理是什么呢?
先看這段代碼:
public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } }
反編譯(Outer$1Inner)得到以下信息:
我們?cè)趦?nèi)部類innerMethod方法中,可以看到以下這條指令:
3:bipush 10
它表示將常量10壓入棧中,表示使用的是一個(gè)本地局部變量。
其實(shí),如果一個(gè)變量的值在編譯期間可以確定(demo中確定是10了),則編譯器會(huì)默認(rèn)在匿名內(nèi)部類(局部?jī)?nèi)部類)的常量池中添加一個(gè)內(nèi)容相等的字面量或直接將相應(yīng)的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中。
醬紫可以確保局部?jī)?nèi)部類使用的變量與外層的局部變量區(qū)分開(kāi),它們只是值相等而已。
以上例子,為什么要加final呢?是因?yàn)樯芷诓灰恢拢? 局部變量直接存儲(chǔ)在棧中,當(dāng)方法執(zhí)行結(jié)束后,非final的局部變量就被銷毀。而局部?jī)?nèi)部類對(duì)局部變量的引用依然存在,如果局部?jī)?nèi)部類要調(diào)用局部變量時(shí),就會(huì)出錯(cuò)。加了final,可以確保局部?jī)?nèi)部類使用的變量與外層的局部變量區(qū)分開(kāi),解決了這個(gè)問(wèn)題。
我們?cè)賮?lái)看一段代碼,其實(shí)就是把變量a挪到傳參方式進(jìn)來(lái)。
public class Outer { void outMethod(final int a){ class Inner { void innerMethod(){ System.out.println(a); } } } }
反編譯可得:
我們看到匿名內(nèi)部類Outer$1Inner的構(gòu)造器含有兩個(gè)參數(shù),一個(gè)是指向外部類對(duì)象的引用,一個(gè)是int型變量,很顯然,這里是將變量innerMethod方法中的形參a以參數(shù)的形式傳進(jìn)來(lái)對(duì)匿名內(nèi)部類中的拷貝(變量a的拷貝)進(jìn)行賦值初始化。
那么,新的問(wèn)題又來(lái)了,既然在innerMethod方法中訪問(wèn)的變量a和outMethod方法中的變量a不是同一個(gè)變量,當(dāng)在innerMethod方法中修改a會(huì)怎樣?那就會(huì)造成數(shù)據(jù)不一致的問(wèn)題了。
怎么解決呢?使用final修飾符,final修飾的引用類型變量,不允許指向新的對(duì)象,這就解決數(shù)據(jù)不一致問(wèn)題。注意: 在Java8 中,被局部?jī)?nèi)部類引用的局部變量,默認(rèn)添加final,所以不需要添加final關(guān)鍵詞。
一般我們?cè)谀男﹫?chǎng)景下使用內(nèi)部類呢?
場(chǎng)景之一:一些多算法場(chǎng)合
一些算法多的場(chǎng)合,也可以借助內(nèi)部類,如:
Arrays.sort(emps,new Comparator(){ Public int compare(Object o1,Object o2) { return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears(); } });
場(chǎng)景二:解決一些非面向?qū)ο蟮恼Z(yǔ)句塊。
如果一些語(yǔ)句塊,包括if…else語(yǔ)句,case語(yǔ)句等等比較多,不好維護(hù)擴(kuò)展,那么就可以借助內(nèi)部類+設(shè)計(jì)模式解決。
場(chǎng)景之三:適當(dāng)使用內(nèi)部類,使得代碼更加靈活和富有擴(kuò)展性。
適當(dāng)?shù)氖褂脙?nèi)部類,可以使得你的代碼更加靈活和富有擴(kuò)展性。如JDK的lamda表達(dá)式,用內(nèi)部類非常多,代碼優(yōu)雅很多。如下:
// JDK8 Lambda表達(dá)式寫法 new Thread(() -> System.out.println("Thread run()")).start();
場(chǎng)景四:當(dāng)某個(gè)類除了它的外部類,不再被其他的類使用時(shí)。
如果一個(gè)類,不能為其他的類使用;或者出于某種原因,不能被其他類引用。那我們就可以考慮把它實(shí)現(xiàn)為內(nèi)部類。數(shù)據(jù)庫(kù)連接池就是這樣一個(gè)典型例子。
最后,我們來(lái)看一道經(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(); } }
運(yùn)行結(jié)果:
以上是“在Java中什么是內(nèi)部類”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。