Java提高篇 —— Java内部类详解
一、簡(jiǎn)介
?
? ? ? ?內(nèi)部類是一個(gè)非常有用的特性但又比較難理解使用的特性。
? ? ? ?內(nèi)部類我們從外面看是非常容易理解的,無(wú)非就是在一個(gè)類的內(nèi)部在定義一個(gè)類。
public class OuterClass {private String name ;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}class InnerClass{public InnerClass(){name = "chenssy";age = 23;}} }? ? ? ?在這里InnerClass就是內(nèi)部類,對(duì)于初學(xué)者來說內(nèi)部類實(shí)在是使用的不多,但是隨著編程能力的提高,我們會(huì)領(lǐng)悟到它的魅力所在,它可以使用能夠更加優(yōu)雅的設(shè)計(jì)我們的程序結(jié)構(gòu)。在使用內(nèi)部類之間我們需要明白為什么要使用內(nèi)部類,內(nèi)部類能夠?yàn)槲覀儙硎裁礃拥暮锰帯?/p>
?
二、為什么要使用內(nèi)部類?
?
? ? ? ?為什么要使用內(nèi)部類?在《Think in java》中有這樣一句話:使用內(nèi)部類最吸引人的原因是:每個(gè)內(nèi)部類都能獨(dú)立地繼承一個(gè)(接口的)實(shí)現(xiàn),所以無(wú)論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類都沒有影響。
? ? ? ?在我們程序設(shè)計(jì)中有時(shí)候會(huì)存在一些使用接口很難解決的問題,這個(gè)時(shí)候我們可以利用內(nèi)部類提供的、可以繼承多個(gè)具體的或者抽象的類的能力來解決這些程序設(shè)計(jì)問題。可以這樣說,接口只是解決了部分問題,而內(nèi)部類使得多重繼承的解決方案變得更加完整。
public interface Father {}public interface Mother {}public class Son implements Father, Mother {}public class Daughter implements Father{class Mother_ implements Mother{} }? ? ? ?其實(shí)對(duì)于這個(gè)實(shí)例我們確實(shí)是看不出來使用內(nèi)部類存在何種優(yōu)點(diǎn),但是如果Father、Mother不是接口,而是抽象類或者具體類呢?這個(gè)時(shí)候我們就只能使用內(nèi)部類才能實(shí)現(xiàn)多重繼承了。
? ? ? ?其實(shí)使用內(nèi)部類最大的優(yōu)點(diǎn)就在于它能夠非常好的解決多重繼承的問題,但是如果我們不需要解決多重繼承問題,那么我們自然可以使用其他的編碼方式,但是使用內(nèi)部類還能夠?yàn)槲覀儙砣缦绿匦?#xff08;摘自《Think in java》):
? ? ? ?1、內(nèi)部類可以用多個(gè)實(shí)例,每個(gè)實(shí)例都有自己的狀態(tài)信息,并且與其他外圍對(duì)象的信息相互獨(dú)立。
? ? ? ?2、在單個(gè)外圍類中,可以讓多個(gè)內(nèi)部類以不同的方式實(shí)現(xiàn)同一個(gè)接口,或者繼承同一個(gè)類。
? ? ? ?3、創(chuàng)建內(nèi)部類對(duì)象的時(shí)刻并不依賴于外圍類對(duì)象的創(chuàng)建。
? ? ? ?4、內(nèi)部類并沒有令人迷惑的“is-a”關(guān)系,他就是一個(gè)獨(dú)立的實(shí)體。
? ? ? ?5、內(nèi)部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
?
三、內(nèi)部類基礎(chǔ)
?
? ? ? ?在這個(gè)部分主要介紹內(nèi)部類如何使用外部類的屬性和方法,以及使用.this與.new。
? ? ? ?當(dāng)我們?cè)趧?chuàng)建一個(gè)內(nèi)部類的時(shí)候,它無(wú)形中就與外圍類有了一種聯(lián)系,依賴于這種聯(lián)系,它可以無(wú)限制地訪問外圍類的元素。
public class OuterClass {private String name ;private int age;/**省略getter和setter方法**/public class InnerClass{public InnerClass(){name = "chenssy";age = 23;}public void display(){System.out.println("name:" + getName() +" ;age:" + getAge());}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.display();} } -------------- Output: name:chenssy ;age:23? ? ? ?在這個(gè)應(yīng)用程序中,我們可以看到內(nèi)部了InnerClass可以對(duì)外圍類OuterClass的屬性進(jìn)行無(wú)縫的訪問,盡管它是private修飾的。這是因?yàn)楫?dāng)我們?cè)趧?chuàng)建某個(gè)外圍類的內(nèi)部類對(duì)象時(shí),此時(shí)內(nèi)部類對(duì)象必定會(huì)捕獲一個(gè)指向那個(gè)外圍類對(duì)象的引用,只要我們?cè)谠L問外圍類的成員時(shí),就會(huì)用這個(gè)引用來選擇外圍類的成員。
? ? ? ?其實(shí)在這個(gè)應(yīng)用程序中我們還看到了如何來引用內(nèi)部類:引用內(nèi)部類我們需要指明這個(gè)對(duì)象的類型:OuterClasName.InnerClassName。同時(shí)如果我們需要?jiǎng)?chuàng)建某個(gè)內(nèi)部類對(duì)象,必須要利用外部類的對(duì)象通過.new來創(chuàng)建內(nèi)部類:?OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
? ? ? ?同時(shí)如果我們需要生成對(duì)外部類對(duì)象的引用,可以使用OuterClassName.this,這樣就能夠產(chǎn)生一個(gè)正確引用外部類的引用了。當(dāng)然這點(diǎn)實(shí)在編譯期就知曉了,沒有任何運(yùn)行時(shí)的成本。
public class OuterClass {public void display(){System.out.println("OuterClass...");}public class InnerClass{public OuterClass getOuterClass(){return OuterClass.this;}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.getOuterClass().display();} } ------------- Output: OuterClass...? ? ? ?到這里了我們需要明確一點(diǎn),內(nèi)部類是個(gè)編譯時(shí)的概念,一旦編譯成功后,它就與外圍類屬于兩個(gè)完全不同的類(當(dāng)然他們之間還是有聯(lián)系的)。對(duì)于一個(gè)名為OuterClass的外圍類和一個(gè)名為InnerClass的內(nèi)部類,在編譯成功后,會(huì)出現(xiàn)這樣兩個(gè)class文件:OuterClass.class和OuterClass$InnerClass.class。
? ? ? ?在Java中內(nèi)部類主要分為:成員內(nèi)部類、局部?jī)?nèi)部類、匿名內(nèi)部類、靜態(tài)內(nèi)部類。
?
四、成員內(nèi)部類
?
? ? ? ?成員內(nèi)部類也是最普通的內(nèi)部類,它是外圍類的一個(gè)成員,所以他是可以無(wú)限制的訪問外圍類的所有 成員屬性和方法,盡管是private的,但是外圍類要訪問內(nèi)部類的成員屬性和方法則需要通過內(nèi)部類實(shí)例來訪問。
在成員內(nèi)部類中要注意兩點(diǎn):
? ? ? ?第一:成員內(nèi)部類中不能存在任何static的變量和方法;
? ? ? ?第二:成員內(nèi)部類是依附于外圍類的,所以只有先創(chuàng)建了外圍類才能夠創(chuàng)建內(nèi)部類。
public class OuterClass {private String str;public void outerDisplay(){System.out.println("outerClass...");}public class InnerClass{public void innerDisplay(){//使用外圍內(nèi)的屬性str = "chenssy...";System.out.println(str);//使用外圍內(nèi)的方法outerDisplay();}}/*推薦使用getxxx()來獲取成員內(nèi)部類,尤其是該內(nèi)部類的構(gòu)造函數(shù)無(wú)參數(shù)時(shí) */public InnerClass getInnerClass(){return new InnerClass();}public static void main(String[] args) {OuterClass outer = new OuterClass();OuterClass.InnerClass inner = outer.getInnerClass();inner.innerDisplay();} } -------------------- chenssy... outerClass...PS:推薦使用getxxx()來獲取成員內(nèi)部類,尤其是該內(nèi)部類的構(gòu)造函數(shù)無(wú)參數(shù)時(shí) 。
?
五、局部?jī)?nèi)部類
?
? ? ? ?有這樣一種內(nèi)部類,它是嵌套在方法和作用于內(nèi)的,對(duì)于這個(gè)類的使用主要是應(yīng)用與解決比較復(fù)雜的問題,想創(chuàng)建一個(gè)類來輔助我們的解決方案,到那時(shí)又不希望這個(gè)類是公共可用的,所以就產(chǎn)生了局部?jī)?nèi)部類,局部?jī)?nèi)部類和成員內(nèi)部類一樣被編譯,只是它的作用域發(fā)生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會(huì)失效。
? ? ? ?對(duì)于局部?jī)?nèi)部類實(shí)在是想不出什么好例子,所以就引用《Think in java》中的經(jīng)典例子了。
定義在方法里:
public class Parcel5 {public Destionation destionation(String str){class PDestionation implements Destionation{private String label;private PDestionation(String whereTo){label = whereTo;}public String readLabel(){return label;}}return new PDestionation(str);}public static void main(String[] args) {Parcel5 parcel5 = new Parcel5();Destionation d = parcel5.destionation("chenssy");} }定義在作用域內(nèi):
public class Parcel6 {private void internalTracking(boolean b){if(b){class TrackingSlip{private String id;TrackingSlip(String s) {id = s;}String getSlip(){return id;}}TrackingSlip ts = new TrackingSlip("chenssy");String string = ts.getSlip();}}public void track(){internalTracking(true);}public static void main(String[] args) {Parcel6 parcel6 = new Parcel6();parcel6.track();} }?
六、匿名內(nèi)部類
?
在做Swing編程中,我們經(jīng)常使用這種方式來綁定事件:
button2.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { System.out.println("你按了按鈕二"); } });我們咋一看可能覺得非常奇怪,因?yàn)檫@個(gè)內(nèi)部類是沒有名字的,在看如下這個(gè)例子:
public class OuterClass {public InnerClass getInnerClass(final int num,String str2){return new InnerClass(){int number = num + 3;public int getNumber(){return number;}}; /* 注意:分號(hào)不能省 */}public static void main(String[] args) {OuterClass out = new OuterClass();InnerClass inner = out.getInnerClass(2, "chenssy");System.out.println(inner.getNumber());} }interface InnerClass {int getNumber(); }---------------- Output: 5這里我們就需要看清幾個(gè)地方:
? ? ? ?1、?匿名內(nèi)部類是沒有訪問修飾符的。
? ? ? ?2、?new 匿名內(nèi)部類,這個(gè)類首先是要存在的。如果我們將那個(gè)InnerClass接口注釋掉,就會(huì)出現(xiàn)編譯出錯(cuò)。
? ? ? ?3、?注意getInnerClass()方法的形參,第一個(gè)形參是用final修飾的,而第二個(gè)卻沒有。同時(shí)我們也發(fā)現(xiàn)第二個(gè)形參在匿名內(nèi)部類中沒有使用過,所以當(dāng)所在方法的形參需要被匿名內(nèi)部類使用,那么這個(gè)形參就必須為final。
? ? ? ?4、?匿名內(nèi)部類是沒有構(gòu)造方法的。因?yàn)樗B名字都沒有何來構(gòu)造方法。
?
? ? ? ?上面第一個(gè)形參定義成final的,而第二個(gè)卻沒有,那么形參為什么要定義為final呢?在網(wǎng)上找到本人比較如同的解釋:
? ? ? ?這是一個(gè)編譯器設(shè)計(jì)的問題,如果你了解java的編譯原理的話很容易理解。 ?
? ? ? ?首先,內(nèi)部類被編譯的時(shí)候會(huì)生成一個(gè)單獨(dú)的內(nèi)部類的.class文件,這個(gè)文件并不與外部類在同一class文件中。 ?
? ? ? ?當(dāng)外部類傳的參數(shù)被內(nèi)部類調(diào)用時(shí),從java程序的角度來看是直接的調(diào)用例如:?
public void dosome(final String a,final int b){ class Dosome{public void dosome(){System.out.println(a+b)}}; Dosome some=new Dosome(); some.dosome(); }? ? ? ?從代碼來看好像是那個(gè)內(nèi)部類直接調(diào)用的a參數(shù)和b參數(shù),但是實(shí)際上不是,在java編譯器編譯以后實(shí)際的操作代碼是:
class Outer$Dosome{ public Dosome(final String a,final int b){ this.Dosome$a=a; this.Dosome$b=b; } public void dosome(){ System.out.println(this.Dosome$a+this.Dosome$b); } }? ? ? ?從以上代碼看來,內(nèi)部類并不是直接調(diào)用方法傳進(jìn)來的參數(shù),而是內(nèi)部類將傳進(jìn)來的參數(shù)通過自己的構(gòu)造器備份到了自己的內(nèi)部,自己內(nèi)部的方法調(diào)用的實(shí)際是自己的屬性而不是外部類方法的參數(shù)。
? ? ? ?這樣理解就很容易得出為什么要用final了,因?yàn)閮烧邚耐獗砜雌饋硎峭粋€(gè)東西,實(shí)際上卻不是這樣,如果內(nèi)部類改掉了這些參數(shù)的值也不可能影響到原參數(shù),然而這樣卻失去了參數(shù)的一致性,因?yàn)閺木幊倘藛T的角度來看他們是同一個(gè)東西,如果編程人員在程序設(shè)計(jì)的時(shí)候在內(nèi)部類中改掉參數(shù)的值,但是外部調(diào)用的時(shí)候又發(fā)現(xiàn)值其實(shí)沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設(shè)計(jì)人員把內(nèi)部類能夠使用的參數(shù)設(shè)定為必須是final來規(guī)避這種莫名其妙錯(cuò)誤的存在。(簡(jiǎn)單理解就是,拷貝引用,為了避免引用值發(fā)生改變,例如被外部類的方法修改等,而導(dǎo)致內(nèi)部類得到的值不一致,于是用final來讓該引用不可改變)
? ? ? ?因?yàn)槟涿麅?nèi)部類,沒名字,是用默認(rèn)的構(gòu)造函數(shù)的,無(wú)參數(shù)的,那如果需要參數(shù)呢?則需要該類有帶參數(shù)的構(gòu)造函數(shù):
public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } public Inner getInner(final String name, String city) { return new Inner(name, city) { private String nameStr = name; public String getName() { return nameStr; } }; } } abstract class Inner { Inner(String name, String city) { System.out.println(city); } abstract String getName(); }? ? ? ?注意這里的形參city,由于它沒有被匿名內(nèi)部類直接使用,而是被抽象類Inner的構(gòu)造函數(shù)所使用,所以不必定義為final。
? ? ? ?而匿名內(nèi)部類通過實(shí)例初始化,可以達(dá)到類似構(gòu)造器的效果:
public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); System.out.println(inner.getProvince()); } public Inner getInner(final String name, final String city) { return new Inner() { private String nameStr = name; private String province; // 實(shí)例初始化 { if (city.equals("gz")) { province = "gd"; }else { province = ""; } } public String getName() { return nameStr; } public String getProvince() { return province; } }; } } interface Inner { String getName(); String getProvince(); }?
七、靜態(tài)內(nèi)部類
?
? ? ? ?Static可以修飾成員變量、方法、代碼塊,其他它還可以修飾內(nèi)部類,使用static修飾的內(nèi)部類我們稱之為靜態(tài)內(nèi)部類,不過我們更喜歡稱之為嵌套內(nèi)部類。靜態(tài)內(nèi)部類與非靜態(tài)內(nèi)部類之間存在一個(gè)最大的區(qū)別,我們知道非靜態(tài)內(nèi)部類在編譯完成之后會(huì)隱含地保存著一個(gè)引用,該引用是指向創(chuàng)建它的外圍內(nèi),但是靜態(tài)內(nèi)部類卻沒有。沒有這個(gè)引用就意味著:
? ? ? ?1、?它的創(chuàng)建是不需要依賴于外圍類的。
? ? ? ?2、?它不能使用任何外圍類的非static成員變量和方法。
public class OuterClass {private String sex;public static String name = "chenssy";/***靜態(tài)內(nèi)部類*/static class InnerClass1{/* 在靜態(tài)內(nèi)部類中可以存在靜態(tài)成員 */public static String _name1 = "chenssy_static";public void display(){/* * 靜態(tài)內(nèi)部類只能訪問外圍類的靜態(tài)成員變量和方法* 不能訪問外圍類的非靜態(tài)成員變量和方法*/System.out.println("OutClass name :" + name);}}/*** 非靜態(tài)內(nèi)部類*/class InnerClass2{/* 非靜態(tài)內(nèi)部類中不能存在靜態(tài)成員 */public String _name2 = "chenssy_inner";/* 非靜態(tài)內(nèi)部類中可以調(diào)用外圍類的任何成員,不管是靜態(tài)的還是非靜態(tài)的 */public void display(){System.out.println("OuterClass name:" + name);}}/*** @desc 外圍類方法* @author chenssy* @data 2013-10-25* @return void*/public void display(){/* 外圍類訪問靜態(tài)內(nèi)部類:內(nèi)部類. */System.out.println(InnerClass1._name1);/* 靜態(tài)內(nèi)部類 可以直接創(chuàng)建實(shí)例不需要依賴于外圍類 */new InnerClass1().display();/* 非靜態(tài)內(nèi)部的創(chuàng)建需要依賴于外圍類 */OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();/* 方位非靜態(tài)內(nèi)部類的成員需要使用非靜態(tài)內(nèi)部類的實(shí)例 */System.out.println(inner2._name2);inner2.display();}public static void main(String[] args) {OuterClass outer = new OuterClass();outer.display();} } ---------------- Output: chenssy_static OutClass name :chenssy chenssy_inner OuterClass name:chenssy? ? ? ?上面這個(gè)例子充分展現(xiàn)了靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的區(qū)別。
?
?
總結(jié)
以上是生活随笔為你收集整理的Java提高篇 —— Java内部类详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 教给孩子的10句“保命金言”(图)
- 下一篇: java美元兑换,(Java实现) 美元