一、簡介
?
? ? ? ?內部類是一個非常有用的特性但又比較難理解使用的特性。
? ? ? ?內部類我們從外面看是非常容易理解的,無非就是在一個類的內部在定義一個類。
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就是內部類,對于初學者來說內部類實在是使用的不多,但是隨著編程能力的提高,我們會領悟到它的魅力所在,它可以使用能夠更加優雅的設計我們的程序結構。在使用內部類之間我們需要明白為什么要使用內部類,內部類能夠為我們帶來什么樣的好處。
?
二、為什么要使用內部類?
?
? ? ? ?為什么要使用內部類?在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對于內部類都沒有影響。
? ? ? ?在我們程序設計中有時候會存在一些使用接口很難解決的問題,這個時候我們可以利用內部類提供的、可以繼承多個具體的或者抽象的類的能力來解決這些程序設計問題。可以這樣說,接口只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。
public interface Father {}public interface Mother {}public class Son implements Father, Mother {}public class Daughter implements Father{class Mother_ implements Mother{}
}
? ? ? ?其實對于這個實例我們確實是看不出來使用內部類存在何種優點,但是如果Father、Mother不是接口,而是抽象類或者具體類呢?這個時候我們就只能使用內部類才能實現多重繼承了。
? ? ? ?其實使用內部類最大的優點就在于它能夠非常好的解決多重繼承的問題,但是如果我們不需要解決多重繼承問題,那么我們自然可以使用其他的編碼方式,但是使用內部類還能夠為我們帶來如下特性(摘自《Think in java》):
? ? ? ?1、內部類可以用多個實例,每個實例都有自己的狀態信息,并且與其他外圍對象的信息相互獨立。
? ? ? ?2、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或者繼承同一個類。
? ? ? ?3、創建內部類對象的時刻并不依賴于外圍類對象的創建。
? ? ? ?4、內部類并沒有令人迷惑的“is-a”關系,他就是一個獨立的實體。
? ? ? ?5、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
?
三、內部類基礎
?
? ? ? ?在這個部分主要介紹內部類如何使用外部類的屬性和方法,以及使用.this與.new。
? ? ? ?當我們在創建一個內部類的時候,它無形中就與外圍類有了一種聯系,依賴于這種聯系,它可以無限制地訪問外圍類的元素。
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
? ? ? ?在這個應用程序中,我們可以看到內部了InnerClass可以對外圍類OuterClass的屬性進行無縫的訪問,盡管它是private修飾的。這是因為當我們在創建某個外圍類的內部類對象時,此時內部類對象必定會捕獲一個指向那個外圍類對象的引用,只要我們在訪問外圍類的成員時,就會用這個引用來選擇外圍類的成員。
? ? ? ?其實在這個應用程序中我們還看到了如何來引用內部類:引用內部類我們需要指明這個對象的類型:OuterClasName.InnerClassName。同時如果我們需要創建某個內部類對象,必須要利用外部類的對象通過.new來創建內部類:?OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
? ? ? ?同時如果我們需要生成對外部類對象的引用,可以使用OuterClassName.this,這樣就能夠產生一個正確引用外部類的引用了。當然這點實在編譯期就知曉了,沒有任何運行時的成本。
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...
? ? ? ?到這里了我們需要明確一點,內部類是個編譯時的概念,一旦編譯成功后,它就與外圍類屬于兩個完全不同的類(當然他們之間還是有聯系的)。對于一個名為OuterClass的外圍類和一個名為InnerClass的內部類,在編譯成功后,會出現這樣兩個class文件:OuterClass.class和OuterClass$InnerClass.class。
? ? ? ?在Java中內部類主要分為:成員內部類、局部內部類、匿名內部類、靜態內部類。
?
四、成員內部類
?
? ? ? ?成員內部類也是最普通的內部類,它是外圍類的一個成員,所以他是可以無限制的訪問外圍類的所有 成員屬性和方法,盡管是private的,但是外圍類要訪問內部類的成員屬性和方法則需要通過內部類實例來訪問。
在成員內部類中要注意兩點:
? ? ? ?第一:成員內部類中不能存在任何static的變量和方法;
? ? ? ?第二:成員內部類是依附于外圍類的,所以只有先創建了外圍類才能夠創建內部類。
public class OuterClass {private String str;public void outerDisplay(){System.out.println("outerClass...");}public class InnerClass{public void innerDisplay(){//使用外圍內的屬性str = "chenssy...";System.out.println(str);//使用外圍內的方法outerDisplay();}}/*推薦使用getxxx()來獲取成員內部類,尤其是該內部類的構造函數無參數時 */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()來獲取成員內部類,尤其是該內部類的構造函數無參數時 。
?
五、局部內部類
?
? ? ? ?有這樣一種內部類,它是嵌套在方法和作用于內的,對于這個類的使用主要是應用與解決比較復雜的問題,想創建一個類來輔助我們的解決方案,到那時又不希望這個類是公共可用的,所以就產生了局部內部類,局部內部類和成員內部類一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。
? ? ? ?對于局部內部類實在是想不出什么好例子,所以就引用《Think in java》中的經典例子了。
定義在方法里:
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");}
}
定義在作用域內:
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();}
}
?
六、匿名內部類
?
在做Swing編程中,我們經常使用這種方式來綁定事件:
button2.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { System.out.println("你按了按鈕二"); } });
我們咋一看可能覺得非常奇怪,因為這個內部類是沒有名字的,在看如下這個例子:
public class OuterClass {public InnerClass getInnerClass(final int num,String str2){return new InnerClass(){int number = num + 3;public int getNumber(){return number;}}; /* 注意:分號不能省 */}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
這里我們就需要看清幾個地方:
? ? ? ?1、?匿名內部類是沒有訪問修飾符的。
? ? ? ?2、?new 匿名內部類,這個類首先是要存在的。如果我們將那個InnerClass接口注釋掉,就會出現編譯出錯。
? ? ? ?3、?注意getInnerClass()方法的形參,第一個形參是用final修飾的,而第二個卻沒有。同時我們也發現第二個形參在匿名內部類中沒有使用過,所以當所在方法的形參需要被匿名內部類使用,那么這個形參就必須為final。
? ? ? ?4、?匿名內部類是沒有構造方法的。因為它連名字都沒有何來構造方法。
?
? ? ? ?上面第一個形參定義成final的,而第二個卻沒有,那么形參為什么要定義為final呢?在網上找到本人比較如同的解釋:
? ? ? ?這是一個編譯器設計的問題,如果你了解java的編譯原理的話很容易理解。 ?
? ? ? ?首先,內部類被編譯的時候會生成一個單獨的內部類的.class文件,這個文件并不與外部類在同一class文件中。 ?
? ? ? ?當外部類傳的參數被內部類調用時,從java程序的角度來看是直接的調用例如:?
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();
}
? ? ? ?從代碼來看好像是那個內部類直接調用的a參數和b參數,但是實際上不是,在java編譯器編譯以后實際的操作代碼是:
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); }
}
? ? ? ?從以上代碼看來,內部類并不是直接調用方法傳進來的參數,而是內部類將傳進來的參數通過自己的構造器備份到了自己的內部,自己內部的方法調用的實際是自己的屬性而不是外部類方法的參數。
? ? ? ?這樣理解就很容易得出為什么要用final了,因為兩者從外表看起來是同一個東西,實際上卻不是這樣,如果內部類改掉了這些參數的值也不可能影響到原參數,然而這樣卻失去了參數的一致性,因為從編程人員的角度來看他們是同一個東西,如果編程人員在程序設計的時候在內部類中改掉參數的值,但是外部調用的時候又發現值其實沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設計人員把內部類能夠使用的參數設定為必須是final來規避這種莫名其妙錯誤的存在。(簡單理解就是,拷貝引用,為了避免引用值發生改變,例如被外部類的方法修改等,而導致內部類得到的值不一致,于是用final來讓該引用不可改變)
? ? ? ?因為匿名內部類,沒名字,是用默認的構造函數的,無參數的,那如果需要參數呢?則需要該類有帶參數的構造函數:
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,由于它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,所以不必定義為final。
? ? ? ?而匿名內部類通過實例初始化,可以達到類似構造器的效果:
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; // 實例初始化 { if (city.equals("gz")) { province = "gd"; }else { province = ""; } } public String getName() { return nameStr; } public String getProvince() { return province; } }; }
} interface Inner { String getName(); String getProvince();
}
?
七、靜態內部類
?
? ? ? ?Static可以修飾成員變量、方法、代碼塊,其他它還可以修飾內部類,使用static修飾的內部類我們稱之為靜態內部類,不過我們更喜歡稱之為嵌套內部類。靜態內部類與非靜態內部類之間存在一個最大的區別,我們知道非靜態內部類在編譯完成之后會隱含地保存著一個引用,該引用是指向創建它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:
? ? ? ?1、?它的創建是不需要依賴于外圍類的。
? ? ? ?2、?它不能使用任何外圍類的非static成員變量和方法。
public class OuterClass {private String sex;public static String name = "chenssy";/***靜態內部類*/static class InnerClass1{/* 在靜態內部類中可以存在靜態成員 */public static String _name1 = "chenssy_static";public void display(){/* * 靜態內部類只能訪問外圍類的靜態成員變量和方法* 不能訪問外圍類的非靜態成員變量和方法*/System.out.println("OutClass name :" + name);}}/*** 非靜態內部類*/class InnerClass2{/* 非靜態內部類中不能存在靜態成員 */public String _name2 = "chenssy_inner";/* 非靜態內部類中可以調用外圍類的任何成員,不管是靜態的還是非靜態的 */public void display(){System.out.println("OuterClass name:" + name);}}/*** @desc 外圍類方法* @author chenssy* @data 2013-10-25* @return void*/public void display(){/* 外圍類訪問靜態內部類:內部類. */System.out.println(InnerClass1._name1);/* 靜態內部類 可以直接創建實例不需要依賴于外圍類 */new InnerClass1().display();/* 非靜態內部的創建需要依賴于外圍類 */OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();/* 方位非靜態內部類的成員需要使用非靜態內部類的實例 */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
? ? ? ?上面這個例子充分展現了靜態內部類和非靜態內部類的區別。
?
?