深入理解java巢狀類和內部類、匿名類

leixiongbo發表於2016-08-09

深入理解java巢狀類和內部類、匿名類


一、什麼是巢狀類及內部類

  可以在一個類的內部定義另一個類,這種類稱為巢狀類(nested classes),它有兩種型別:靜態巢狀類和非靜態巢狀類。靜態巢狀類使用很少,最重要的是非靜態巢狀類,也即是被稱作為內部類(inner)。巢狀類從JDK1.1開始引入。其中inner類又可分為三種:
  其一、在一個類(外部類)中直接定義的內部類;
  其二、在一個方法(外部類的方法)中定義的內部類;
  其三、匿名內部類。

  下面,我將說明這幾種巢狀類的使用及注意事項。

二、靜態巢狀類

  如下所示程式碼為定義一個靜態巢狀類,
  1. public class StaticTest {   
  2.         private static String name = "javaJohn";           
  3.   private String id = "X001";  
  4.   static class Person{  
  5.     private String address = "swjtu,chenDu,China";  
  6.     public String mail = "josserchai@yahoo.com";//內部類公有成員  
  7.     public void display(){  
  8.       //System.out.println(id);//不能直接訪問外部類的非靜態成員  
  9.       System.out.println(name);//只能直接訪問外部類的靜態成員  
  10.       System.out.println("Inner "+address);//訪問本內部類成員。  
  11.     }  
  12.   }  
  13.   
  14.   public void printInfo(){  
  15.     Person person = new Person();  
  16.     person.display();  
  17.     //System.out.println(mail);//不可訪問  
  18.     //System.out.println(address);//不可訪問  
  19.     System.out.println(person.address);//可以訪問內部類的私有成員  
  20.     System.out.println(person.mail);//可以訪問內部類的公有成員  
  21.   
  22.   }  
  23.   public static void main(String[] args) {  
  24.   StaticTest staticTest = new StaticTest();  
  25.   staticTest.printInfo();  
  26. }  
  27. }  


  在靜態巢狀類內部,不能訪問外部類的非靜態成員,這是由Java語法中"靜態方法不能直接訪問非靜態成員"所限定。若想訪問外部類的變數,必須通過其它方法解決,由於這個原因,靜態巢狀類使用很少。注意,外部類訪問內部類的的成員有些特別,不能直接訪問,但可以通過內部類來訪問,這是因為靜態巢狀內的所有成員和方法預設為靜態的了。同時注意,內部靜態類Person只在類StaticTest 範圍內可見,若在其它類中引用或初始化,均是錯誤的。

三、在外部類中定義內部類

  如下所示程式碼為在外部類中定義兩個內部類及它們的呼叫關係:
  
  1. public class Outer {   
  2.            int outer_x = 100;  
  3.     class Inner{  
  4.       public int y = 10;  
  5.       private int z = 9;  
  6.       int m = 5;  
  7.       public void display(){  
  8.         System.out.println("display outer_x:"+ outer_x);  
  9.       }  
  10.       private void display2(){  
  11.         System.out.println("display outer_x:"+ outer_x);  
  12.       }  
  13.     }  
  14.     void test(){  
  15.       Inner inner = new Inner();  
  16.       inner.display();  
  17.       inner.display2();  
  18.       //System.out.println("Inner y:" + y);//不能訪問內部內變數  
  19.       System.out.println("Inner y:" + inner.y);//可以訪問  
  20.       System.out.println("Inner z:" + inner.z);//可以訪問  
  21.       System.out.println("Inner m:" + inner.m);//可以訪問  
  22.       InnerTwo innerTwo = new InnerTwo();  
  23.       innerTwo.show();  
  24.     }  
  25.     class InnerTwo{  
  26.       Inner innerx = new Inner();  
  27.       public void show(){  
  28.         //System.out.println(y);//不可訪問Innter的y成員  
  29.         //System.out.println(Inner.y);//不可直接訪問Inner的任何成員和方法  
  30.         innerx.display();//可以訪問  
  31.         innerx.display2();//可以訪問  
  32.         System.out.println(innerx.y);//可以訪問  
  33.         System.out.println(innerx.z);//可以訪問  
  34.         System.out.println(innerx.m);//可以訪問  
  35.       }  
  36.     }  
  37.     public static void main(String args[]){  
  38.       Outer outer = new Outer();  
  39.       outer.test();  
  40.     }  
  41.   }  


  以上程式碼需要說明有,對於內部類,通常在定義類的class關鍵字前不加public 或 private等限制符,若加了沒有任何影響,同時好像這些限定符對內部類的變數和方法也沒有影響(?)。另外,就是要注意,內部類Inner及InnterTwo只在類Outer的作用域內是可知的,如果類Outer外的任何程式碼嘗試初始化類Inner或使用它,編譯就不會通過。同時,內部類的變數成員只在內部內內部可見,若外部類或同層次的內部類需要訪問,需採用示例程式中的方法,不可直接訪問內部類的變數。


四、在外部類中定義內部類

  匿名類就是沒有名字的內部類,是內部類的一種特殊情況。?????????  這句話對嗎???

前端時間在寫.net專案中,一直錯將.cs裡的兩個class當作內部類,原來是一個檔案裡的兩個類而已,這讓我想起了Java中的內部類,比較內部類,那麼還有兩個類,那就是匿名類和匿名內部類。今天我想就Java中的這三種類進行個比較。

我們知道在Java語言規範中可以做很多事,例如一個類或一個介面中可以宣告一個類或介面,在一個方法中可以宣告一個類,類與介面宣告可以巢狀任意深度等。

 

匿名類:

      1、new <類或介面><類的主體>,匿名類的宣告是在編譯時進行的,例項化是在執行時進行的,所以在for迴圈中一個new語句會建立相同匿名類的幾個例項,而不是建立幾個不同匿名類的一個例項。

      2、如果要執行的物件需要一個物件,但卻不值得建立全新的物件(可能是因為該物件只在一個方法內部使用),在這個時候使用匿名類就會非常合適,所以說,匿名類一般會在swing程式中快速建立事件處理程式。

Java程式碼  收藏程式碼
  1. firstButton.addActionListener(new ActionListener() {  
  2.         @Override  
  3.         public void actionPerformed(ActionEvent e) {  
  4.             getTxtValue().setText("第一個按鈕觸發的事件!");  
  5.         }  
  6.     });   

       3、從技術上說,匿名類可以被看作非靜態的內部類,所以他們具有方法內部宣告的非靜態內部類相同的許可權和限制。

 

 

內部類:

內部類顧名思義就是在一個類的內部還有一個類

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo {  
  9.     public static void main(String[] args) {  
  10.         new Outer().fun();  
  11.     }  
  12. }  
  13.   
  14. class Outer {  
  15.   
  16.     private String name = "Hello 內部類";  
  17.   
  18.     class Inner {  
  19.         public void print() {  
  20.             System.out.println("name = " + name);  
  21.   
  22.         }  
  23.     };  
  24.   
  25.     public void fun() {  
  26.         new Inner().print();  
  27.     }  
  28. }  

 

 內部類生成的.class檔名為:Outer$Inner.class,從上面的結構發現內部類的的缺點是“結構非常的混亂”。

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo02 {  
  9.     public static void main(String[] args) {  
  10.         new Outer02().fun();  
  11.     }  
  12. }  
  13.   
  14. class Outer02 {  
  15.   
  16.     private String name = "Hello 內部類";  
  17.   
  18.     public void fun() {  
  19.         new Inner02(this).print();  
  20.     }  
  21.   
  22.     public String getName() {  
  23.   
  24.         return this.name;  
  25.     }  
  26. };  
  27.   
  28. class Inner02 {  
  29.     private Outer02 out;  
  30.   
  31.     public Inner02(Outer02 out) {  
  32.         this.out = out;  
  33.     }  
  34.   
  35.     public void print() {  
  36.         System.out.println("name = " + this.out.getName());  
  37.   
  38.     }  
  39. };  

 從上可以看出內部類的優點是“可以方便的訪問外部類中的私有成員”;

如果要在外部直接使用內部類的例項化物件:

      外部類.內部類 內部類物件 = 外部類例項.new 內部類例項();

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang  2012-1-11 
  5.  * 
  6.  *  Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo03 {  
  9.     public static void main(String[] args) {  
  10.         Outer03 out = new Outer03();//外部類例項  
  11.         Outer03.Inner inner = out.new Inner();//例項化內部類物件  
  12.         inner.print();  
  13.     }  
  14. }  
  15. class Outer03{  
  16.     private String name = "Hello 內部類";  
  17.     class Inner {  
  18.         public void print() {  
  19.             System.out.println("name = " + name);  
  20.         }  
  21.     }  
  22. }  

 

 一個內部類如果使用static關鍵字宣告的話,則此內部類就將成為外部類,可以直接通過外部類.內部類的形式訪問

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo04 {  
  9.     public static void main(String[] args) {  
  10.         Outer04.Inner inner = new Outer04.Inner();// 例項化內部類物件  
  11.         inner.print();  
  12.     }  
  13. }  
  14.   
  15. class Outer04 {  
  16.     private static String name = "Hello 內部類";  
  17.   
  18.     static class Inner {  
  19.         public void print() {  
  20.             System.out.println("name = " + name);  
  21.         }  
  22.     }  
  23. }  

 

 內部類可以在任意的地方使用,例如方法中宣告

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo05 {  
  9.     public static void main(String[] args) {  
  10.         new Outer05().fun();  
  11.     }  
  12. }  
  13.   
  14. class Outer05 {  
  15.     private static String name = "Hello 內部類";  
  16.   
  17.     public void fun() {  
  18.         class Inner {  
  19.             public void print() {  
  20.                 System.out.println("name = " + name);  
  21.             }  
  22.         }  
  23.         new Inner().print();  
  24.     }  
  25. }  

 

 在方法中定義的內部類,可以直接訪問外部類中的各個成員,但是如果要訪問方法中的引數,則需要在引數上加上final關鍵字宣告;

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class InnerClassDemo06 {  
  9.     public static void main(String[] args) {  
  10.         new Outer06().fun(20);  
  11.     }  
  12. }  
  13.   
  14. class Outer06 {  
  15.     private static String name = "Hello 內部類";  
  16.   
  17.     public void fun(final int temp) {  
  18.         class Inner {  
  19.             public void print() {  
  20.                 System.out.println("temp = " + temp);  
  21.                 System.out.println("name = " + name);  
  22.             }  
  23.         }  
  24.         new Inner().print();  
  25.     }  
  26. }  

 匿名類與內部的聯絡與區別:

按所在位置可以分為兩大類:

      1、在類的方法中

                     特點:

                              a、可以訪問宿主類的所有元素 ;

                              b、儲存宿主類物件的引用,建立物件時必須有宿主類物件;

                              c、 不能有靜態資料;

繼續劃分:

                             A、本地內部類;

                             B、匿名內部類

 兩者的區別在於本地內部類有構造方法,而匿名內部類只能例項初始化;

      2、在類或介面作用域中;

                     繼續劃分:

                            A、普通內部類

                            B、靜態內部類

 

 

匿名內部類:

匿名內部類是在抽象類和介面的基礎之上發展起來的。

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class NoNameClass01 {  
  9.     public static void main(String[] args) {  
  10.         new X().fun2();  
  11.     }  
  12. }  
  13.   
  14. interface A {  
  15.     public void fun();  
  16. }  
  17.   
  18. class B implements A {  
  19.     public void fun() {  
  20.   
  21.         System.out.println("Hello 準備匿名內部類");  
  22.     }  
  23. }  
  24.   
  25. class X {  
  26.     public void fun1(A a) {  
  27.         a.fun();  
  28.     }  
  29.   
  30.     public void fun2() {  
  31.         this.fun1(new B());  
  32.     }  
  33. }  

 通過上面的Demo,如果現在假設B類只使用一次,那麼還有必要將其定義成一個單獨的類麼?

 呵呵,此時就可以使用匿名內部類:

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class NoNameClass02 {  
  9.   
  10.     public static void main(String[] args) {  
  11.         new XX().fun2();  
  12.     }  
  13. }  
  14.   
  15. interface AA {  
  16.     public void fun();  
  17. }  
  18.   
  19. class XX {  
  20.     public void fun1(AA a) {  
  21.         a.fun();  
  22.     }  
  23.     public void fun2() {  
  24.         this.fun1(new AA() {  
  25.             public void fun() {  
  26.                 System.out.println("Hello 準備匿名內部類");  
  27.             }  
  28.         });  
  29.     }  
  30. }  

 其實在真正的專案開發中匿名內部類使用的非常之少,一般在Java的圖形介面和現在的Android中使用的比較多點。

 

 最後給一個內部類實現的簡單連結串列:

Java程式碼  收藏程式碼
  1. package com.iflytek.innerclass;  
  2.   
  3. /** 
  4.  * @author xudongwang 2012-1-11 
  5.  *  
  6.  *         Email:xdwangiflytek@gmail.com 
  7.  */  
  8. public class LinkDemo {  
  9.     public static void main(String args[]) {  
  10.         Link link = new Link();  
  11.         link.add("A");  
  12.         link.add("B");  
  13.         link.add("C");  
  14.         link.add("D");  
  15.         link.add("E");  
  16.         link.print();  
  17.     }  
  18. };  
  19.   
  20. class Link {  
  21.     class Node {  
  22.         private String name;  
  23.         private Node next; // 單向連結串列,每個節點指向下一個節點  
  24.   
  25.         public Node(String name) {  
  26.             this.name = name; // 通過構造方法為name屬性賦值  
  27.         }  
  28.   
  29.         public void addNode(Node newNode) { // 增加節點  
  30.             if (this.next == null) {  
  31.                 this.next = newNode; // 儲存節點  
  32.             } else {  
  33.                 this.next.addNode(newNode); // 繼續向下查詢  
  34.             }  
  35.         }  
  36.   
  37.         public void printNode() { // 輸出節點  
  38.             System.out.println(this.name);  
  39.             if (this.next != null) { // 此節點之後還存在其他的節點  
  40.                 this.next.printNode();  
  41.             }  
  42.         }  
  43.     };  
  44.   
  45.     private Node root; // 連結串列的頭  
  46.   
  47.     public void add(String name) { // 增加節點  
  48.         Node newNode = new Node(name); // 定義一個新的節點  
  49.         /* 
  50.          * 如果是第一個節點,則肯定是根節點, 如果是第二個節點,則肯定放在根節點next中 如果是第三個節點,則肯定放在第二個節點的next中 
  51.          */  
  52.         if (this.root == null) {  
  53.             this.root = newNode; // 將第一個節點設定成根節點  
  54.         } else {  
  55.             // 肯定要放在最後一個節點之後  
  56.             // 通過節點.next來不斷的判斷  
  57.             this.root.addNode(newNode);  
  58.         }  
  59.     }  
  60.   
  61.     public void print() {  
  62.         if (this.root != null) { // 如果根節點為空了,則沒有任何內容  
  63.             this.root.printNode();  
  64.         }  
  65.     }  
  66. };  

相關文章