Java 內部類綜述

摘要:html

  多重繼承指的是一個類能夠同時從多於一個的父類那裏繼承行爲和特徵,然而咱們知道Java爲了保證數據安全,它只容許單繼承。但有時候,咱們確實是須要實現多重繼承,並且現實生活中也真正地存在這樣的狀況,好比遺傳:咱們即繼承了父親的行爲和特徵也繼承了母親的行爲和特徵。可幸的是,Java 提供了兩種方式讓咱們曲折地來實現多重繼承:接口和內部類。事實上,實現多重繼承是內部類的一個極其重要的應用。除此以外,內部類還能夠很好的實現隱藏(例如,私有成員內部類)。內部類共有四種類型,即成員內部類、靜態內部類、局部內部類和匿名內部類。特別地,java

  • 成員內部類:成員內部類是外圍類的一個成員,是 依附於外圍類的,因此,只有先建立了外圍類對象纔可以建立內部類對象。也正是因爲這個緣由,成員內部類也不能含有 static 的變量和方法;android

  • 靜態內部類:靜態內部類,就是修飾爲 static 的內部類,該內部類對象 不依賴於外部類對象,就是說咱們能夠直接建立內部類對象,但其只能夠直接訪問外部類的全部靜態成員和靜態方法;express

  • 局部內部類:局部內部類和成員內部類同樣被編譯,只是它的 做用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效;編程

  • 匿名內部類:定義匿名內部類的前提是,內部類必需要繼承一個類或者實現接口,格式爲 new 父類或者接口(){定義子類的內容(如函數等)}。也就是說,匿名內部類最終提供給咱們的是一個匿名子類的對象。安全

一. 內部類概述

一、 內部類基礎less

  內部類指的是在一個類的內部所定義的類,類名不須要和源文件名相同。內部類是一個編譯時的概念,一旦編譯成功,內部類和外部類就會成爲兩個徹底不一樣的類。例如,對於一個名爲 Outer 的外部類和在其內部定義的名爲 Inner 的內部類,在編譯完成後,會出現 Outer.class 和 Outer$inner.class 兩個類。所以,內部類的成員變量/方法名能夠和外部類的相同。內部類能夠是靜態static的,也可用 public,default,protected 和 private 修飾。 特別地,關於 Java源文件名與類名的關係( java源文件名的命名與內部類無關,如下3條規則中所涉及的類和接口均指的是外部類/接口),須要符合下面三條規則:ide

  • 若是java源文件包含public類(public接口),則源文件名必須與public類名(public接口名)相同。 
      一個java源文件中,若是有public類或public接口,那麼就只能有一個public類或一個public接口,不能有多個public的類或接口。固然,一個java源文件中能夠有多個包可見的類或接口,即默認訪問權限修飾符(類名前沒有訪問權限修飾符)。public類(接口) 與 包可見的類(接口)在文件中的順序能夠隨意,即public類(接口)能夠不在第一個的位置。函數

  • 若是java源文件不包含public類(public接口),則java源文件名沒有限制。 
      只要符合文件名的命名規範就能夠,能夠不與文件中任一個類或接口同名,固然,也能夠與其中之一同名。學習

  • 類和接口的命名不能衝突。 
      同一個包中的任何一個類或接口的命名都不能相同。不一樣包中的類或接口的命名能夠相同,由於經過包能夠把它們區分開來。


二、 內部類的做用

使用內部類能夠給咱們帶來如下優勢:

  • 內部類能夠很好的實現隱藏(通常的非內部類,是不容許有 private 與 protected 權限的,但內部類能夠);

  • 內部類擁有外圍類的全部元素的訪問權限;

  • 能夠實現多重繼承;

  • 能夠避免修改接口而實現同一個類中兩種同名方法的調用。


1)內部類能夠很好的實現隱藏

  平時咱們對類的訪問權限,都是經過類前面的訪問修飾符來限制的,通常的非內部類,是不容許有 private 與 protected 權限的,但內部類能夠,因此咱們能經過內部類來隱藏咱們的信息。能夠看下面的例子:

//測試接口 public interface InterfaceTest { public void test(); } //外部類 public class Example { //內部類 private class InnerClass implements InterfaceTest{ @Override public void test() { System.out.println("I am Rico."); } } //外部類方法 public InterfaceTest getInnerInstance(){ return new InnerClass(); } } //客戶端 public class Client { public static void main(String[] args) { Example ex = new Example(); InterfaceTest test = ex.getInnerInstance(); test.test(); } }/* Output: I am Rico. *///:~ 

  對客戶端而言,咱們能夠經過 Example 的getInnerInstance()方法獲得一個InterfaceTest 實例,但咱們並不知道這個實例是如何實現的,也感覺不到對應的具體實現類的存在。因爲 InnerClass 是 private 的,因此,咱們若是不看源代碼的話,連實現這個接口的具體類的名字都看不到,因此說內部類能夠很好的實現隱藏。


2)內部類擁有外圍類的全部元素的訪問權限

//外部類 public class Example { private String name = "example"; //內部類 private class Inner{ public Inner(){ System.out.println(name); // 訪問外部類的私有屬性 } } //外部類方法 public Inner getInnerInstance() { return new Inner(); } } //客戶端 public class Client { public static void main(String[] args) { Example ex = new Example(); ex.getInnerInstance(); } }/* Output: example *///:~ 

  name 這個成員變量是在Example裏面定義的私有變量,這個變量在內部類中能夠被無條件地訪問。

  

3)能夠實現多重繼承

  對多重繼承而言,能夠這樣說,接口只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。內部類使得Java的繼承機制更加完善,是內部類存在的最大理由。Java中的類只能繼承一個類,它的多重繼承在咱們沒有學習內部類以前是用接口來實現的。但使用接口有時候有不少不方便的地方,好比,咱們實現一個接口就必須實現它裏面的全部方法;而內部類可使咱們的類繼承多個具體類或抽象類,規避接口的限制性。看下面的例子:

//父類Example1 public class Example1 { public String name() { return "rico"; } } //父類Example2 public class Example2 { public int age() { return 25; } } //實現多重繼承的效果 public class MainExample { //內部類Test1繼承類Example1 private class Test1 extends Example1 { public String name() { return super.name(); } } //內部類Test2繼承類Example2 private class Test2 extends Example2 { public int age() { return super.age(); } } public String name() { return new Test1().name(); } public int age() { return new Test2().age(); } public static void main(String args[]) { MainExample mexam = new MainExample(); System.out.println("姓名:" + mexam.name()); System.out.println("年齡:" + mexam.age()); } }/* Output: 姓名:rico 年齡:25 *///:~ 

  注意到類 MainExample,在這個類中,包含兩個內部類 Test1 和 Test2。其中,類Test1繼承了類Example1,類Test2繼承了類Example2。這樣,類MainExample 就擁有了 類Example1 和 類Example2 的方法,也就間接地實現了多繼承。


4) 避免修改接口而實現同一個類中兩種同名方法的調用

  考慮這樣一種情形,一個類要繼承一個類,還要實現一個接口,但是它所繼承的類和接口裏面有兩個相同的方法(方法簽名一致),那麼咱們該怎麼區分它們呢?這就須要使用內部類了。例如,

//Test 所實現的接口 public interface InterfaceTest { public void test(); } //Test 所實現的類 public class MyTest { public void test(){ System.out.println("MyTest"); } } //不使用內部類的情形 public class Test extends MyTest implements InterfaceTest{ public void test(){ System.out.println("Test"); } }

  此時,Test中的 test() 方法是屬於覆蓋 MyTest 的 test() 方法呢,仍是實現 InterfaceTest 中的 test() 方法呢?咱們怎麼能調到 MyTest 這裏的方法?顯然這是很差區分的。而咱們若是用內部類就很好解決這一問題了。看下面代碼:

//Test 所實現的接口 public interface InterfaceTest { public void test(); } //Test 所實現的類 public class MyTest { public void test(){ System.out.println("MyTest"); } } //使用內部類的情形 public class AnotherTest extends MyTest { private class InnerTest implements InterfaceTest { @Override public void test() { System.out.println("InterfaceTest"); } } public InterfaceTest getCallbackReference() { return new InnerTest(); } public static void main(String[] args) { AnotherTest aTest = new AnotherTest(); aTest.test(); // 調用類MyTest 的 test() 方法 aTest.getCallbackReference().test(); // 調用InterfaceTest接口中的 test() 方法 } }

  經過使用內部類來實現接口,就不會與外圍類所繼承的同名方法衝突了。


三、 內部類的種類

  在Java中,內部類的使用共有兩種狀況:

  (1) 在類中定義一個類(成員內部類,靜態內部類); 
  (2) 在方法中定義一個類(局部內部類,匿名內部類)。


二. 成員內部類

一、定義與原理

  成員內部類是最普通的內部類,它是外圍類的一個成員,在實際使用中,通常將其可見性設爲 private。成員內部類是依附於外圍類的,因此,只有先建立了外圍類對象纔可以建立內部類對象。也正是因爲這個緣由,成員內部類也不能含有 static 的變量和方法,看下面例子:

public class Outter { private class Inner { private final static int x=1; // OK /* compile errors for below declaration * "The field x cannot be declared static in a non-static inner type, * unless initialized with a constant expression" */ final static Inner a = new Inner(); // Error static Inner a1=new Inner(); // Error static int y; // Error } }

  若是上面的代碼編譯無誤, 那麼咱們就能夠直接經過 Outter.Inner.a 拿到內部類Inner的實例。 因爲內部類的實例必定要綁定到一個外部類的實例的,因此矛盾。所以,成員內部類不能含有 static 變量/方法。此外,成員內部類與 static 的關係還包括:

  • 包含 static final 域,但該域的初始化必須是一個常量表達式;
  • 內部類能夠繼承含有static成員的類。

二、交互

 成員內部類與外部類的交互關係爲:

  • 成員內部類能夠直接訪問外部類的全部成員和方法,即便是 private 的;
  • 外部類須要經過內部類的對象訪問內部類的全部成員變量/方法。
//外部類 class Out { private int age = 12; private String name = "rico"; //內部類 class In { private String name = "livia"; public void print() { String name = "tom"; System.out.println(age); System.out.println(Out.this.name); System.out.println(this.name); System.out.println(name); } } // 推薦使用getxxx()來獲取成員內部類的對象 public In getInnerClass(){ return new In(); } } public class Demo { public static void main(String[] args) { Out.In in = new Out().new In(); // 片斷 1 in.print(); //或者採用註釋內兩種方式訪問 /* * 片斷 2 Out out = new Out(); out.getInnerClass().print(); // 推薦使用外部類getxxx()獲取成員內部類對象 Out.In in = out.new In(); in.print(); */ } }/* Output: 12 rico livia tom *///:~ 

 對於代碼片斷 1和2,能夠用來生成內部類的對象,這種方法存在兩個小知識點須要注意:

1) 開頭的 Out 是爲了標明須要生成的內部類對象在哪一個外部類當中; 
2) 必須先有外部類的對象才能生成內部類的對象。


所以,成員內部類,外部類和客戶端之間的交互關係爲:

  • 在成員內部類使用外部類對象時,使用 outer.this 來表示外部類對象;
  • 在外部類中使用內部類對象時,須要先進行建立內部類對象;
  • 在客戶端建立內部類對象時,須要先建立外部類對象。

特別地,對於成員內部類對象的獲取,外部類通常應提供相應的 getxxx() 方法。


三、私有成員內部類

  若是一個成員內部類只但願被外部類操做,那麼可使用 private 將其聲明私有內部類。例如,

class Out {
    private int age = 12; private class In { public void print() { System.out.println(age); } } public void outPrint() { new In().print(); } } public class Demo { public static void main(String[] args) { /* * 此方法無效 Out.In in = new Out().new In(); in.print(); */ Out out = new Out(); out.outPrint(); } }/* Output: 12 *///:~ 

  在上面的代碼中,咱們必須在Out類裏面生成In類的對象進行操做,而沒法再使用Out.In in = new Out().new In() 生成內部類的對象。也就是說,此時的內部類只對外部類是可見的,其餘類根本不知道該內部類的存在。


三. 靜態內部類

一、定義與原理

   靜態內部類,就是修飾爲 static 的內部類,該內部類對象不依賴於外部類對象,就是說咱們能夠直接建立內部類對象。看下面例子: 
   
            靜態內部類.png-29.5kB


二、交互

 靜態內部類與外部類的交互關係爲:

  • 靜態內部類能夠直接訪問外部類的全部靜態成員和靜態方法,即便是 private 的;
  • 外部類能夠經過內部類對象訪問內部類的實例成員變量/方法;對於內部類的靜態域/方法,外部類能夠經過內部類類名訪問。

三、成員內部類和靜態內部類的區別

 成員內部類和靜態內部類之間的不一樣點包括:

  • 靜態內部類對象的建立不依賴外部類的實例,但成員內部類對象的建立須要依賴外部類的實例;

  • 成員內部類可以訪問外部類的靜態和非靜態成員,靜態內部類不能訪問外部類的非靜態成員;


四. 局部內部類

一、定義與原理

   有這樣一種內部類,它是嵌套在方法和做用域內的,對於這個類的使用主要是應用與解決比較複雜的問題,想建立一個類來輔助咱們的解決方案,但又不但願這個類是公共可用的,因此就產生了局部內部類。局部內部類和成員內部類同樣被編譯,只是它的做用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。

// 例 1:定義於方法內部 public class Parcel4 { public Destination destination(String s) { class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel4 p = new Parcel4(); Destination d = p.destination("Tasmania"); } } 
// 例 2:定義於做用域內部 public class Parcel5 { 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("slip"); String s = ts.getSlip(); } } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel5 p = new Parcel5(); p.track(); } } 

二、final 參數

   對於final參數,如果將引用類型參數聲明爲final,咱們沒法在方法中更改參數引用所指向的對象;如果將基本類型參數聲明爲final,咱們能夠讀參數,但卻沒法修改參數(這一特性主要用來向局部內部類和匿名內部類傳遞數據)。

  若是定義一個局部內部類,而且但願它的方法能夠直接使用外部定義的數據,那麼咱們必須將這些數據設爲是 final 的;特別地,若是隻是局部內部類的構造器須要使用外部參數,那麼這些外部參數就不必設置爲 final,例如: 
      局部內部類.png-66.9kB


五. 匿名內部類

   有時候我爲了免去給內部類命名,便傾向於使用匿名內部類,由於它沒有名字。匿名內部類的使用須要注意如下幾個地方:

  • 匿名內部類是沒有訪問修飾符的;

  • 匿名內部類是沒有構造方法的 (由於匿名內部類連名字都沒有)

  • 定義匿名內部類的前提是,內部類必須是繼承一個類或者實現接口,格式爲 new 父類或者接口(){子類的內容(如函數等)}。也就是說,匿名內部類最終提供給咱們的是一個匿名子類的對象,例如:

// 例 1 abstract class AbsDemo { abstract void show(); } public class Outer { int x=3; public void function()//可調用函數 { new AbsDwmo()//匿名內部類 { void show() { System.out.println("x==="+x); } void abc() { System.out.println("haha"); } }.abc(); //匿名內部類調用函數,匿名內部類方法只能調用一次 } }
// 例 2 interface Inner { //註釋後,編譯時提示類Inner找不到 String getName(); } public class Outer { public Inner getInner(final String name, String city) { return new Inner() { private String nameStr = name; public String getName() { return nameStr; } }; } 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 instanceof Inner); //匿名內部類實質上是一個匿名子類的對象 } /* Output: Inner true *///:~ } 

  • 若匿名內部類 (匿名內部類沒有構造方法) 須要直接使用其所在的外部類方法的參數時,該形參必須爲 final 的;若是匿名內部類沒有直接使用其所在的外部類方法的參數時,那麼該參數就沒必要爲final 的,例如:
// 情形 1:匿名內部類直接使用其所在的外部類方法的參數 name 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) { // 形參 name 被設爲 final return new Inner() { private String nameStr = name; // OK private String cityStr = city; // Error: 形參 city 未被設爲 final public String getName() { return nameStr; } }; } } // 情形 2:匿名內部類沒有直接使用其所在的外部類方法的參數 public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } //注意這裏的形參city,因爲它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,因此沒必要定義爲final public Inner getInner(String name, String city) { return new Inner(name, city) { // OK,形參 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(); } 

  從上述代碼中能夠看到,當匿名內部類直接使用其所在的外部類方法的參數時,那麼這些參數必須被設爲 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來規避這種莫名其妙錯誤的存在。」


  以上關於匿名內部類的每一個例子使用的都是默認無參構造函數,下面咱們介紹 帶參數構造函數的匿名內部類

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(); } 

  特別地,匿名內部類經過實例初始化 (實例語句塊主要用於匿名內部類中),能夠達到相似構造器的效果,以下:

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; } }; } } 

六. 內部類的繼承

  內部類的繼承,是指內部類被繼承,普通類 extents 內部類。而這時候代碼上要有點特別處理,具體看如下例子: 
              內部類繼承.png-48.5kB

  能夠看到,子類的構造函數裏面要使用父類的外部類對象.super() [成員內部類對象的建立依賴於外部類對象];而這個外部類對象須要從外面建立並傳給形參。


引用

談談Java的匿名內部類 
java源文件名與類名的關係 
Java內部類的做用 
Java內部類的使用小結 
Java中普通內部類爲什麼不能有static數據和static字段,也不能包含嵌套類。 
java提升篇(八)—-詳解內部類 
【解惑】領略Java內部類的「內部」 
java提升篇(九)—–實現多重繼承

相關文章
相關標籤/搜索