java內部類是從JDK1.1開始出現的,因此,很多人都不陌生,但是又會覺得不熟悉。原因是平時編寫代碼時可能用到的場景不多,用得最多的是在有事件監聽的情況下。所以,這裡將從四個方面做一個簡單的總結: 一.內部類基礎 在Java中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。廣 ...
java內部類是從JDK1.1開始出現的,因此,很多人都不陌生,但是又會覺得不熟悉。原因是平時編寫代碼時可能用到的場景不多,用得最多的是在有事件監聽的情況下。所以,這裡將從四個方面做一個簡單的總結:
一.內部類基礎
在Java中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、局部內部類、匿名內部類和靜態內部類。下麵就先來簡單瞭解一下這四種內部類的用法。
1.成員內部類
成員內部類是最普通的內部類,它的定義為位於另一個類的內部,這裡總結它的一些特性:
- 成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)
- 當成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即預設情況下訪問的是成員內部類的成員
- 在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問
- 成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象
- 成員內部類中不能定義任何靜態成員變數或靜態成員方法,但可以有靜態常量。
1 public class InnerClassDemo { 2 public static void main(String[] args) { 3 4 //必須先創建一個外部類對象,才能創建一個成員內部類的對象 5 Outer outer = new Outer("Liming", 20); 6 Outer.Inner inner = outer.new Inner(); 7 inner.show(); 8 } 9 10 } 11 12 class Outer{ 13 14 private static String name; 15 private int age; 16 private int score = 100; 17 18 public Outer(String name, int age) { 19 this.name = name; 20 this.age = age; 21 22 //外部類訪問內部類的成員,必須先創建一個成員內部類的對象 23 Inner inner = new Inner(); 24 System.out.println(inner.score);//80 25 } 26 27 class Inner{ 28 29 int score = 80; 30 31 void show(){ 32 //內部類能夠訪問外部類的靜態和私有成員 33 System.out.println(name);//Liming 34 System.out.println(age);//20 35 36 System.out.println(score);//80 37 /* 38 * 發生隱藏現象,訪問的是內部類的score屬性,若要訪問外部類的score屬性,則需要這樣的格式: 39 * 外部類.this.成員變數 40 * 外部類.this.成員方法 41 */ 42 System.out.println(Outer.this.score);//100 43 44 } 45 } 46 }
代碼示例:
1 public class InnerClassDemo { 2 public static void main(String[] args) { 3 4 //必須先創建一個外部類對象,才能創建一個成員內部類的對象 5 Outer outer = new Outer("Liming", 20); 6 Outer.Inner inner = outer.new Inner(); 7 inner.show(); 8 } 9 } 10 11 class Outer{ 12 private static String name; 13 private int age; 14 private int score = 100; 15 16 public Outer(String name, int age) { 17 this.name = name; 18 this.age = age; 19 20 //外部類訪問內部類的成員,必須先創建一個成員內部類的對象 21 Inner inner = new Inner(); 22 System.out.println(inner.score);//80 23 } 24 25 /* 26 * 內部類也可以有一些訪問控制修飾符,如private,public,protected 27 * 此處採用預設訪問許可權,只能在同一個包中訪問 28 */ 29 class Inner{ 30 int score = 80; //static int score = 80; 直接報錯,編譯不通過 31 void show(){ 32 //內部類能夠訪問外部類的靜態和私有成員 33 System.out.println(name);//Liming 34 System.out.println(age);//20 35 36 System.out.println(score);//80 37 /* 38 * 此處發生隱藏現象,訪問的是內部類的score屬性,若要訪問外部類的score屬性,則需: 39 * 外部類.this.成員變數 40 * 外部類.this.成員方法 41 */ 42 System.out.println(Outer.this.score);//100 43 44 } 45 } 46 }
註意:這段代碼的執行順序是:調用Outer構造器創建外部類對象 > 創建內部類對象 > 執行show方法 。
2.靜態內部類
靜態內部類也是定義在另一個類裡面的類,只不過在類的前面多了一個關鍵字static。
- 靜態內部類是不需要依賴於外部類的,即是在沒有創建外部類對象的情況下也可以創建靜態內部類的對象
- 靜態內部類只能訪問外部類的靜態成員,而不能訪問外部類的非靜態成員
代碼示例:
1 public class InnerClassDemo { 2 public static void main(String[] args) { 3 4 //直接創建靜態內部類的對象,不依賴於外部類 5 Inner inner = new Inner(); 6 inner.show(); 7 } 8 } 9 10 class Outer{ 11 public static String name = "Liming"; 12 public int age = 20; 13 14 static class Inner{ 15 void show(){ 16 //靜態內部類只能訪問外部類的靜態成員而不能訪問非靜態成員 17 System.out.println(name);//Liming 18 // System.out.println(age); 直接報錯,編譯不通過 19 } 20 } 21 22 }
3.局部內部類
局部內部類是定義在一個方法或者一個作用域裡面的類,它和成員內部類的區別在於局部內部類的訪問僅限於方法內或者該作用域內。
代碼示例:
1 class People{ 2 public People() { 3 4 } 5 } 6 7 class Man{ 8 public Man(){ 9 10 } 11 12 public People getWoman(){ 13 class Woman extends People{ //局部內部類 14 int age =0; 15 } 16 return new Woman(); 17 } 18 }
註意:局部內部類就像是方法裡面的一個局部變數一樣,是不能有public、protected、private以及static修飾符的。
4.匿名內部類
匿名內部類應該是平時我們編寫代碼時用得最多的,在編寫事件監聽的代碼時使用匿名內部類不但方便,而且使代碼更加容易維護。
- 匿名內部類不能有訪問修飾符和static修飾符
- 匿名內部類是唯一一種沒有構造器的類,因為連類名都沒有何來構造器
- 匿名內部類的使用範圍非常有限,大部分匿名內部類用於介面回調,“new 匿名內部類”,這個類首先是要存在的。
代碼示例:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 scan_bt.setOnClickListener(new OnClickListener() { 2 3 @Override 4 public void onClick(View v) { 5 // TODO Auto-generated method stub 6 7 } 8 }); 9 10 history_bt.setOnClickListener(new OnClickListener() { 11 12 @Override 13 public void onClick(View v) { 14 // TODO Auto-generated method stub 15 16 } 17 });View Code
二.深入理解Java內部類
1.靜態內部類與非靜態內部類
- 靜態內部類的創建不依賴於外部類,並且可以定義靜態成員
- 非靜態內部類依賴於外部類的創建而創建,並且不能定義任何的靜態成員
解析:靜態內部類不依賴於任何外部類,所以可以有自己的靜態成員;而非靜態內部類必須通過外部類創建,它的存在嚴格依賴於外部類,所以不能有自己的靜態成員。
- 靜態內部類只能訪問外部類的靜態成員,而不能訪問任何非靜態成員
解析:靜態內部類不依賴於外部類,所以可在未創建外部類對象的情況下創建靜態內部類的實例對象,而沒有外部類對象就不能訪問外部類的非靜態成員。
2.成員內部類可以無條件訪問外部類的成員
之前,我們已經討論過了成員內部類可以無條件訪問外部類的成員,那具體究竟是如何實現的呢?下麵通過反編譯位元組碼文件看看究竟。
- 事實上,編譯器在進行編譯的時候,會將內部類單獨編譯成一個位元組碼文件,命名方式為:外部類$內部類.class 或 外部類$X.class(X為一個整數)。
通過反編譯工具,將內部類的位元組碼文件進行反編譯,得到:
1 E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner 2 Compiled from "Outter.java" 3 public class com.cxh.test2.Outter$Inner extends java.lang.Object 4 SourceFile: "Outter.java" 5 InnerClass: 6 #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes 7 t2/Outter 8 minor version: 0 9 major version: 50 10 Constant pool: 11 const #1 = class #2; // com/cxh/test2/Outter$Inner 12 const #2 = Asciz com/cxh/test2/Outter$Inner; 13 const #3 = class #4; // java/lang/Object 14 const #4 = Asciz java/lang/Object; 15 const #5 = Asciz this$0; 16 const #6 = Asciz Lcom/cxh/test2/Outter;; 17 const #7 = Asciz <init>; 18 const #8 = Asciz (Lcom/cxh/test2/Outter;)V; 19 const #9 = Asciz Code; 20 const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t 21 est2/Outter; 22 const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter; 23 const #12 = Method #3.#13; // java/lang/Object."<init>":()V 24 const #13 = NameAndType #7:#14;// "<init>":()V 25 const #14 = Asciz ()V; 26 const #15 = Asciz LineNumberTable; 27 const #16 = Asciz LocalVariableTable; 28 const #17 = Asciz this; 29 const #18 = Asciz Lcom/cxh/test2/Outter$Inner;; 30 const #19 = Asciz SourceFile; 31 const #20 = Asciz Outter.java; 32 const #21 = Asciz InnerClasses; 33 const #22 = class #23; // com/cxh/test2/Outter 34 const #23 = Asciz com/cxh/test2/Outter; 35 const #24 = Asciz Inner; 36 37 { 38 final com.cxh.test2.Outter this$0; 39 40 public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter); 41 Code: 42 Stack=2, Locals=2, Args_size=2 43 0: aload_0 44 1: aload_1 45 2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter; 46 5: aload_0 47 6: invokespecial #12; //Method java/lang/Object."<init>":()V 48 9: return 49 LineNumberTable: 50 line 16: 0 51 line 18: 9 52 53 LocalVariableTable: 54 Start Length Slot Name Signature 55 0 10 0 this Lcom/cxh/test2/Outter$Inner; 56 57 58 }
第11行~35行是常量池的內容,這裡需要註意的是第38行的內容:
final com.cxh.test2.Outter this$0;
這行是一個指向外部類對象的指針,看到這裡想必大家豁然開朗了。也就是說編譯器會預設為成員內部類添加了一個指向外部類對象的引用,那麼這個引用是如何賦初值的呢?下麵接著看內部類的構造器:
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
從這裡可以看出,雖然我們在定義的內部類的構造器是無參構造器,編譯器還是會預設添加一個參數,該參數的類型為指向外部類對象的一個引用,所以成員內部類中的Outter this&0 指針便指向了外部類對象,因此可以在成員內部類中隨意訪問外部類的成員。從這裡也間接說明瞭成員內部類是依賴於外部類的,如果沒有創建外部類的對象,則無法對Outter this&0引用進行初始化賦值,也就無法創建成員內部類的對象了。
3.局部內部類和匿名內部類只能訪問局部final變數
關於這個問題,我們先來看一段代碼:
1 /* 2 * 局部內部類和匿名內部類只能訪問局部final變數 3 */ 4 public class InnerClass { 5 6 public static void main(String[] args) { 7 test(11); 8 } 9 10 public static void test(final int a ){ 11 final double b = 11.234; 12 //定義一個匿名內部類 13 new Thread(){ 14 public void run() { 15 System.out.println(a); 16 System.out.println(b); 17 } 18 19 }.start(); 20 } 21 }
編譯後生成了兩個文件,即匿名內部類單獨生成一個文件:InnerClass$1.class
上段代碼中,如果把變數a和b前面的任一個final去掉,這段代碼都編譯不過。我們可以考慮這樣一個問題: 當test方法執行完畢之後,變數a的生命周期就結束了,而此時Thread對象的生命周期很可能還沒有結束,那麼在Thread的run方法中繼續訪問變數a就變成不可能了,但是又要實現這樣的效果,怎麼辦呢?Java採用“複製”的方法來解決這個問題。
詳細介紹請參看:http://www.cnblogs.com/dolphin0520/p/3811445.html
4.靜態內部類特殊的地方
- 靜態內部類是不依賴於外部類的,也就說可以在不創建外部類對象的情況下創建內部類的對象。
- 靜態內部類是不持有指向外部類對象的引用的,可以反編譯靜態內部類的class文件,發現是沒有Outter this&0引用的。
三.內部類的作用和使用場景
關於內部類的作用和使用場景,這裡總結了以下兩點:
- 每個內部類都能獨立的繼承一個(介面的)實現,所以無論外部類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。所以,內部類使得多重繼承的解決方案變得完整
- 方便將存在一定邏輯關係的類組織在一起,又可以對外界隱藏
- 其中,匿名內部類常用於事件監聽和多線程程式
全文參考自:http://www.cnblogs.com/dolphin0520/p/3811445.html