文章目錄
Java 基礎 —— 內部類
基礎認識
Java中,將一個類定義在另一個類中,這個就稱爲內部類。
內部類可通俗認爲分爲以下幾類:
- 成員內部類
- 局部內部類
- 匿名內部類
- 靜態內部類
成員內部類
位於一個類的內部,屬於最基礎的內部類。
public class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
/**
* 內部類 Draw
*/
class Draw {
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
成員內部類訪問外部類
閱讀上述代碼,類Draw像是Circle的一個成員,Circle成爲外部類。成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private和final修飾成員)。
public class Circle {
private double radius = 0;
public static int count =1;
private static final String test = "我是靜態私有成員";
public Circle(double radius) {
this.radius = radius;
}
/**
* 內部類
*/
class Draw {
public void drawSahpe() {
// 外部類的private成員
System.out.println(radius);
// 外部類的靜態成員
System.out.println(count);
// 外部類的private,static,final成員
System.out.println(test);
}
}
}
注意:內部類和外部類存在同名成員變量和方法時,會發生隱藏對象。默認情況會訪問成員內部類的成員,如果要訪問外部類同名成員,語法如下:
外部類.this.成員變量
外部類.this.成員方法
外部類訪問成員內部類
內部類訪問外部類雖然很方便,但是反之則沒那麼容易,外部類如果想使用內部類的方法和成員,必須先實例化內部類(new),代碼如下:
public class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
// 必須先創建成員內部類的對象,再進行訪問
getDrawInstance().drawSahpe();
}
/**
* 實例化一個內部類
* @return
*/
private Draw getDrawInstance() {
return new Draw();
}
/**
* 內部類
*/
class Draw {
public void drawSahpe() {
// 外部類的private成員
System.out.println(radius);
}
}
}
外部如何使用成語內部類
成員內部類是依附外部類進行使用的,如果要使用成員內部類,必須存在一個外部類對象,依託於外部類創建成員內部類,代碼如下:
- 成員內部類
public class Outter {
/**
* 聲明成員內部類,但沒有實例化
*/
private Inner inner = null;
/**
* 外部類構造函數
*/
public Outter() {
}
/**
* 創建成員內部類
* @return
*/
public Inner getInnerInstance() {
if(inner == null) {
inner = new Inner();
}
return inner;
}
/**
* 成員內部類構造函數
*/
class Inner {
public Inner() {
}
}
}
- 測試運行代碼
public class Test {
public static void main(String[] args) {
// 第一種方式:
// 聲明並實例化外部類。
Outter outter = new Outter();
// 必須通過外部類Outter對象來獲取成員內部類。
Outter.Inner inner = outter.new Inner();
// 第二種方式:
// 通過外部類定義的函數獲取,函數封裝了上面的實例化操作。
Outter.Inner inner1 = outter.getInnerInstance();
}
}
成員內部類的訪問權限
- 成員內部類可以擁有private訪問權限、protected訪問權限、public訪問權限及包訪問權限。
藉由上述例子舉例:- 如果成員內部類 Inner 用 private 修飾,則只能在外部類的內部訪問。
- 如果成員內部類 Inner 用 public 修飾,則任何地方都能訪問。
- 如果成員內部類 Inner 用 protected 修飾,則只能在同一個包下,或者繼承外部類的情況下訪問。
- 如果默認訪問權限(默認是default修飾),則只能在同一個包下訪問。
外部類只允許用public和包訪問兩種權限修飾。
局部內部類
局部內部類是定義在一個方法或者一個作用域裏面的類,與成員內部類的區別在於訪問權限於方法內或者該作用域內,示例代碼如下:
- People類
class People{
public static String peopleString = "Frank能活到";
public People() {
}
public String getPeopleString() {
return peopleString;
}
}
- 成員內部類
public class Man {
public Man(){
}
public String getWoman(){
/**
* 局部內部類
*/
class Woman extends People{
/**
* 定義年齡
*/
public int age = 100;
/**
* 返回一句話
* @return
*/
public String getSuperString() {
return super.getPeopleString() + 100 + "歲";
}
}
// 直接返回局部內部類裏面的一個函數,如果這裏返回局部內部類,外部無法獲取到內部的成員;
return new Woman().getSuperString();
}
public static void main(String[] args) {
System.out.println(new Man().getWoman());
}
}
局部內部類和方法內的局部變量相似,是不能有public、protected、private及static修飾。
匿名內部類
匿名內部類大量應用在研發中。
使用方法一
下面舉例,Android事件監聽:
// 爲button設置點擊監聽,OnClickListener爲匿名內部類。
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
// 爲history_btn設置點擊監聽。
history_btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
這兩個按鈕設置了點擊監聽,內部類代碼如下:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
使用方法二
匿名內部類在使用時前,必須存在。我們也可以像以下示例中的方式使用:
/**
* 分別爲button、history_btn設置監聽。
*/
private void setListener() {
button.setOnClickListener(new Listener1());
history_btn.setOnClickListener(new Listener2());
}
/**
* 局部內部類,監聽1
*/
class Listener1 implements View.OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
/**
* 局部內部類,監聽2
*/
class Listener2 implements View.OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
雖然方法2可以達到一樣的效果,但是代碼冗長不利於維護,所以一般採用第一種寫法。
匿名內部類的特點
- 匿名內部類不能有訪問修飾符和static修飾符。
- 匿名內部類是唯一一種沒有構造器的類。
因爲沒有構造器,匿名內部類的使用範圍非常有限,大部分匿名內部類用於接口回調。 - 匿名內部類編譯時虛擬機自動起名爲
Outter$1.class
。
通常,匿名內部類用於繼承其它類或者實現接口,並不需要增加額外方法,只是對繼承方法對重載或重構。
靜態內部類
同樣是定義在一個類裏面的類,並多了一個static修飾。
靜態內部類的特點
- 包含static修飾
- 靜態內部類是不需要依賴外部類的,和類的靜態成員屬性類似。
- 靜態內部類不能使用外部類的非static成員變量或方法。
因爲在沒有外部類的對象情況下,可以創建靜態內部類的對象,如果允許訪問外部類非static成員就會出現矛盾,因爲外部類的非static成員,必須依附於具體的對象。
靜態內部類示例
- 靜態內部類
class Outter {
public int num = 99;
public static int numTwo = 100;
/**
* 外部類構造函數
*/
public Outter() {
}
/**
* 靜態內部類
*/
static class Inner {
/**
* 靜態內部類構造函數
*/
public Inner() {
System.out.println(numTwo);
// 靜態內部類無法訪問外部類沒有static修飾的成員
System.out.println(num);
}
/**
* public修飾成員
*/
public String PUBLIC_INNER_STRING = "public_inner_string";
/**
* public static 修飾成員
*/
public static String PUBLIC_STATIC_INNER_STRING = "static_inner_string";
/**
* private 修飾成員
*/
private String PRIVATE_INNER_STRING = "private_inner_string";
/**
* private static 修飾成員
*/
private static String PRIVATE_STATIC_INNER_STRING = "private_static_inner_string";
}
}
- main函數
public class Man {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
// 只允許訪問 public 修飾成員
System.out.println(inner.PUBLIC_INNER_STRING);
// 不允許訪問 public static 修飾成員
System.out.println(inner.PUBLIC_STATIC_INNER_STRING);
// 不允許訪問 private 修飾成員
System.out.println(inner.PRIVATE_INNER_STRING);
// 不允許訪問 private static 修飾成員
System.out.println(inner.PRIVATE_STATIC_INNER_STRING);
}
}
深入理解內部類
爲什麼成員內部類可以無條件訪問外部類的成員 ?
上面提到,成員內部類可以無條件的訪問外部類的成員,
那到底是如何做到的呢?我們將下面的示例編譯成字節碼,通過字節碼文件來解析其中奧妙:
- Java文件
public class Outter {
/**
* 聲明私有成員內部類
*/
private Inner inner = null;
/**
* 外部類構造函數
*/
public Outter() {
}
/**
* 獲取成員內部類
* @return
*/
public Inner getInnerInstance() {
if(inner == null) {
inner = new Inner();
}
return inner;
}
/**
* 成員內部類
* protected修飾:只能在同一個包下或者繼承外部類的情況下訪問
*/
protected class Inner {
public Inner() {
}
}
}
- 編譯,生成字節碼文件
- 字節碼文件
Classfile target/classes/com/frank/cp/test/Outter$Inner.class
Last modified 2019-4-22; size 497 bytes
MD5 checksum 893ed4fb28ba17c1675fffac1ac300c0
Compiled from "Outter.java"
public class com.frank.cp.test.Outter$Inner
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Fieldref #3.#19 // com/frank/cp/test/Outter$Inner.this$0:Lcom/frank/cp/test/Outter;
#2 = Methodref #4.#20 // java/lang/Object."<init>":()V
#3 = Class #22 // com/frank/cp/test/Outter$Inner
#4 = Class #23 // java/lang/Object
#5 = Utf8 this$0
#6 = Utf8 Lcom/frank/cp/test/Outter;
#7 = Utf8 <init>
#8 = Utf8 (Lcom/frank/cp/test/Outter;)V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Inner
#14 = Utf8 InnerClasses
#15 = Utf8 Lcom/frank/cp/test/Outter$Inner;
#16 = Utf8 MethodParameters
#17 = Utf8 SourceFile
#18 = Utf8 Outter.java
#19 = NameAndType #5:#6 // this$0:Lcom/frank/cp/test/Outter;
#20 = NameAndType #7:#24 // "<init>":()V
#21 = Class #25 // com/frank/cp/test/Outter
#22 = Utf8 com/frank/cp/test/Outter$Inner
#23 = Utf8 java/lang/Object
#24 = Utf8 ()V
#25 = Utf8 com/frank/cp/test/Outter
{
final com.frank.cp.test.Outter this$0;
descriptor: Lcom/frank/cp/test/Outter;
flags: ACC_FINAL, ACC_SYNTHETIC
public com.frank.cp.test.Outter$Inner(com.frank.cp.test.Outter);
descriptor: (Lcom/frank/cp/test/Outter;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/frank/cp/test/Outter;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 44: 0
line 46: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/frank/cp/test/Outter$Inner;
0 10 1 this$0 Lcom/frank/cp/test/Outter;
MethodParameters:
Name Flags
this$0 final mandated
}
SourceFile: "Outter.java"
InnerClasses:
protected #13= #3 of #21; //Inner=class com/frank/cp/test/Outter$Inner of class com/frank/cp/test/Outter
line 9至 line 35是常量池內容,我們看36行代碼:
final com.frank.cp.test.Outter this$0;
通過這行我們可以看出,編譯器默認會爲成員內部類添加一個指向外部類對象的引用,那麼它是如何賦初始值的呢?下面接着看內部類的構造器:
public com.frank.cp.test.Outter$Inner(com.frank.cp.test.Outter);
如代碼所示,雖然定義的內部類構造函數是無參的,但是編譯器默認添加了一個參數,該參數的類型爲指向外部類對象的一個引用,所以,成員內部類中的Outter this&0 指針便指向了外部類對象,因此,可以在成員內部類中隨意訪問外部類的成員。
從字節碼中也可以看出,成員內部類是依賴於外部類的,如果沒有創建外部類對象,則無法對 Outter this&0 引用進行初始化賦值,也就無法創建成員內部類對象。
使用場景和優點
使用內部類的主要原因:
- 1.每個內部類都可以獨立繼承一個接口的實現,無論外部類是繼承了父類的子類還是實現接口的實現類,都可以使用內部類,且沒有影響。內部類使多繼承解決方案更加完整;
- 2.方便將存在一定邏輯關係的類組裝一起,又可以對外界隱藏(詳見:
靜態內部類的示例
); - 3.方便編寫事件驅動程序(點擊事件監聽等);
- 4.方便編寫線程代碼;