轉載出處:http://www.reibang.com/p/e385ce41ca5b
概念
Java類中不僅可以定義變量和方法荚孵,還可以定義類纬朝,這樣定義在類內部的類就被稱為內部類。根據定義的方式不同判没,內部類分為靜態(tài)內部類隅茎,成員內部類,局部內部類俏竞,匿名內部類四種。
Java為什么要引入內部類這個概念呢?原因在于跃捣,內部類定義在類的內部,可以方便訪問外部類的變量和方法酣胀,并且和其它類進行隔離闻镶。
Thinking in java中對內部類的描述
1丸升、內部類可以用多個實例,每個實例都有自己的狀態(tài)信息墩剖,并且與其他外圍對象的信息相互獨立夷狰。
2、在單個外圍類中爷绘,可以讓多個內部類以不同的方式實現同一個接口进倍,或者繼承同一個類。
3毙籽、創(chuàng)建內部類對象的時刻并不依賴于外圍類對象的創(chuàng)建毡庆。
4、內部類并沒有令人迷惑的“is-a”關系毅否,他就是一個獨立的實體蝇刀。
5、內部類提供了更好的封裝捆探,除了該外圍類,其他類都不能訪問曾雕。
一 靜態(tài)內部類
定義在類內部的靜態(tài)類剖张,就是靜態(tài)內部類揩环。
1.語法
定義一個靜態(tài)內部類
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
Inner就是靜態(tài)內部類。靜態(tài)內部類可以訪問外部類所有的靜態(tài)變量和方法顾犹,即使是private的也一樣褒墨。靜態(tài)內部類和一般類一致,可以定義靜態(tài)變量柬唯、方法圃庭,構造方法等。
其它類使用靜態(tài)內部類需要使用“外部類.靜態(tài)內部類”方式拘央,如下所示:
Out.Inner inner = new Out.Inner();
inner.print();
2.應用場景
Effictive Java中的builder模式就是利用的靜態(tài)內部類來建立外部類實例
Java集合類HashMap內部就有一個靜態(tài)內部類Entry灰伟。Entry是HashMap存放元素的抽象儒旬,HashMap內部維護Entry數組用了存放元素,但是Entry對使用者是透明的挡爵。像這種和外部類關系密切的甚垦,且不依賴外部類實例的涣雕,都可以使用靜態(tài)內部類挣郭。
二 成員內部類
定義在類內部的非靜態(tài)類疗韵,就是成員內部類。
1.語法
定義一個成員內部類:
public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
public Out getOut(){
return Out.this;
}
}
/*推薦使用getxxx()來獲取成員內部類,尤其是該內部類的構造函數無參數時 */
public InnerClass getInnerClass(){
return new Inner();
}
}
成員內部類可以訪問外部類所有的變量和方法彩库,包括靜態(tài)和實例,私有和非私有骇钦。和靜態(tài)內部類不同的是,每一個成員內部類的實例都依賴一個外部類的實例窥翩。其它類使用內部類必須要先創(chuàng)建一個外部類的實例寇蚊。如下所示:
Out out = new Out();
Out.Inner inner = out.new Inner(); //out.getInnerClass();
inner.print();
在成員內部類中要注意兩點
第一:成員內部類中不能存在任何static的變量和方法棍好;
第二:成員內部類是依附于外圍類的,所以只有先創(chuàng)建了外圍類才能夠創(chuàng)建內部類扒怖。
分析:非static的內部類业稼,在外部類加載的時候,并不會加載它俯邓,所以它里面不能有靜態(tài)變量或者靜態(tài)方法。
1熔号、static類型的屬性和方法,在類加載的時候就會存在于內存中川慌。
2、要使用某個類的static屬性或者方法兑燥,那么這個類必須要加載到jvm中琴拧。
基于以上兩點,可以看出挣饥,如果一個非static的內部類如果具有static的屬性或者方法沛膳,那么就會出現一種情況:內部類未加載,但是卻試圖在內存中創(chuàng)建static的屬性和方法短荐,這當然是錯誤的。原因:類還不存在叹哭,但卻希望操作它的屬性和方法。
三 局部內部類
定義在方法或作用域中的類糠排,就是局部內部類乳讥。
1.語法
定義一個局部內部類
public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
//如果想在外部得到該內部類廓俭,必須繼承或者實現一個現有的類或接口,并且返回new Inner2();
class Inner2 extends People{
@Override
public String readName() {
return null;
}
public People getInner2(){
return new Inner2();
}
}
}
public static void testStatic(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(a);
//定義在靜態(tài)方法中的局部類不可以訪問外部類的實例變量
//System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
}
}
2.注意
1.定義在實例方法中的局部類可以訪問外部類的所有變量和方法
2.定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法
3.局部類還可以訪問方法的參數和方法中的局部變量汹忠,這些參數和變量必須要聲明為final的(如果參數傳入進來而沒有被內部類使用則可以不用聲明為final的)宽菜。
四 匿名內部類
1.寫法
new 父類構造器(參數列表)|實現接口()
{
//匿名內部類的類體部分
}
在這里我們看到使用匿名內部類我們必須要繼承一個父類或者實現一個接口铅乡,當然也僅能只繼承一個父類或者實現一個接口烈菌。同時它也是沒有class關鍵字花履,這是因為匿名內部類是直接使用new來生成一個對象的引用诡壁。當然這個引用是隱式的荠割。
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird){
System.out.println(bird.getName() + "能夠飛 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
});
}
}
------------------
Output:
大雁能夠飛 10000米
在Test類中蔑鹦,test()方法接受一個Bird類型的參數,同時我們知道一個抽象類是沒有辦法直接new的铺纽,我們必須要先有實現類才能new出來它的實現類實例火鼻。所以在mian方法中直接使用匿名內部類來創(chuàng)建一個Bird實例魁索。
由于匿名內部類不能是抽象類,所以它必須要實現它的抽象父類或者接口里面所有的抽象方法粗蔚。
對于這段匿名內部類代碼其實是可以拆分為如下形式:
public class WildGoose extends Bird{
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
}
WildGoose wildGoose = new WildGoose();
test.test(wildGoose);
例子2
public class Super {
public void test1(){
System.out.println("superClass");
}
}
Super s = new Super(){
@Override
public void test1() {
System.out.println("sonClass");
}
};
s.test1();
在這里系統(tǒng)會創(chuàng)建一個繼承自Bird類的匿名類的對象呢诬,該對象轉型為對Bird類型的引用短绸。
對于匿名內部類的使用它是存在一個缺陷的当辐,就是它僅能被使用一次鲤看,創(chuàng)建匿名內部類時它會立即創(chuàng)建一個該類的實例,該類的定義會立即消失找筝,所以匿名內部類是不能夠被重復使用慷吊。對于上面的實例,如果我們需要對test()方法里面內部類進行多次使用急鳄,建議重新定義類谤民,而不是使用匿名內部類赖临。
2.注意事項
在使用匿名內部類的過程中兢榨,我們需要注意如下幾點:
1顺饮、使用匿名內部類時,我們必須是繼承一個類或者實現一個接口兼雄,但是兩者不可兼得,同時也只能繼承一個類或者實現一個接口块攒。
2囱井、匿名內部類中是不能定義構造函數的趣避。
3、匿名內部類中不能存在任何的靜態(tài)成員變量和靜態(tài)方法程帕。
4、匿名內部類為局部內部類讲逛,所以局部內部類的所有限制同樣對匿名內部類生效妆绞。
5、匿名內部類不能是抽象的括饶,它必須要實現繼承的類或者實現的接口的所有抽象方法
五 final
為何局部內部類(包含匿名內部類)来涨,它所使用到的外部的參數(來自外部方法或者外部類)必須是聲明為final的呢
分析
先定義一個接口:
public interface MyInterface {
void doSomething();
}
然后創(chuàng)建這個接口的匿名子類:
public class TryUsingAnonymousClass {
public void useMyInterface() {
final Integer number = 123;
System.out.println(number);
MyInterface myInterface = new MyInterface() {
@Override
public void doSomething() {
System.out.println(number);
}
};
myInterface.doSomething();
System.out.println(number);
}
}
這個匿名子類會被編譯成一個單獨的類蹦掐,反編譯的結果是這樣的:
class TryUsingAnonymousClass$1
implements MyInterface {
private final TryUsingAnonymousClass this$0;
private final Integer paramInteger;
TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) {
this.this$0 = this$0;
this.paramInteger = paramInteger;
}
public void doSomething() {
System.out.println(this.paramInteger);
}
}
可以看到名為number的局部變量是作為構造方法的參數傳入匿名內部類的(以上代碼經過了手動修改,真實的反編譯結果中有一些不可讀的命名)藤滥。
如果Java允許匿名內部類訪問非final的局部變量的話,那我們就可以在TryUsingAnonymousClass$1中修改paramInteger向图,但是這不會對number的值有影響标沪,因為它們是不同的reference。
這就會造成數據不同步的問題檩赢。
所以,Java為了避免數據不同步的問題贞瞒,做出了局部內部類(包含匿名內部類)只可以訪問final的局部變量的限制军浆。