1. 什么是嵌套類?
在 Java 語言中允許在另外一個類中定義一個類,這樣的類被稱為嵌套類。包含嵌套類的類稱為外部類(outer class),也可以叫做封閉類,在本文中統一叫做外部類。
內部類的語法:
class OuterClass {
// code
class NestedClass {
// code
}
}
嵌套類分為兩類:
- 靜態嵌套類( Static nested classes ):使用
static
聲明,一般稱為嵌套類( Nested Classes )。- 非靜態嵌套類( Non-static Nested Classes ):非
static
聲明,一般稱為內部類( Inner Classes )。
class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
嵌套類是它的外部類的成員,非靜態嵌套類(內部類)可以訪問外部類的其他成員,即使該成員是私有的。而靜態嵌套類只能訪問外部類的靜態成員。
嵌套類作為外部類的一個成員,可以被聲明為:private
,public
,protected
或者包范圍(注意:外部類只能被聲明為public
或者包范圍)。
2. 為什么要使用嵌套類?
使用嵌套類的主要優點有以下幾個方面:
- 嵌套類可以訪問外部類的所有數據成員和方法,即使它是私有的。
- 提高可讀性和可維護性:因為如果一個類只對另外一個類可用,那么將它們放在一起,這更便於理解和維護。
- 提高封裝性:給定兩個類A和B,如果需要訪問A類中的私有成員,則可以將B類封裝在A類中,這樣不僅可以使得B類可以訪問A類中的私有成員,並且可以在外部隱藏B類本身。
- 減少代碼的編寫量。
3. 嵌套類的類型
前文已經介紹了嵌套類分為靜態嵌套類和非靜態嵌套類,而非靜態嵌套類又稱為內部類(內部類是嵌套類的子集)。
靜態嵌套類可以使用外部類的名稱來訪問它。例如:
OuterClass.StaticNestedClass
如果要為靜態嵌套類創建對象,則使用以下語法:
OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();
非靜態嵌套類(內部類)又可以分為以下三種:
- 成員內部類(Member inner class)
- 匿名內部類(Anonymous inner class)
- 局部內部類(Local inner class)
類型 | 描述 |
---|---|
成員內部類 | 在類中和方法外部創建的類 |
匿名內部類 | 為實現接口或擴展類而創建的類,它的名稱由編譯器決定 |
局部內部類 | 在方法內部創建的類 |
靜態嵌套類 | 在類中創建的靜態類 |
嵌套接口 | 在類中或接口中創建的接口 |
4. 靜態嵌套類
靜態嵌套類其實就是在頂級類中封裝的一個頂級類,它的行為和頂級類一樣,它被封裝在頂級類中其實就是為了方便打包。
- 靜態嵌套類是外部類的靜態成員,它可以直接使用
外部類名.靜態嵌套類名
訪問自身。 - 它可以訪問外部類的靜態成員和靜態私有成員。
- 與其他類一樣,靜態嵌套類不能訪問非靜態成員。
靜態嵌套類的語法如下:
class OuterClass{
// 外部類的靜態數據成員
static int data = 30;
// 外部類中的靜態嵌套類
static class StaticNestedClass {
// 靜態嵌套類中的實例方法
void getData() {System.out.println("data is "+data);}
}
public static void main(String args[]){
// 實例化靜態嵌套類(創建對象)
OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass();
obj.getData();
}
}
輸出結果:
data is 30
在上面的例子中,你需要先創建一個靜態嵌套類的實例,因為你需要訪問它的實例方法getData()
。但是你並不需要實例化外部類,因為靜態嵌套類相對於外部類來說它是靜態的,可以直接使用外部類名.靜態嵌套類名
訪問。
由編譯器生成的靜態嵌套類:
import java.io.PrintStream;
static class OuterClass$Inner
{
OuterClass$StaticNestedClass(){}
void getData(){
System.out.println((new StringBuilder()).append("data is ")
.append(OuterClass.data).toString());
}
}
從上面的代碼可以看出,靜態嵌套類實際上是直接通過OuterClass.data
來訪問外部類的靜態成員的。
使用靜態嵌套類的靜態方法示例:
如果靜態嵌套類中有靜態成員,則不需要實例化靜態嵌套類即可直接訪問。
class OuterClass2{
static int data = 10;
static class StaticNestedClass{
// 靜態嵌套類的靜態方法
static void getData() {System.out.println("data is "+data);}
}
public static void main(String args[]){
// 不需要創建實例可以直接訪問
OuterClass2.StaticNestedClass.getData();
}
}
輸出結果:
data is 10
5. 非靜態嵌套類
非靜態嵌套類也就是內部類,它有以下幾個特點:
- 實例化內部類必須先實例化一個外部類。
- 內部類實例與外部類實例相關聯,所有不能在內部類中定義任何靜態成員。
- 內部類是非靜態的。
下面看詳細介紹↓
5.1 成員內部類
在外部類中並且在外部類的方法外創建的非靜態嵌套類,稱為成員內部類。說白了成員內部類就是外部類的一個非靜態成員而已。
語法如下:
class OuterClass{
//code
class MemberInnerClass{
//code
}
}
成員內部類的示例:
在下面的示例中,我們在成員內部類中創建了getData()
和getTest()
這兩個實例方法,並且這兩個方法正在訪問外部類中的私有數據成員和成員方法。
class OuterClass {
private int data = 30;
public void test() {System.out.println("我是外部類成員方法");}
class MemberInnerClass {
// 訪問外部類的私有數據成員
void getData() {System.out.println("data is "+data);}
// 訪問外部類的成員方法
void getTest() {OuterClass.this.test();}
}
public static void main(String args[]) {
// 首先必須實例化外部類
OuterClass obj = new OuterClass();
// 接着通過外部類對象來創建內部類對象
OuterClass.MemberInnerClass in = obj.new MemberInnerClass();
in.getData();
in.getTest();
}
}
輸出結果:
data is 30
我是外部類成員方法
訪問外部類的成員方法時,可以直接通過外部類名.this.外部類成員方法()
來調用,或者直接使用外部類成員方法()
來調用。
成員內部類的工作方式:
Java 編譯器創建了兩個.class
文件,其中一個是成員內部類OuterClass$MemberInnerClass.class
,成員內部類文件名格式為外部類名$成員內部類名
。
如果你想創建成員內部類的實例,你必須先實例化一個外部類,接着,通過外部類的實例來創建成員內部類的實例。因此成員內部類的實例必須存在於一個外部類的實例中。另外,由於內部類的實例與外部類的實例相關聯,因此它不能定義任何靜態成員。
由編譯器生成的成員內部類代碼:
在下面的例子中,編譯器創建了一個名為OuterClass$MemberInnerClass
的內部類文件,成員內部類具有外部類的引用,這就是為什么它可以訪問包括私有在內的所有外部類成員。
import java.io.PrintStream;
class OuterClass$MemberInnerClass {
final OuterClass this$0;
OuterClass$MemberInnerClass() {
super();
this$0 = OuterClass.this;// 引用了外部類
}
void getData() {
System.out.println((new StringBuilder()).append("data is ")
.append(OuterClass.access$000(OuterClass.this)).toString());
}
}
5.2 局部內部類
局部內部類(Local inner class)通常定義在一個塊中。所以通常你會在一個方法塊中找到局部內部類。
正如局部變量那樣,局部內部類的作用域受到方法的限制。它可以訪問外部類的所有成員,和它所在的局部方法中所有局部變量。
如果你想調用局部內部類中的方法,你必須先在局部方法中實例化局部內部類。
請看下面示例:
public class localInner {
private int data = 30;// 實例變量
// 實例方法
void display() {
// 局部內部類
class Local{
void msg() {System.out.println(data);}
}
// 訪問局部內部類中的方法
Local l = new Local();
l.msg();
}
public static void main(String args[]){
localInner obj = new localInner();
obj.display();
}
}
輸出結果
30
由編譯器生成的局部內部類代碼:
此時編譯器創建了一個名為localInner$Local
的類,它具有外部類的引用
import java.io.PrintStream;
class localInner$Local {
final localInner this$0; // 自動生成的局部變量
localInner$Local() {
super();
this$0 = localInner.this;
}
void msg()
{
System.out.println(localInner.access$000(localInner.this));
}
}
局部內部類的一些規則:
- 無法從方法外部調用局部內部類
- 局部內部類不能被聲明為
private, public, protected
- 在
JDK1.7
之前局部內部類不能訪問非final
的局部變量,但是在JDK1.8
及之后是可以訪問非final
的局部變量的。
局部內部類和局部變量的示例:
class localInner2 {
private int data = 30;//實例變量
void display() {
int value = 50;//在 Jdk1.7 之前局部變量必須被聲明為'final'
class Local{
void msg() {System.out.println("value: "+value);}
}
Local l = new Local();
l.msg();
}
public static void main(String args[]){
localInner2 obj = new localInner2();
obj.display();
}
}
輸出結果
value: 50
5.3 匿名內部類
在 Java 中沒有命名的內部類稱為匿名內部類,當我們需要重寫類或接口中的方法時,都會使用到它。匿名內部類在使用的時候將同時聲明和實例化。
匿名類可以通過以下兩種方式進行創建:
- 類(可能是抽象類或者其他類)
- 接口
使用類創建匿名內部類:
abstract class Person {
abstract void eat();
}
public class TestAnonymousInner {
public static void main(String[] args){
// 使用匿名內部類實例化抽象類Person
Person p = new Person() {
void eat() {System.out.println("nice fruits");}
};
p.eat();
}
}
輸出結果
nice fruits
上面代碼中有以下一段:
Person p = new Person() {
void eat() {System.out.println("nice fruits");}
};
它主要有兩個作用:
- 創建一個匿名內部類,它的名稱由編譯器決定,並給出了
Person
類中eat()
方法的實現。 - 創建匿名類的對象,並使用了類型為
Person
的引用變量p
來引用。
由編譯器生成的匿名內部類代碼:
import java.io.PrintStream;
static class TestAnonymousInner$1 extends Person
{
TestAnonymousInner$1(){}
void eat() {
System.out.println("nice fruits");
}
}
從以上代碼,我們可以看出,編譯器實際上將匿名內部類命名為TestAnonymousInner$1
,並繼承了抽象類Person
。
使用接口創建匿名內部類:
interface Eatable{
void eat();
}
class TestAnnonymousInner1{
public static void main(String args[]){
// 創建匿名內部類來實現接口Eatable
Eatable e = new Eatable() {
public void eat(){System.out.println("nice fruits");}
};
e.eat();
}
}
輸出結果
nice fruits
上面代碼有以下一段:
Eatable e = new Eatable() {
public void eat(){System.out.println("nice fruits");}
};
它主要有兩個作用:
- 創建一個匿名類,它的名稱由編譯器決定,並給出了
eat()
方法的實現。 - 創建匿名類的對象,並使用了類型為
Person
的引用變量p
來引用。
由編譯器生成的匿名內部類代碼:
import java.io.PrintStream;
static class TestAnonymousInner1$1 implements Eatable
{
TestAnonymousInner1$1(){}
void eat(){System.out.println("nice fruits");}
}
從以上代碼我們可以看出,編譯器實際上創建了一個名為TestAnonymousInner1$1
的類,並使用該類實現了Eatable
接口。
6. 嵌套接口
前文主要介紹了嵌套類相關的知識,Java 中嵌套接口與嵌套類類似,下面我們來了解一下關於嵌套接口方面的知識。
嵌套接口(Nested Interface)指的是在一個類或接口中創建的接口。嵌套接口用於將相關的接口進行分組以便於維護。嵌套接口必須由外部接口或外部類引用,它不能直接訪問。
需要記住的關於嵌套接口的要點:
這里給出了 Java 程序員應該記住的關於嵌套接口的要點:
- 在接口中聲明的嵌套接口,默認修飾符為
public
,也必須為public
,但在類中聲明嵌套接口則可以使用任何訪問修飾符。 - 嵌套接口都被隱世聲明為靜態的。
在接口中聲明嵌套接口:
interface interface_name{
...
interface nested_interface_name{
...
}
}
在類中聲明嵌套接口:
class class_name{
...
interface nested_interface_name{
...
}
}
在接口中聲明嵌套接口的示例:
在下面這個例子中,我們將學習如何聲明嵌套接口以及如何使用它。
interface Showable{
void show();
// 聲明嵌套接口
interface Message{
void msg();
}
}
class TestNestedInterface1 implements Showable.Message{
// 實現嵌套接口Message中的msg()方法
public void msg() {System.out.println("Hello nested interface");}
public static void main(String args[]){
// 實例化TestNestedInterface1類,向上轉型為Showable.Message接口類型
Showable.Message message = new TestNestedInterface1();
message.msg();
}
}
輸出結果:
Hello nested interface
正如上面的示例中所看到的,我們不能直接訪問嵌套接口Message
,但是我們可以通過外部接口Showable
來訪問它。
就好比房間中的衣櫃,我們不能直接拿到衣櫃中的衣服,我們必須先進入房間才行。
由編譯器生成的嵌套接口Message的代碼:
public static interface Showable$Message
{
public abstract void msg();
}
從上面代碼可以看出,嵌套接口實際被聲明為靜態的了。
在類中聲明嵌套接口的示例:
class A{
interface Message{
void msg();
}
}
class TestNestedInterface2 implements A.Message{
// 實現Message接口中的msg()方法
public void msg() {System.out.println("Hello nested interface");}
public static void main(String args[]){
// 實例化TestNestedInterface2類,並向上轉型為A.Message類型
A.Message message = new TestNestedInterface2();
message.msg();
}
}
輸出結果:
Hello nested interface
最后思考一下,我們可以在接口中定義一個類嗎?
答案是可以的。當我們在接口中定義類時,Java 編譯器會自動創建一個靜態嵌套類。
interface M{
class A{}
}