JAVA基礎——內部類詳解


JAVA內部類詳解

在我的另一篇java三大特性的封裝中講到java內部類的簡單概要,這里將詳細深入了解java內部類的使用和應用。

我們知道內部類可分為以下幾種:

  • 成員內部類
  • 靜態內部類
  • 方法內部類
  • 匿名內部類

這里我們先將以這個分類來詳細了解各個內部類的情況。然后給內部類作出總結。

一、成員內部類

  內部類中最常見的就是成員內部類,也稱為普通內部類。我們來看如下代碼:

  

  運行結果為:

  

  從上面的代碼中我們可以看到,成員內部類的使用方法

  1、 Inner 類定義在 Outer 類的內部相當於 Outer 類的一個成員變量的位置,Inner 類可以使用任意訪問控制符,如 public 、 protected 、 private 等

  2、 Inner 類中定義的 test() 方法可以直接訪問 Outer 類中的數據,而不受訪問控制符的影響,如直接訪問 Outer 類中的私有屬性a

  3、 定義了成員內部類后,必須使用外部類對象來創建內部類對象,而不能直接去 new 一個內部類對象,即:內部類 對象名 = 外部類對象.new 內部類( );

  4、 編譯上面的程序后,會發現產生了兩個 .class 文件

  

  其中,第二個是外部類的 .class 文件,第一個是內部類的 .class 文件,即成員內部類的 .class 文件總是這樣:外部類名$內部類名.class

  另外,友情提示哦:

  1、 外部類是不能直接使用內部類的成員和方法滴。如:

  

  那么外部類如何使用內部類的成員和方法呢??

  答:可先創建內部類的對象,然后通過內部類的對象來訪問其成員變量和方法。

  Java 編譯器在創建內部類對象時,隱式的把其外部類對象的引用也傳了進去並一直保存着。這樣就使得內部類對象始終可以訪問其外部類對象,同時這也是為什么在外部 類作用范圍之外向要創建內部類對象必須先創建其外部類對象的原因。

  2、 如果外部類和內部類具有相同的成員變量或方法,內部類默認訪問自己的成員變量或方法,如果要訪問外部類的成員變量,可以使用 this 關鍵字。如:

  

  運行結果:

二、靜態內部類

  靜態內部類是 static 修飾的內部類,這種內部類的特點是:

  1、 靜態內部類不能直接訪問外部類的非靜態成員,但可以通過 new 外部類().成員 的方式訪問。

  2、 如果外部類的靜態成員與內部類的成員名稱相同,可通過“類名.靜態成員”訪問外部類的靜態成員;如果外部類的靜態成員與內部類的成員名稱不相同,則可通過“成員名”直接調用外部類的靜態成員。

  3、 創建靜態內部類的對象時,不需要外部類的對象,可以直接創建 內部類 對象名= new 內部類();

   

  運行結果 : 

三、方法內部類

  方法內部類就是內部類定義在外部類的方法中,方法內部類只在該方法的內部可見,即只在該方法內可以使用

   

  一定要注意哦:由於方法內部類不能在外部類的方法以外的地方使用,因此方法內部類不能使用訪問控制符和 static 修飾符。

 四、匿名內部類

  匿名類是不能有名稱的類,所以沒辦法引用他們。必須在創建時,作為new語句的一部分來聲明他們。

  但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口。
  這就要采用另一種形式 的new語句,如下所示:

   new <類或接口> <類的主體>

        這種形式的new語句聲明一個 新的匿名類,他對一個給定的類進行擴展,或實現一個給定的接口。他還創建那個類的一個新實例,並把他作為語句的結果而返回。要擴展的類和要實現的接口是 new語句的操作數,后跟匿名類的主體。

        注意匿名類的聲明是在編譯時進行的,實例化在運行時進行。這意味着 for循環中的一個new語句會創建相同匿名類的幾個實例,而不是創建幾個不同匿名類的一個實例。

        從技術上說,匿名類可被視為非靜態的內 部類,所以他們具備和方法內部聲明的非靜態內部類相同的權限和限制。

        假如要執行的任務需要一個對象,但卻不值得創建全新的對象(原因可能 是所需的類過於簡單,或是由於他只在一個方法內部使用),匿名類就顯得很有用。匿名類尤其適合在Swing應用程式中快速創建事件處理程式。以下是一個匿名內部類的實例:

  1、匿名內部類的基本實現:

    

  運行結果: 

  可以看到,我們直接將抽象類Person中的方法在大括號中實現了,這樣便可以省略一個類的書寫,並且,匿名內部類還能用於接口上。

  2、在接口上使用匿名內部類:

  

  運行結果:

  由上面的例子可以看出,只要一個類是抽象的或是一個接口,那么其子類中的方法都可以使用匿名內部類來實現。

  在使用匿名內部類的過程中,我們需要注意如下幾點:

      1、使用匿名內部類時,我們必須是繼承一個類或者實現一個接口,但是兩者不可兼得,同時也只能繼承一個類或者實現一個接口。

      2、匿名內部類中是不能定義構造函數的。

      3、匿名內部類中不能存在任何的靜態成員變量和靜態方法。

      4、匿名內部類為局部內部類(即方法內部類),所以局部內部類的所有限制同樣對匿名內部類生效。

      5、匿名內部類不能是抽象的,它必須要實現繼承的類或者實現的接口的所有抽象方法。

  匿名內部類重點難點

1. 如果是在一個方法的匿名內部類,可以利用這個方法傳進你想要的參數,不過記住,這些參數必須被聲明為 final 。

 使用的形參為何要為final??

 參考博文:http://android.blog.51cto.com/268543/384844  http://blog.csdn.net/chenssy/article/details/13170015

 我們給匿名內部類傳遞參數的時候,若該形參在內部類中需要被使用,那么該形參必須要為final。也就是說:當所在的方法的形參需要被內部類里面使用時,該形參必須為final。

     首先我們知道在內部類編譯成功后,它會產生一個class文件,該class文件與外部類並不是同一class文件,僅僅只保留對外部類的引用。當外部類傳入的參數需要被內部類調用時,從java程序的角度來看是直接被調用:

    public class OuterClass {  
        public void display(final String name,String age){  
            class InnerClass{  
                void display(){  
                    System.out.println(name);  
                }  
            }  
        }  
    }  

  從上面代碼中看好像name參數應該是被內部類直接調用?其實不然,在java編譯之后實際的操作如下:

    public class OuterClass$InnerClass {  
        public InnerClass(String name,String age){  
            this.InnerClass$name = name;  
            this.InnerClass$age = age;  
        }  
          
          
        public void display(){  
            System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );  
        }  
    }  

 所以從上面代碼來看,內部類並不是直接調用方法傳遞的參數,而是利用自身的構造器對傳入的參數進行備份,自己內部方法調用的實際上時自己的屬性而不是外部方法傳遞進來的參數。

 直到這里還沒有解釋為什么是final。在內部類中的屬性和外部方法的參數兩者從外表上看是同一個東西,但實際上卻不是,所以他們兩者是可以任意變化的,也就是說在內部類中我對屬性的改變並不會影響到外部的形參,而然這從程序員的角度來看這是不可行的,畢竟站在程序的角度來看這兩個根本就是同一個,如果內部類該變了,而外部方法的形參卻沒有改變這是難以理解和不可接受的,所以為了保持參數的一致性,就規定使用final來避免形參的不改變。

   簡單理解就是,拷貝引用,為了避免引用值發生改變,例如被外部類的方法修改等,而導致內部類得到的值不一致,於是用final來讓該引用不可改變。

   故如果定義了一個匿名內部類,並且希望它使用一個其外部定義的參數,那么編譯器會要求該參數引用是final的。

2. 匿名內部類中使用初始化代碼塊

   我們一般都是利用構造器來完成某個實例的初始化工作的,但是匿名內部類是沒有構造器的!那怎么來初始化匿名內部類呢?使用構造代碼塊!利用構造代碼塊能夠達到為匿名內部類創建一個構造器的效果。

    public class OutClass {  
        public InnerClass getInnerClass(final int age,final String name){  
            return new InnerClass() {  
                int age_ ;  
                String name_;  
                //構造代碼塊完成初始化工作  
                {  
                    if(0 < age && age < 200){  
                        age_ = age;  
                        name_ = name;  
                    }  
                }  
                public String getName() {  
                    return name_;  
                }  
                  
                public int getAge() {  
                    return age_;  
                }  
            };  
        }  
          
        public static void main(String[] args) {  
            OutClass out = new OutClass();  
              
            InnerClass inner_1 = out.getInnerClass(201, "chenssy");  
            System.out.println(inner_1.getName());  
              
            InnerClass inner_2 = out.getInnerClass(23, "chenssy");  
            System.out.println(inner_2.getName());  
        }  
    }  

五、內部類總結

  學習了上面四種類型的內部類,我們知道了如何使用各個內部類,那么為什么要使用內部類呢??

  參考博文:http://blog.csdn.net/yu422560654/article/details/7466260

  首先舉一個簡單的例子,如果你想實現一個接口,但是這個接口中的一個方法和你構想的這個類中的一個 方法的名稱,參數相同,你應該怎么辦?這時候,你可以建一個內部類實現這個接口。由於內部類對外部類的所有內容都是可訪問的,所以這樣做可以完成所有你直 接實現這個接口的功能。
 
不過你可能要質疑,更改一下方法的不就行了嗎?的確,以此作為設計內部類的理由,實在沒有說服 力。
  真正的原因是這樣的,java中的內部類和接口加在一起,可以的解決常被C++程序員抱怨java中存在的一個問題——沒有多繼承。實際上,C++的多繼承設計起來很復雜,而java通過內部類加上接口,可以很好的實現多繼承的效果。
 
內部類:一個內部類的定義是定義在另一個內部的類。
  原因是:
  1.一個內部類的對象能夠訪問創建它的對象的實現,包括私有數據。
  2.對於同一個包中的其他類來說,內部類能夠隱藏起來。
  3.匿名內部類可以很方便的定義回調。
  4.使用內部類可以非常方便的編寫事件驅動程序。

      內部類可以讓你更優雅地設計你的程序結構。下面從以下幾個方面來介紹:

  首先看這個例子:

1     public interface Contents {  
2      int value();  
3     }  
4       
5     public interface Destination {  
6      String readLabel();  
7     }  
 1     public class Goods {  
 2       private valueRate=2;  
 3       
 4      private class Content implements Contents {  
 5        private int i = 11 * valueRate;  
 6       public int value() {  
 7        return i;  
 8       }  
 9      }  
10       
11      protected class GDestination implements Destination {  
12       private String label;  
13       private GDestination(String whereTo) {  
14        label = whereTo;  
15       }  
16       public String readLabel() {  
17        return label;  
18       }  
19      }  
20       
21      public Destination dest(String s) {  
22       return new GDestination(s);  
23      }  
24       
25       public Contents cont() {  
26       return new Content();  
27      }  
28     }  

    在這個例子里類 Content 和 GDestination 被定義在了類 Goods 內部,並且分別有着 protected 和 private 修飾符來控制訪問級別。Content 代表着 Goods 的內容,而 GDestination 代表着 Goods 的目的地。它們分別實現了兩個接口Content和Destination。在后面的main方法里,直接用 Contents c 和 Destination d進行操作,你甚至連這兩個內部類的名字都沒有看見!這樣,內部類的第一個好處就體現出來了——隱藏你不想讓別人知道的操作,也即封裝性。

  非靜態內部類對象有着指向其外部類對象的引用

  修改上面的例子:

 1     public class Goods {  
 2       private valueRate=2;  
 3       
 4      private class Content implements Contents {  
 5        private int i = 11 * valueRate;  
 6       public int value() {  
 7        return i;  
 8       }  
 9      }  
10       
11      protected class GDestination implements Destination {  
12       private String label;  
13       private GDestination(String whereTo) {  
14        label = whereTo;  
15       }  
16       public String readLabel() {  
17        return label;  
18       }  
19      }  
20       
21      public Destination dest(String s) {  
22       return new GDestination(s);  
23      }  
24       
25       public Contents cont() {  
26       return new Content();  
27      }  
28     }  

  在這里我們給 Goods 類增加了一個 private 成員變量 valueRate,意義是貨物的價值系數,在內部類 Content 的方法 value() 計算價值時把它乘上。我們發現,value() 可以訪問 valueRate,這也是內部類的第二個好處——一個內部類對象可以訪問創建它的外部類對象的內容,甚至包括私有變量!這是一個非常有用的特性,為我們在設計時提供了更多的思路和捷徑。要想實現這個功能,內部類對象就必須有指向外部類對象的引用。Java 編譯器在創建內部類對象時,隱式的把其外部類對象的引用也傳了進去並一直保存着。這樣就使得內部類對象始終可以訪問其外部類對象,同時這也是為什么在外部 類作用范圍之外向要創建內部類對象必須先創建其外部類對象的原因。


  個人想說的話:對於java的內部類我花了比較長的時間在網上搜集資料,這部分的學習也有很多理解不夠的地方,如果有疑問可以在下面留言,我盡我所能解答;如果有改進的批評的地方歡迎指正,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM