Java 嵌套類基礎詳解


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)

Nested Classes

類型 描述
成員內部類 在類中和方法外部創建的類
匿名內部類 為實現接口或擴展類而創建的類,它的名稱由編譯器決定
局部內部類 在方法內部創建的類
靜態嵌套類 在類中創建的靜態類
嵌套接口 在類中或接口中創建的接口

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{}  
}  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM