Java程式設計思想閱讀筆記(第10章內部類)
阿新 • • 發佈:2019-02-20
內部類
- 內部類是指在一個外部類的內部再定義一個類。內部類作為外部類的一個成員,並且依附於外部類而存在的
- 可以將一個類的定義放在另一個類定義內部,這就是內部類
- 內部類自動擁有對包裹它的基類所有成員的訪問許可權
- 內部類可為靜態,可用protected和private修飾(而外部類只能使用public和預設的包訪問許可權)
- 內部類主要有以下幾類:成員內部類、區域性內部類、靜態內部類、匿名內部類
內部類的共性
- 內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class檔案,但是前面冠以外部類的類名和$符號 。
- 內部類不能用普通的方式訪問。
- 內部類宣告成靜態的,就不能隨便的訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變數 。
- 外部類不能直接訪問內部類的的成員,但可以通過內部類物件來訪問
- 內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變數,無論是否是private的 。因為當某個外圍類的物件建立內部類的物件時,此內部類會捕獲一個隱式引用,它引用了例項化該內部物件的外圍類物件。通過這個指標,可以訪問外圍類物件的全部狀態。
通過反編譯內部類的位元組碼,分析之後主要是通過以下幾步做到的: 1 編譯器自動為內部類新增一個成員變數,這個成員變數的型別和外部類的型別相同,這個成員變數就是指向外部類物件的引用 2 編譯器自動為內部類的構造方法新增一個引數,引數的型別是外部類的型別, 在構造方法內部使用這個引數為1中新增的成員變數賦值; 3 在呼叫內部類的建構函式初始化內部類物件時,會預設傳入外部類的引用。
在方法或者任意作用域定義內部類的兩個理由
- 實現了某型別的介面,於是可以建立內部類並返回對其的引用
- 想解決一個問題,需要一個類協助解決問題,但是不希望這個類是公共可用的
為什麼需要內部類?
- 內部類方法可以訪問該類定義所在的作用域的資料,包括私有的資料
- 內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 private 與protected許可權的,但內部類可以
- 可是實現多重繼承
- 當想要定義一個回撥函式且不想編寫大量程式碼時,使用匿名內部類比較便捷
- 每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。大家都知道Java只能繼承一個類,它的多重繼承在我們沒有學習內部類之前是用介面來實現的。但使用介面有時候有很多不方便的地方。比如我們實現一個介面就必須實現它裡面的所有方法。而有了內部類就不一樣了。它可以使我們的類繼承多個具體類或抽象類。
- 內部類使用得多繼承的解決方案變得完整。介面解決了部分問題,而內部類有效地實現了多重繼承,也就是說,內部類允許繼承多個非介面型別(類或者抽象類)。
成員內部類:
即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有static的變數和方法
public class TestParcel {
//每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響
private class PContents implements Contents{
// private static int i=11;
private int i=11;
@Override
public int value(){
return i;
}
}
public static void main(String[] args) {
TestParcel t=new TestParcel();
//內部類不能用普通的方式訪問
TestParcel.PContents p=t.new PContents();
}
}
區域性內部類:
- 在方法中定義的內部類稱為區域性內部類。與區域性變數類似,區域性內部類不能有訪問說明符,因為它不是外圍類的一部分,但是它可以訪問當前程式碼塊內的常量,和此外圍類所有的成員。
需要注意的是:
- (1)、方法內部類只能在定義該內部類的方法內例項化,不可以在此方法外對其例項化。
- (2)、方法內部類物件不能使用該內部類所在方法的非final區域性變數。
public class Outer {
private int s = 100;
private int out_i = 1;
public void f(final int k) {
final int s = 200;
int i = 1;
final int j = 10;
// 定義在方法內部
class Inner {
int s = 300;// 可以定義與外部類同名的變數
// static int m = 20;//不可以定義靜態變數
Inner(int k) {
inner_f(k);
}
int inner_i = 100;
void inner_f(int k) {
// 如果內部類沒有與外部類同名的變數,在內部類中可以直接訪問外部類的例項變數
System.out.println(out_i);
// 可以訪問外部類的區域性變數(即方法內的變數)
System.out.println(j);
System.out.println(i);
// 如果內部類中有與外部類同名的變數,直接用變數名訪問的是內部類的變數
System.out.println(s);
// 用this.變數名訪問的也是內部類變數
System.out.println(this.s);
// 用外部類名.this.內部類變數名訪問的是外部類變數
System.out.println(Outer.this.s);
}
}
new Inner(k);
}
public static void main(String[] args) {
// 訪問區域性內部類必須先有外部類物件
Outer out = new Outer();
out.f(3);
}
}
靜態內部類(巢狀類):
- 如果你不需要內部類物件與其外圍類物件之間有聯絡,那你可以將內部類宣告為static。這通常稱為巢狀類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類物件隱含地儲存了一個引用,指向建立它的外圍類物件。然而,當內部類是static的時,就不是這樣了。
巢狀類意味著:
- 要建立巢狀類的物件,並不需要其外圍類的物件。
- 不能從巢狀類的物件中訪問非靜態的外圍類物件。
public class Outer {
private static int i = 1;
private int j = 10;
public static void outer_f1() {}
public void outer_f2() {}
// 靜態內部類可以用public,protected,private修飾
// 靜態內部類中可以定義靜態或者非靜態的成員
private static class Inner {
static int inner_i = 100;
int inner_j = 200;
static void inner_f1() {
// 靜態內部類只能訪問外部類的靜態成員(包括靜態變數和靜態方法)
System.out.println("Outer.i" + i);
outer_f1();
}
void inner_f2() {
// 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變數和非靜態方法)
// System.out.println("Outer.i"+j);
// outer_f2();
}
}
public void outer_f3() {
// 外部類訪問內部類的靜態成員:內部類.靜態成員
System.out.println(Inner.inner_i);
Inner.inner_f1();
// 外部類訪問內部類的非靜態成員:例項化內部類即可
Inner inner = new Inner();
inner.inner_f2();
}
public static void main(String[] args) {
new Outer().outer_f3();
}
}
匿名內部類:
簡單地說:匿名內部類就是沒有名字的內部類
什麼情況下需要使用匿名內部類?如果滿足下面的一些條件,使用匿名內部類是比較合適的:
- 只用到類的一個例項。
- 類在定義後馬上用到。
- 類非常小(SUN推薦是在4行程式碼以下)
- 給類命名並不會導致你的程式碼更容易被理解。
在使用匿名內部類時,要記住以下幾個原則:
- 匿名內部類不能有構造方法。
- 匿名內部類不能定義任何靜態成員、方法和類。
- 匿名內部類不能是public,protected,private,static。
- 只能建立匿名內部類的一個例項。
- 一個匿名內部類一定是在new的後面,用其隱含實現一個介面或實現一個類。
- 因匿名內部類為區域性內部類,所以區域性內部類的所有限制都對其生效。
public class Parcel8 {
public Destination dest(final String name, String city) {
return new Destination(name, city) {
private String label = name;
public String getName() {
return label;
}
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania", "gz");
}
abstract class Destination {
Destination(String name, String city) {
System.out.println(city);
}
abstract String getName();
}
}