java - java內部類和靜態嵌套類

  显示原文与译文双语对照的内容

在Java中內部類和靜態嵌套類的主要區別是什麼? 設計/implementation 在選擇以下任何一個方面是否扮演了一個角色?

时间:

嵌套類分為兩類: 靜態和 non-static 。聲明為靜態的嵌套類只是靜態嵌套類。 Non-static嵌套類稱為內部類。

靜態嵌套類使用封閉類名訪問:


OuterClass.StaticNestedClass

例如要為靜態嵌套類創建一個對象,請使用以下語法:


OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

作為內部類實例的對象存在於外部類的實例中。 請考慮以下類:


class OuterClass {
. . .
 class InnerClass {
. . .
 }
}

InnerClass的實例只能在OuterClass的實例中存在,並且可以直接訪問它的封閉實例的方法和欄位。

要實例化內部類,必須首先實例化外部類。 然後,使用以下語法在外部對象內創建內部對象:


OuterClass.InnerClass innerObject = outerObject.new InnerClass();

參見:Java教程- 嵌套類

出於完整性的考慮注意,還有這樣的事情作為一個內部類沒有封閉的實例:


class A {
 int t() { return 1; }
 static A a = new A() { int t() { return 2; } };
}

這裡 new A() {.. . } 是一個內部類中定義一個靜態上下文和沒有一個封閉的實例。

Java教程說明:

術語:嵌套類分為兩類: 靜態和 non-static 。聲明為靜態的嵌套類只是靜態嵌套類。 Non-static嵌套類稱為內部類。

通常來說,"嵌套"和"內部"是大多數程序員交替使用的術語,但我將使用正確的術語"嵌套類",它涵蓋了內部和靜態。

類可以嵌套無限, 比如 類可以包含類b包含c類包含類d,然而, 等等 不止一個一級類嵌套是罕見的,因為它通常是糟糕的設計。

創建嵌套類有以下三個原因:

  • 組織:有時候,將一個類排序到另一個類的命名空間中似乎是最明智的,特別是當它不會被用於任何其他上下文時
  • 訪問:嵌套類對它的包含類的變數/欄位具有特殊訪問許可權。
  • 方便:必須為每個新類型創建一個新文件,這又是煩人的,特別是當類型只在一個上下文中使用時

有在java四種嵌套類。 簡而言之,它們是:

  • 靜態類: 聲明為另一個類的靜態成員
  • 內部類: 聲明為另一個類的實例成員
  • 本地內部類: 在另一個類的實例方法中聲明
  • 匿名內部類: 像一個局部內部類,但作為一個表達式寫入,返回一次性對象

更多細節:

靜態類

靜態類是最容易理解的類,因為它們與包含類的實例無關。

靜態類是聲明為另一個類的靜態成員的類。 就像其他靜態成員一樣,此類實際上只是一個使用包含類作為它的命名空間的掛起, e.g 。 類山羊聲明為類的靜態成員犀牛在包比薩眾所周知的名字 pizza.Rhino.Goat 。


package pizza;

public class Rhino {

. . .

 public static class Goat {
. . .
 }
}

坦白地說,靜態類是一個非常無用的特性,因為類已經被包劃分為命名空間。 創建靜態類的唯一可以想象的原因是這樣一個類可以訪問它的類靜態成員的私有私有類,但我發現它對靜態類特性的存在是一個非常糟糕的理由。

內部類

內部類是聲明為另一個類的non-static成員的類:


package pizza;

public class Rhino {

 public class Goat {
. . .
 }

 private void jerry() {
 Goat g = new Goat();
 }
}

靜態類一樣,內部類被稱為限定類名稱 pizza.Rhino.Goat,但在包含類中,它可以由它的簡單名稱知道。 但是,內部類的每個實例都綁定到它的包含類的特定實例: 上面創建的山羊 jerry隱式綁定到犀牛實例這傑瑞 。 否則,我們讓犀牛實例相關聯明確當我們實例化山羊 :


Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();

(注意你指的是內心的類型作為山羊奇怪新的語法: Java推斷來自 。的包含類型。 而且,新的rhino.Goat() 對我也有更大的意義。

那麼這對我們有什麼好處? 內部類實例可以訪問包含類實例的實例成員。 這些封閉的實例內部類內成員是指通過只是簡單的姓名,不通過 這 ( 內部類中的引用內部類實例,而不是關聯的包含類實例):


public class Rhino {

 private String barry;

 public class Goat {
 public void colin() {
 System.out.println(barry);
 }
 }
}

內部類,你可以參考這個包含類的 Rhino.this, 你可以使用這個引用其成員, e.g 。 Rhino.this. 。

本地內部類

局部內部類是在方法體中聲明的類。 此類類只能在它的包含方法中知道,因此它只能被實例化,並且它的成員可以在它的包含方法中訪問。 增益是一個局部內部類實例被綁定,並且可以訪問它的包含方法的final 局部變數。 當實例使用它的包含方法的final 局部時,變數保留在創建實例時保留的值,即使變數已經超出了範圍( 這實際上是java的粗糙的,限制的閉包版本) 。

因為局部內部類既不是類或者包的成員,也不是用訪問級別聲明的。 ( 但是,請明確,它自己的成員具有像普通類一樣的訪問級別。)

如果本地內部類中聲明的方法,實例內部類的一個實例與實例由方法的包含創建實例的時候,所以包含實例可以像訪問類成員內部類實例。 本地內部類被實例化簡單地通過它的名字, e.g 。 本地內部類 cat 被實例化新 Cat(), 不是新 this.Cat() 與你預期的一樣。

匿名內部類

匿名內部類是編寫本地內部類的一種語法方便的方法。 大多數情況下,局部內部類在每次運行它的方法時都被實例化一次。 那麼,如果我們可以將局部內部類定義和它的單個實例化組合成一個方便的語法形式,那麼就很好了,如果我們不需要為類( 代碼包含的無用名稱越少,越好) 設計一個名稱。 匿名內部類允許兩種情況:


new *ParentClassName*(*constructorArgs*) {*members*}

這是返回一個未命名類的新實例的表達式,它擴展了 ParentClassName 。 你不能提供自己的構造函數;相反,它是隱式提供的,它只是調用超級構造函數,所以提供的參數必須符合超級構造函數。 ( 如果父對象包含多個構造函數,則調用"最簡單","最簡單"由相當複雜的規則集決定) 。在detail--just中,不值得學習NetBeans或者 Eclipse 告訴你什麼。

或者,你可以指定一個介面來實現:


new *InterfaceName*() {*members*}

這樣的聲明創建了一個未命名類的新實例,它擴展了對象並實現了 。InterfaceName 。 同樣,你不能提供自己的構造函數;在本例中,Java隱式提供了一個 no-arg,do-nothing構造函數( 所以在這種情況下不會有構造函數參數) 。

即使不能給匿名內部類提供構造函數,仍然可以使用初始值設定項( 放置在任何方法外部的{} 塊) 進行任何設置。

很明顯,匿名內部類只是用一個實例創建本地內部類的不太靈活的方法。 如果你想要一個本地內部類實現多個介面或實現介面,擴展一些類除了對象或指定自己的構造函數,你被創建常規命名本地內部類。

我認為在上面的答案中,真正的差異並不明顯。

首先獲取術語:

  • 嵌套類是包含在源代碼級別的另一個類中的類。
  • 它是靜態的如果你聲明靜態修飾符。
  • 一個non-static嵌套類被稱為內部類。 ( 我使用non-static嵌套類。)

馬丁的回答是正確的。 但是,實際的問題是: 聲明嵌套類靜態或者非靜態的目的是什麼?

你使用靜態嵌套類如果你只是想讓你一起如果他們一起屬於局部或者嵌套類是專門用於封閉類。 靜態嵌套類和每個其他類之間沒有語義差異。

Non-static嵌套類是不同的野獸。 類似於匿名內部類,此類嵌套類實際上是閉包。 這意味著它們捕獲周圍的作用域和封閉實例,並使它的可以訪問。 也許一個例子會闡明。 請查看容器的這個存根:


public class Container {
 public class Item{
 Object data;
 public Container getContainer(){
 return Container.this;
 }
 public Item(Object data) {
 super();
 this.data = data;
 }

 }

 public static Item create(Object data){
//does not compile since no instance of Container is available
 return new Item(data);
 }
 public Item createSubItem(Object data){
//compiles, since 'this' Container is available
 return new Item(data);
 }
}

在本例中,你希望從子項目到父容器的引用。 使用non-static嵌套類,無需一些工作就可以工作。 你可以使用語法 Container.this 訪問容器的封閉實例。

以下更嚴格的解釋:

如果你查看編譯器為( non-static ) 嵌套類生成的Java位元組碼,它可能會變得更加清晰:


//class version 49.0 (49)
//access flags 33
public class Container$Item {

//compiled from: Container.java
//access flags 1
 public INNERCLASS Container$Item Container Item

//access flags 0
 Object data

//access flags 4112
 final Container this$0

//access flags 1
 public getContainer() : Container
 L0
 LINENUMBER 7 L0
 ALOAD 0: this
 GETFIELD Container$Item.this$0 : Container
 ARETURN
 L1
 LOCALVARIABLE this Container$Item L0 L1 0
 MAXSTACK = 1
 MAXLOCALS = 1

//access flags 1
 public <init>(Container,Object) : void
 L0
 LINENUMBER 12 L0
 ALOAD 0: this
 ALOAD 1
 PUTFIELD Container$Item.this$0 : Container
 L1
 LINENUMBER 10 L1
 ALOAD 0: this
 INVOKESPECIAL Object.<init>() : void
 L2
 LINENUMBER 11 L2
 ALOAD 0: this
 ALOAD 2: data
 PUTFIELD Container$Item.data : Object
 RETURN
 L3
 LOCALVARIABLE this Container$Item L0 L3 0
 LOCALVARIABLE data Object L0 L3 2
 MAXSTACK = 2
 MAXLOCALS = 3
}

你可以看到編譯器創建一個隱藏的欄位 Container this$0 。 在構造函數中設置了一個額外的參數,它具有類型容器的附加參數來指定封閉實例。 在源中看不到此參數,但編譯器隱式生成它為嵌套類。

馬丁的例子


OuterClass.InnerClass innerObject = outerObject.new InnerClass();

將被編譯到類似於位元組碼的調用中


new InnerClass(outerObject)

為了滿足完整性:

一個匿名類 non-static嵌套類的一個完美的例子,只是沒有與之關聯的名字,以後不能引用。

我認為以上的答案解釋的真正區別一個嵌套類和靜態嵌套類的應用程序設計:

概述

可以非靜態的或靜態嵌套類和在每種情況下是一個類中定義另一個類。 嵌套類應該只存在服務是封閉類,如果一個嵌套類被其他類( 不只是封閉的) 有用,應該聲明為一個頂級類。

差異

非靜態嵌套類 : 隱式關聯包含類的封閉實例,這意味著可以調用方法和訪問封閉實例的變數。 非靜態嵌套類的一個常見用法是定義適配器類。

靜態嵌套類: 不能訪問封閉類實例並調用它,因此當嵌套類不需要訪問封閉類的實例時,應該使用它。 靜態嵌套類的一個常見用途是實現外部對象的組件。

結束語

因此,從設計角度來看,兩者之間的主要區別是: 非靜態嵌套類可以訪問容器類的實例,而靜態不能 。

我認為,通常遵循的約定是:

  • 頂級類是一個靜態類中的 嵌套類
  • 頂級類中的非靜態類內部類,進一步有兩個更多的形式:
    • 當地類——命名類聲明的一塊像一個方法或者構造函數體內
    • 匿名類 - 它的實例在表達式和語句中創建的未命名類

然而,還沒有其他值得記住的 。: 。

  • 頂級類和靜態嵌套類在語義上相同,但在靜態嵌套類的情況下,它可以對私有靜態欄位/它的外部 [parent] 類的方法進行靜態引用,反之亦然。

  • 內部類可以訪問外部 [parent] 類的封閉實例的實例變數。 然而,並非所有內部類都有封閉實例,例如靜態上下文中的內部類,如靜態初始化塊中使用的匿名類,不包括。

  • 默認情況下,匿名類擴展父類或者實現父介面,沒有進一步的子句來擴展任何其他類或者實現任何其他介面。 那麼,

    • new YourClass(){}; 意味著 class [Anonymous] extends YourClass {}
    • new YourInterface(){}; 意味著 class [Anonymous] implements YourInterface {}

我覺得更大的問題是一個要使用的問題? 這主要取決於你所處理的場景,但閱讀 @jrudolph 給出的回復可以幫助你做出一些決定。

簡而言之,我們需要 nested classes 主要是因為Java不提供 closures

nested classes 是在另一個封閉類的體內定義的類。 它們有兩種類型- static and non-static

它們被視為封閉類的成員,因此你可以指定四個訪問說明符的任何一個 private, package, protected, or public 我們沒有top-level類,它只能聲明為 public or package

Inner classes aka Non-stack classes 即使 Static nested classes 沒有訪問頂級類的其他成員的許可權,也可以訪問頂級類的其他成員。


public class OuterClass {
 public static class Inner1 {
 }
 public class Inner2 {
 }
}

Inner1是我們的靜態內部類,而Inner2是我們的內部類,它不是靜態的。 它們之間的關鍵區別是,你不能創建一個沒有外部的Inner2實例,因為你可以獨立創建一個Inner1對象。

你什麼時候使用內部課程。

考慮 Class AClass B 相關的情況,Class B 需要訪問 Class A 成員,並且 Class B 只與 Class A 相關。 將內部類別分為磅。

要創建內部類的實例,你需要創建外部類的實例。


OuterClass outer = new OuterClass();
OuterClass.Inner2 inner = OuterClass.new Inner2();

你什麼時候使用靜態內部類。

當你知道它與 enclosing class/top class的實例沒有任何關係時,你將定義一個靜態內部類。 如果內部類不使用外部類的方法或者欄位,則只是浪費空間,因此使它的成為靜態的。

例如要為靜態嵌套類創建一個對象,請使用以下語法:


OuterClass.Inner1 nestedObject = new OuterClass.Inner1();

static nested class的優點是它不需要包含類/頂級類的對象。 這可以幫助你減少應用程序在運行時創建的對象數量。

創建外部類實例時創建內部類的實例。 因此內部類的成員和方法可以訪問外部類實例( 對象)的成員和方法。 當外部類的實例超出範圍時,內部類實例也會停止存在。

靜態嵌套類沒有具體實例。 在第一次使用( 就像靜態方法) 時載入它。 它是一個完全獨立的實體,它的方法和變數沒有對外部類實例的任何訪問。

靜態嵌套類與外部對象不耦合,它們更快,並且不會佔用堆/堆棧內存,因為不需要創建此類的實例。 因此,經驗法則是嘗試定義靜態嵌套類,使用盡可能有限的範圍( 私有> = 類> = 受保護的> = 公共),然後將它的轉換為內部類( 通過刪除""標識符) 並鬆開作用域,如果確實需要。

嵌套靜態類的使用在某些情況下可能有用。

靜態屬性在類通過構造函數實例化之前得到實例化,嵌套靜態類內部的靜態屬性似乎直到類的構造函數被調用,或者至少在屬性被標記為'final'之後才被實例化。

請考慮以下示例:


public class C0 {

 static C0 instance = null;

//Uncomment the following line and a null pointer exception will be
//generated before anything gets printed.
//public static final String outerItem = instance.makeString(98.6);

 public C0() {
 instance = this;
 }

 public String makeString(int i) {
 return ((new Integer(i)).toString());
 }

 public String makeString(double d) {
 return ((new Double(d)).toString());
 }

 public static final class nested {
 public static final String innerItem = instance.makeString(42);
 }

 static public void main(String[] argv) {
 System.out.println("start");
//Comment out this line and a null pointer exception will be
//generated after"start" prints and before the following
//try/catch block even gets entered.
 new C0();
 try {
 System.out.println("retrieve item:" + nested.innerItem);
 }
 catch (Exception e) {
 System.out.println("failed to retrieve item:" + e.toString());
 }
 System.out.println("finish");
 }
}

即使'嵌套'和'innerItem'都被聲明為'靜態 final'。 在類實例化之後,nested.innerItem的設置才會發生,你可以通過註釋和取消註釋我引用的行,在上面看到。 'outerItem'同樣不適用。

至少這就是我在 Java 6.0中看到的。

在創建實例時,使用外部類的對象引用創建非靜態內部類的實例。 這意味著它有inclosing實例。 但是靜態內部類的實例是用外部類的引用創建的,而不是外部類的引用。 這意味著它沒有inclosing實例。

例如:


class A
{
 class B
 {
//static int x; not allowed here….. 
 }
 static class C
 {
 static int x;//allowed here
 }
}

class Test
{
 public static void main(String… str)
 {
 A o=new A();
 A.B obj1 =o.new B();//need of inclosing instance

 A.C obj2 =new A.C();

//not need of reference of object of outer class….
 }
}

嵌套類:類內部的類

類型:

  1. 靜態嵌套類
  2. Non-static嵌套類 [Inner class ]

差異:

Non-static嵌套類 [Inner class]

內部類的non-static嵌套類對象存在於外部類的對象內。 內部類可以訪問外部類的數據成員。 要創建內部類的對象,首先必須創建對象的外部類。


outerclass outerobject=new outerobject();
outerclass.innerclass innerobjcet=outerobject.new innerclass(); 

靜態嵌套類

在內部類的靜態嵌套類對象中不需要外部類對象,因為"靜態"表示不需要創建對象。


class outerclass A {
 static class nestedclass B {
 static int x = 10;
 }
}

如果你想訪問,則在內部方法中寫入


 outerclass.nestedclass.x; i.e. System.out.prinltn( outerclass.nestedclass.x);

...