內部類
今天接觸了一天的 Java 內部類,這個東西給我感覺一點就是:變化多端。之所以說這個東西變化多端,后面我們會用一些例子來證明,通過不同的形式來實現同一個方法。
內部類,可以分為以下四種
- 成員內部類:創建在類內方法外,和成員變量及成員方法類似。
- 局部內部類:創建在方法內部,類似局部代碼塊。
-
靜態內部類:用
static
修飾的內部類。 - 匿名內部類:也叫匿名子類對象,最特殊也是最常用的一種類型;一般用在方法參數的位置,匿名子類對象的類內方法也叫做 閉包。
1.創建內部類類對象格式
- 如果我們想訪問一個內部類時,我們需要通過
外部類名.內部類名
來找到這個內部類。 - 在我們創建下面??這個內部類對象的格式是這樣的:
外部類.內部類 對象名 = 外部類對象.內部類對象
class Outer {
class Inner {
public void method() {
System.out.println("Hello Inner");
}
}
}
class Sample_InnerClass01 {
public static void main(String[] args) {
// 外部類.內部類 對象名 = 外部類對象.內部類對象;
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}
Tip??:直接通過這種方式在創建內部類對象是不常用、也不推薦的方式,下面我們會介紹其他調用內部類的方式。
2.成員內部類私有的使用
- 我們知道面向對象一個重要原則是封裝,就像我們會私有化成員變量一樣,在一般情況下內部類也是需要進行私有化,并提供外部訪問接口。
- Sample 中我們私有化了
Inner
內部類,提供了print
方法供外部調用。
class Outer {
private int num = 10;
private class Inner {
public void method() {
System.out.println("Print: " + num);
}
}
public void print() {
Inner in = new Inner();
in.method();
}
}
class Sample_InnerClass02 {
public static void main(String[] args) {
Outer o = new Outer();
o.print();
}
}
Thinking???♂?:本例中為什么內部類能訪問到外部類的成員變量?
3.靜態成員內部類
- 與創建非靜態內部類對象不同的是,創建內部類對象格式有些不同。
- 靜態內部類對象創建格式:
外部類.內部類 對象名 = 外部類名.內部類對象
class Outer {
private static int num = 10;
static class Inner {
public void method() {
System.out.println("Print: " + num);
}
}
}
class Sample_InnerClass03 {
public static void main(String[] args) {
// 外部類.內部類 對象名 = 外部類名.內部類對象;
Outer.Inner oi = new Outer.Inner();
oi.method();
}
}
Reminder?????:靜態內部類中只能訪問本類的靜態成員。
4.成員內部類的小練習??
- 內部類之所以能獲取到外部類的成員,是因為它能獲取到外部類的引用
外部類名.this
。
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
// 內部類之所以能獲取到外部類的成員,是因為它能獲取到外部類的引用 外部類名.this
System.out.println(Outer.this.num); // 10
}
}
}
class Sample_InnerClass04 {
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
5.局部內部類訪問局部變量
- 在內存中
num
會隨著 method 方法的出棧而釋放,而內部類在堆區還沒有消失。JVM 默認為局部變量添加final
,其實是將num
復制 了一份提供給局部內部類來訪問,而之前真正的局部變量num
已經被釋放,所以此時將局部變量用final
修飾為一個常量。 - 如果在內部類中改變
num
的值就會出現這樣的錯誤提示:錯誤: 從內部類引用的本地變量必須是最終變量或實際上的最終變量
。
class Outer {
public void method() {
int num = 10; // 在 JDK1.8之后 JVM 會自動加上 final 修飾符
class Inner {
public void print() {
System.out.println(num);
}
}
Inner in = new Inner();
in.print();
}
}
class Sample_InnerClass05 {
public static void main(String[] args) {
new Outer().method();
}
}
6.匿名子類對象概述
- 概述:實際就是內部類的簡化寫法。
- 前提:存在一個 類 或 接口 ,這里的類可以是具體類也可以是抽象類。
- 格式:
new 類名或接口() {
重寫方法;
}
- 本質:是一個繼承了該類 或 者實現了該接口的 匿名子類對象。
interface Inter {
public abstract void print1();
}
class Outer {
public void method() {
Inter in = new Inter() {
public void print1() {
System.out.println("Print1");
}
};
in.print1();
}
}
class Sample_InnerClass06 {
public static void main(String[] args) {
new Outer().method();
}
}
7.匿名子類對象重寫多個方法的調用
- 使用原則:匿名子類對象只針對重寫一個方法的時候使用。
- 弊端:不能定義、調用子類特有的方法,且沒有子類類名。
interface Inter {
public void show1();
public void show2();
}
class Outer {
public void method() {
Inter in = new Inter() { // 父類引用指向子類對象(多態)
public void show1() {
System.out.println("show1");
}
public void show2() {
System.out.println("show2");
}
};
in.show1();
in.show2();
System.out.println(in);
}
}
class Sample_InnerClass07 {
public static void main(String[] args) {
new Outer().method();
}
}
Discussion??:
Inter
父類指向匿名子類對象,其實是一個多態,如果在子類對象中定義了父類中不存在的方法,則編譯會報錯。而如果創建一個子類對象來實現接口方法的話,就可以擴展子類特有的功能了。
8.接口對象作為參數傳遞的多種實現形式
interface Inter {
void print();
}
class Demo {
public static void method(Inter i) {
i.print();
}
}
class InterClass implements Inter {
public void print() {
System.out.println("Hello World1!");
}
}
class Test {
private static final Inter i3;
static {
i3 = new Inter() {
public void print() {
System.out.println("Hello World3!");
}
};
}
public static void main(String[] args) {
// 1.創建一個實現接口的子類對象。
InterClass i1 = new InterClass();
Demo.method(i1);
// 2.在方法中創建一個匿名子類并返回。
Inter i2 = method();
Demo.method(i2);
// 3.定義一個 Inter類型的 static 成員變量,并在構造代碼塊中用賦值它的子類對象。
Demo.method(i3);
// 4.在成員方法的參數位置創匿名子類對象,并重寫父類方法。(推薦方式)
Demo.method(new Inter() {
public void print() {
System.out.println("Hello World4!");
}
});
}
public static Inter method() {
return new Inter() {
public void print() {
System.out.println("Hello World2!");
}
};
}
}
Discussion??:可以看到,通過子類實現 抽象類 與 接口 的方式千變萬化,語法的掌握是基礎,但最終目的是為了提高代碼的內聚性、減少耦合性并簡化代碼,只有從這個目的出發去使用我們的內部類才能讓我們的編程能力得到提高。
9.悄悄話
- 這是我個人發表的第 2 篇簡書 Blog,第一篇文章 JavaSE 基礎(六)構造函數 試著投了首頁和幾個關于 Java 的專題,都順利通過了,甚至還被專題推薦了文章,真的是沒想到的。今天寫這篇技術 Blog 完全是受著大家的鼓勵而寫出來的。
- 因為這段時間本人正在 JavaSE 的閉關修行中,沒有太多時間去查閱相關資料和做文章優化,所以有些內容可能會有差錯,也請讀者們在評論中批評并指出問題。
-
今天開通了簡書專題 JavaSE 成長之路,主要為一樣正在 JavaSE 修行中的簡友們提供了技術交流的平臺,希望大家多多投稿交流互動。