淺談java內部類

前言java

說到java內部類,想必你們首先會想到比較經常使用的「匿名內部類」,但實際上,這只是內部類的其中一種使用方式而已。內部類的使用方式實際上總共包括:成員內部類, 方法局部類,匿名內部類,下面,我就給你們來一一介紹:mysql

爲何要使用內部類面試

有的時候你可能有這樣一種需求:對一個類(假設它爲MyClass.java)建立一個和它相關的類(假設它是Part.java),但由於Part.java和MyClass之間的聯繫「緊密」且「單一」,致使咱們在這種狀況下,不但願像下面這樣增長一個額外的兄弟類算法

├─MyClass      
└─Part

而但願能將Part.java的數據隱藏在MyClass.java內部,因而這個時候內部類就冠冕堂皇地出現了spring

那麼,這個不請自來的內部類到底給咱們上述的局面形成了怎樣的改變呢? 讓咱們來看看:sql

增長一個額外的兄弟類Part:數組

  1. 對一些沒有關聯的類可見(若是protected則對同一包內類可見,若是public則對全部類可見)
  2. 不能徹底自由的訪問MyClass中的私有數據(必須通過訪問器方法)
  3. 新增了一個java文件

使用內部類,將Part類的定義寫入MyClass內部jvm

  1. 能夠減小多餘的可見性,例如可把Part在MyClass內部定義爲私有,這樣對同一包內其餘類也不可見了
  2. 內部類(Part)能夠自由訪問外圍類的全部數據(MyClass),包括私有數據
  3. 減小了一個java文件,使得類結構更簡潔

成員內部類ide

故名思議,成員內部類嘛~ 使用固然和成員變量很類似咯函數

你能夠像

private String data

這樣定義一個「平行的」成員內部類:

private class Inner

具體看下面的例子:

Outter.java:

public class Outter {
  // 成員變量data
  private String data = "外部數據";
 
  //定義一個內部類
  private class Inner {
    public void innerPrint () {
      System.out.println(data);
    }
   } 
     
  // 外部類的方法, new一個內部類的實例並調用其innerPrint方法
  public void outterPrint () {
    Inner i = new Inner();
    i.innerPrint();
  }
}

Test.java:

public class Test {
  public static void main (String [] args) {
    Outter o = new Outter();
    o.outterPrint();
  }
}

結果輸出:

外部數據

看來這仍是能達到咱們預期的效果的:因爲將Inner內部類設爲private,它變得只對咱們當前的外部類Outter類可見,咱們成功地把它"隱藏"在了Outter類內部,與此同時,它還自由地訪問到了Outter類的私有成員變量data

兩個this

雖然上面的例子看起來挺簡單的,但實際上內部類的做用機制仍是比較複雜的。

首先要考慮的是「this」的問題,外部類和內部類各有一個this,關鍵在於內部類中咱們如何對這兩個this做出區分:

咱們假設上面的例子中的Inner類內部有一個方法fn:

private class Inner {
  public  void fn () {
    Outter.this // 指向Outter實例對象的this引用
    this  // 指向Inner實例對象的this引用
  }
}

在這個方法fn裏,Outter.this是指向Outter實例對象的this的引用, 而this是指向Inner實例對象的this的引用

咱們訪問類中成員變量有兩種方式: 隱式訪問(不加this)和顯式訪問(加this)

隱式訪問類中成員變量

讓咱們對上面的Outter.java作一些改動,增長一行代碼:

public class Outter {
  // 成員變量data
  private String data = "外部數據"; 
  //定義一個內部類
  private class Inner {
    // 增長Inner類對data成員變量的聲明
    private String data = "內部數據" 
    public void innerPrint () {
      System.out.println(data);
    }
  } 
     
  // 外部類的方法, new一個內部類的實例並調用其innerPrint方法
  public void outterPrint () {
    Inner i = new Inner();
    i.innerPrint();
  }
}

結果輸出:

內部數據

如此可見,內部類內聲明的數據會覆蓋外部類的同名數據。或者說, 在上述例子中,對於data成員變量,它會首先在Inner的this中查找有無這個成員變量,而後沒有,那麼就再在Outter.this中查找

顯式訪問類中成員變量

但有的時候咱們但願既能訪問外部類的成員變量,同時也能訪問內部類的成員變量,這個時候咱們就要使用到this了,可是如何區份內部類和外部類的this呢?你能夠這樣:

以上述例子爲例:

訪問外部類定義的成員變量:Outter.this.data
訪問內部類定義的成員變量:this.data

以下圖所示

public class Outter {
  // 外部類的成員變量data
  private String data = "外部數據"; 
  //定義一個內部類
  private class Inner {
    // 內部類的成員變量data
    private String data = "內部數據";
    public void innerPrint () {
      System.out.println(Outter.this.data);
      System.out.println(this.data);
    }
  } 
     
  // 外部類的方法, new一個內部類的實例並調用其innerPrint方法
  public void outterPrint () {
    Inner i = new Inner();
    i.innerPrint();
  }
}

局部內部類

局部內部類是內部類的第二種形式,它讓內部類的「隱藏」得更深一層——寫在外部類的方法內部,而不是處於和外部類方法平行的位置。

讓咱們對上面成員內部類處理的場景作些思考:咱們的Inner內部類僅僅只在outterPrint方法中使用了一次:

public void outterPrint () {
  Inner i = new Inner();
  i.innerPrint();
}

那麼咱們能不能把Inner內部類直接定義在outterPrint的內部呢?這樣的話,它就能更好地隱藏起來,即便是類Outter中除outterPrint外的方法,也不能訪問到它:

如今的Outter的類看起來像這樣:

public class Outter {
  public void outterPrint () {// 外部類方法
    class LocalInner { // 局部內部類
      public void innerPrint () {   }
    } 
    LocalInner i = new LocalInner(); // 實例化局部內部類
    i.innerPrint();
  }
}

相比於成員內部類,局部內部類多了一項能訪問的數據,那就是局部變量(由外部類方法提供)

成員內部類:外部類數據,內部類數據

局部內部類: 外部類數據,內部類數據, 局部數據

具體示例以下:

Outter.java

public class Outter {
  private String data = "外部數據";  // 外部類數據
  public void outterPrint (final String localData) { // 局部數據
    class LocalInner {
      private String data = "內部數據";  // 內部類數據
      public void innerPrint () {
        System.out.println(Outter.this.data);  // 打印外部類數據
        System.out.println(this.data);   //  打印內部類數據
        System.out.println(localData);  // 打印局部數據
      }
    } 
    LocalInner i = new LocalInner();
    i.innerPrint();
  }
}

Test.java:

public class Test {
  public static void main (String [] args) {
    Outter o = new Outter();
    o.outterPrint("局部數據");
  }
}

結果輸出:

外部數據
內部數據
局部數據

局部類所使用的外部類方法的形參必須用final修飾

這裏要注意一點, 局部類所使用的外部類方法的形參必須用final修飾,不然會編譯不經過,也就是說傳入後不準改變

爲何這個方法形參必定要用final修飾?

(僅我的理解,若有不一樣的意見或者更好的理解歡迎在評論區討論)

若是不用final修飾會怎樣? 且聽我慢慢道來:

首先要說一下:

1.內部類和外部類在編譯以後形式上是同樣的,不會有內外之分
2.局部內部類對於使用的外部方法的值會用構造函數作一個拷貝(編譯後)

例如對於下面outterPrint方法中的LocalInner

public void outterPrint (final String data) {
  class LocalInner {
    public void innerPrint () {
    // 使用 data
    }
  }
}/./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天

編譯以後大概長這樣:

public class Outter$LocalInner{ 
  public LocalInner(String data){
    this.LocalInner$data = data; // 對於使用的data作了一次拷貝
  }
  public void innerPrint (){ /* 使用 data */ }
}

這裏要注意的是:

  1. 編譯後,LocalInner並不是直接使用data,而是用構造器拷貝一份後再使用
  2. java是值傳遞的,因此包裹 LocalInner的外部方法outterPrint也會對傳入的data參數作一次拷貝(基本類型數據拷貝副本,對象等則拷貝引用)

OK,如今的狀況是:

方法內的局部類對data拷貝了兩次:外部方法outterPrint值傳遞時的拷貝,和LocalInner構造函數的拷貝
方法內除了局部類外的做用域只拷貝了data一次: 外部方法outterPrint值傳遞時的拷貝

拷貝兩次和拷貝一次,致使在outterPrint方法內部, 局部類內部的data和局部類外部的data是不一樣步的! 也即你在局部類內部改了data不影響局部類外部的data,在局部類外部改了data也不影響局部類內部的data(注意一個前提,值是基本類型的,若是是對象的話由於拷貝的是引用仍然能夠「同步」)

圖示一:在這裏插入圖片描述

圖示二:在這裏插入圖片描述

因而java說: 哎呀媽呀, 這都data都不一樣步了, 要是讓你修改這還了得!!! 因而就強行要求咱們加上final

【注意】所謂的不一樣步主要是針對基本類型來講的,若是是對象之類的話由於拷貝的是引用因此仍然能夠「同步」

如何突破必須用final的限制

咱們上面說到,局部內部類所使用的方法形參必須用final修飾的限制。

例如

public void outterPrint (String data) {// 沒加上final
  class LocalInner { 
    public void changeData () {
      data = "我想修改data的值";  // 在這一行編譯報錯
    }
   } 
}

提示:

Cannot refer to a non-final variable data inside an inner class defined in a different method

那麼,若是咱們有對該形參必須能修改的硬性需求怎麼辦?

你能夠經過一種有趣的方式繞開它:使用一個單元素數組。由於用final修飾的基本類型的變量不容許修改值,可是卻容許修改final修飾的單元素數組裏的數組元素, 由於存放數組的變量的值只是一個引用,咱們修改數組元素的時候是不會修改引用指向的地址的,在這點上final並不會妨礙咱們:

Outter.java

public class Outter {
  public void outterPrint (final String []  data) { 
    class LocalInner { 
      public void innerPrint () {
        data[0] = "冠冕堂皇地修改它!!";   // 修改數據
        System.out.print(data[0]);  // 輸出修改後的數據
      }
    } 
    LocalInner i = new LocalInner();
    i.innerPrint();
  }
}

Test.java:

public class Test {
  public static void main (String [] args) {
    Outter o = new Outter();
    String [] data = new String [1];
    data[0] = "我是數據";
    o.outterPrint(data);  // 修改數據而且輸出
  }
}

結果輸出:

冠冕堂皇地修改它!!

【注意】局部類不能用public或private訪問符進行聲明!!

匿名內部類

假若咱們再把局部內部類再深化一下, 那就是匿名內部類

匿名內部類的使用方式

new [超類/接口] {   /* 類體 */   }

讓咱們看看下面這個例子:
Other.java:

public class Other {    }

Outter.java:

public class Outter {
  public void outterPrint (String data) { 
    Other o = new Other() {  }; // 匿名內部類
  }
}

何謂之匿名?

「誒,不是說好的匿名嗎? 那麼爲何還有個Other的類名呢?」

Other o = new Other() {  /* 匿名內部類的類體 */   };

實際上,這裏的Other並非咱們的匿名內部類,而是咱們匿名內部類的超類,上面一行代碼其實至關於(用成員內部類來表示的話)

// annoymous翻譯爲匿名
public class Outter {
  private class annoymous extends Other{  }  
  public void outterPrint () { 
    Other a = new annoymous();
  }
}

同時要注意,咱們在使用匿名內部類的方式,是在定義一個內部類的同時實例化該內部類:

new Other() {  /* 匿名內部類的類體 */  };  // new操做和定義類的代碼是牢牢結合在一塊兒的

匿名函數的做用

用匿名函數的做用在於在一些特定的場景下寫起來很簡單,例如事件監聽器:

ActionListener listener = new ActionListener() { 
  public void actionPerformed(ActionEvent e) {   }
};

避免了再建立另一個類文件

講的有點亂, 對匿名內部類作個總結:

  1. 省略被定義的類的類名
  2. 必須結合超類或者接口使用,即 new [超類/接口] { / 類體 / }
  3. 在定義該匿名類的同時實例化該匿名類
  4. 在一些場景下能簡化代碼

【注意】匿名類不能有構造器, 由於構造器和類同名,而匿名類沒有類名,因此匿名類不能有構造器

文章總結
咱們使用內部類的緣由主要有三點:
1.實現數據隱藏, 避免多餘的可見性
2.自由訪問外部類的變量

  1. 在使用監聽器等場景的時候使用匿名內部類,避免增長的大量代碼

關於成員內部類, 方法局部類,匿名內部類的關係

從成員內部類,方法局部類到匿名內部類是一個不斷深刻的關係, 成員內部類進一步隱藏可見性就成爲了方法局部類, 方法局部類省去類名,並將類的定義和實例化操做合併到一塊兒,就是匿名內部類。所以,匿名內部類沿襲了成員內部類和方法局部類的基本特特性

內部類的一些特殊的要求
1.局部類不能用public或private訪問符進行聲明
2.局部類所使用的外部類方法的形參必須用final修飾

  1. 匿名內部類不能有構造器

image

最新2020整理收集的一些高頻面試題(都整理成文檔),有不少乾貨,包含mysql,netty,spring,線程,spring cloud、jvm、源碼、算法等詳細講解,也有詳細的學習規劃圖,面試題整理等,須要獲取這些內容的朋友請加Q君樣:909038429/./*歡迎加入java交流Q君樣:909038429一塊兒吹水聊天

相關文章
相關標籤/搜索