一:什麼是內部類?
內部類,就是定義在另外一個類裏面的類。與之對應,包含內部類的類被稱作爲外部類。
二:內部類的作用
內部類主要提供了三種作用:
1.內部類提供了更好的封裝,可以把內部類隱藏在外部類之內,不允許同一個包中的其它類直接訪問內部類。
2.內部類的方法可以直接訪問外部類的所有數據。
3.內部類所實現的功能外部類同樣可以實現,只不過有時實現內部類較爲方便。
三:內部類的分類
內部類主要分爲以下幾種類型:
1.成員內部類
2.靜態內部類
3.方法內部類
4.匿名內部類
接下來分別詳細說明4種內部類型的用法。
四:成員內部類
直接在類中定義一個內部類,Out1.java
public class Out1{
private String outA = "outA";
private String outB = "outB";
class In1{
private String outB = "inB";
public void showIn(){
String outB = "methodB";
System.out.println("調用外部類中的outB: " + Out1.this.outB);
System.out.println("調用內部類實例變量outB: " + this.outB);
System.out.println("調用內部類中方法(本方法)的局部變量outB: " + outB);
}
}
public void showOut(){
System.out.println("在Out1外部類中訪問內部類的數據outB: " + new In1().outB);
}
/**
* 測試main方法
* @param args
*/
public static void main(String[] args){
Out1 out = new Out1();
out.showOut();
//生成內部類的方法
Out1.In1 in = out.new In1();
in.showIn();
}
}
運行結果:
在Out1外部類中訪問內部類的數據outB: inB
調用外部類中的outB: outB
調用內部類實例變量outB: inB
調用內部類中方法(本方法)的局部變量outB: methodB
總結:
1.程序編譯後會出現兩個.class文件,一個是Out1.class,另一個是Out1$In1.class.
2.上述代碼生成內部類的方法Out1.In1 in = out.new In1();說明了必須先有外部類的對象纔能有內部類,開頭的Out1是爲了標識此內部類在哪一個外部類中。
3.內部類在沒有同名成員變量和局部變量的情況下,內部類會直接訪問外部類的成員變量,而無需指定Out1.this.屬性名
否則,內部類中的局部變量會覆蓋外部類的成員變量,而訪問內部類本身的成員變量可用this.屬性名,訪問外部類的成員變量需要使用Out1.this.屬性名。
4.非靜態成員內部類的成員不允許是static類型的。
注意:
還有一個比較特殊的成員內部類叫做私有成員內部類,既成員內部類被private修飾,此時此內部類只能被外部類的方法去操作,而不能在之外生成此內部類的對象。
五:靜態內部類
靜態內部類是被static修飾的內部類,Out2.java
public class Out2 {
private static String outA = "outA";
static class In2{
public void show(){
System.out.println(outA);
}
}
}
測試類Test.java
public class Test {
public static void main(String[] args) {
Out2.In2 in = new Out2.In2();
in.show();
}
}
運行結果:
outA
總結:
1.靜態內部類中的方法只能訪問外部類的靜態變量。
2.靜態內部類實例的創建不再依賴外部類的實例。
3.靜態內部類的成員允許是static類型的。
六:方法內部類
見名知意,一個類在一個方法之中,見Out3.java
public class Out3 {
private String outA = "outA";
//注意下面兩個final
public void test(final int a){
final int b = 2;
class In3{
public void show(){
System.out.println("輸出傳遞進來的參數a: " + a);
System.out.println("輸出外部類的實例變量outA : " + outA);
System.out.println("輸出函數中的局部變量b: " + b);
}
}
new In3().show();
}
public static void main(String[] args) {
new Out3().test(1);
}
}
運行結果:
輸出傳遞進來的參數a: 1
輸出外部類的實例變量outA : outA
輸出函數中的局部變量b: 2
總結:
1.方法內部類若想要訪問方法中的局部變量,必須給變量聲明爲final類型。
2.方法內部類依舊能訪問外部類的所有。
注意:
對於總結的第一條,解釋如下:
內部類的生命週期和方法中的局部變量是不一樣的,內部類是也是一個類,是存儲在堆中,也只有當對該類的引用消失時,內部類纔會消亡。而方法的局部變量是存儲在堆棧中的,當調用結束時就會退棧,即在內存中這個屬性就消失了。也就是說,內部類的生命週期超過了方法中局部變量的生命週期,內部類可能會調用到已經消失的屬性,因此內部類不能訪問方法中的局部變量。 解決方法就是在局部變量前加修飾符final ,此時局部變量就會存在堆中,生命週期跟工程的生命週期是一樣的,此時內部類就可以訪問方法中的局部變量
七:匿名內部類
匿名內部類也就是沒有名字的內部類
正因爲沒有名字,所以匿名內部類只能使用一次,它通常用來簡化代碼編寫
但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口
通常,我們會這樣寫代碼:
abstract class Parent {
public abstract void show();
}
public class Son extends Parent {
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("Hello Son");
}
}
public class Demo {
public static void main(String[] args) {
Parent p = new Son();
p.show();
}
}
輸出結果:
Hello Son
引入匿名內部類後,我們可以這樣改寫代碼(第一種語法形式)。
public class Demo {
public static void main(String[] args) {
new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
}.show();
}
}
這種結果和上面的一樣,這是形式有大大的變化,實際上上面這段代碼的意思就是,聲明一個Parent的子類並重寫Parent的show()方法,然後創建一個該子類的實例然後調用其show()方法。由於聲明的該Parent的子類沒有名字,所以叫匿名類。又由於沒有名字的類只能存在於一個類或者一個方法內部,所以又稱爲匿名內部類。
我們還可以這樣寫(第二種語法形式)
public class Demo {
public static void main(String[] args) {
Parent p = new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
};
p.show();
}
}
這種和第一種唯一的區別是不是直接創建子類並調用其方法,而是聲明一個該子類的父類引用p,然後通過該父類引用調用子類方法。創建完匿名類的實例後,沒有立即執行show(),創建實例和執行實例的方法分開。
還有第三種寫法(第三種語法形式)
public class Demo {
public static void main(String[] args) {
new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
{
show();
}
};
}
}
實際上這種寫法就是在匿名子類的類局部代碼塊中調用其類方法。 局部代碼塊內的語句是在創建該類的實例後由類加載器隱式立即執行的。
一般來說,匿名內部類的優點就是書寫較爲簡單,實例化一個類後立即做一些事情,比較方便。
但它的缺點也很明顯,總結兩條:
1.每一個內部類的實例都會隱性的持有一個指向外部類的引用(靜態內部類除外),這樣一方面是多餘的引用浪費。
2.另一方面當串行化這個子類實例時外部類也會被不知不覺的串行化,如果外部類沒有實現serialize接口時,就會報錯。