题 Java内部类和静态嵌套类


Java中的内部类和静态嵌套类之间的主要区别是什么?设计/实施是否在选择其中一个方面起作用?


1468
2017-09-16 08:22


起源


约书亚布洛赫的回答是 有效的Java  读 item 22 : Favor static member classes over non static - Raymond Chenon


答案:


来自 Java教程

嵌套类分为两类:静态和非静态。声明为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() { ... } 是一个 在静态上下文中定义的内部类 并没有封闭的实例。


1482
2017-09-16 08:28



请注意,您也可以直接导入静态嵌套类,即您可以(在文件的顶部): import OuterClass.StaticNestedClass;  然后参考课程 只是 作为OuterClass。 - Camilo Díaz Repka
@Martin是否有任何特定的技术名称来创建内部类的这个习惯用法 OuterClass.InnerClass innerObject = outerObject.new InnerClass();? - Geek
那么私有静态嵌套类有什么用?我认为我们不能像OuterClass.StaticNestedClass那样从外面实例化它nestedObject = new OuterClass.StaticNestedClass(); - RoboAlex
@Ilya Kogan:我的意思是我知道我们可以访问父类的字段静态或非静态(如果子类是非静态类)我已经使用了他们的功能几次,但我想知道什么样的内存模型他们跟着? OOP概念是否将它们作为一个单独的概念?如果不是他们真正适合OOP的地方。好像我的朋友班:) - Mubashar Ahmad
@martin,我知道我来这个线程的时间非常晚,但是:它是你的变量,a,这是类的静态字段,A。这并不意味着'new A(){int t'创建的匿名类(){返回2;除了我简单地将任何其他对象分配给静态字段a之外,还是静态的,如:在类B中{静态void main(字符串s){Aa = new A()}}(A和B在同一个中) package)这不会使A成为静态类。在您链接的主题中引用的参考文献中的“静态上下文”一词非常模糊。事实上,在该主题的许多评论中都注意到了这一点。 - GrantRobertson


Java教程说

术语:嵌套类是   分为两类:静态   和非静态的。嵌套类   声明为static的简单调用   静态嵌套类。非静态   嵌套类称为内部   类。

一般来说,术语“嵌套”和“内部”可以被大多数程序员互换使用,但我会使用正确的术语“嵌套类”,它涵盖了内部和静态。

类可以嵌套 无限的,例如类A可以包含类B,其中包含类C,其中包含类D等。但是,多级类嵌套很少见,因为它通常是糟糕的设计。

您可以创建嵌套类有三个原因:

  • 组织:有时将类排序到另一个类的命名空间似乎是最明智的,特别是当它不会在任何其他上下文中使用时
  • 访问:嵌套类对其包含类的变量/字段具有特殊访问权限(确切地说,哪些变量/字段取决于嵌套类的类型,无论是内部还是静态)。
  • 方便:必须为每个新类型创建一个新文件是麻烦的,特别是当该类型只在一个上下文中使用时

Java中的四种嵌套类。简而言之,它们是:

  • 静态类:声明为另一个类的静态成员
  • 内心阶级:声明为另一个类的实例成员
  • 当地的内部阶级:在另一个类的实例方法中声明
  • 匿名的内部阶级:类似于本地内部类,但写为返回一次性对象的表达式

让我详细说明一下。


静态类

静态类是最容易理解的类,因为它们与包含类的实例无关。

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个使用包含类作为其命名空间的hanger, 例如 班上 山羊 声明为类的静态成员 犀牛 在包中 比萨 众所周知的名字 pizza.Rhino.Goat

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

坦率地说,静态类是一个非常没用的功能,因为类已经被包分成了命名空间。创建静态类的唯一真正可能的原因是这样的类可以访问其包含类的私有静态成员,但我发现这是静态类特性存在的相当蹩脚的理由。


内在的类

内部类是声明为另一个类的非静态成员的类:

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

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

与静态类一样,内部类被称为包含类名的限定, pizza.Rhino.Goat,但在包含类中,它可以通过其简单的名称来识别。但是,内部类的每个实例都绑定到其包含类的特定实例:上面, 山羊 创建于 杰瑞,隐含地绑在了 犀牛 例 这个 在 杰瑞。否则,我们进行关联 犀牛 实例化时实例显式 山羊

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,你可以使用 这个 引用其成员, 例如Rhino.this.barry


本地内部课程

本地内部类是在方法体中声明的类。这样的类只在其包含方法中是已知的,因此它只能被实例化并且在其包含方法中访问其成员。获得的是本地内部类实例绑定并可以访问其包含方法的最终局部变量。当实例使用其包含方法的最终局部时,该变量将保留实例创建时保留的值,即使该变量已超出范围(这实际上是Java的粗略,有限版本的闭包)。

由于本地内部类既不是类或包的成员,也不会使用访问级别声明它。 (但要明确的是,它自己的成员具有像普通类一样的访问级别。)

如果在实例方法中声明了本地内部类,则内部类的实例化与包含方法所持有的实例相关联。 这个 在实例创建时,所以包含类的实例成员可以像实例内部类一样访问。简单地实例化本地内部类 通过 其名称, 例如 当地的内部阶级  被实例化为 新猫(),而不是新的this.Cat()正如您所料。


匿名内部类

匿名内部类是编写本地内部类的一种语法上方便的方法。最常见的是,每次运行包含方法时,本地内部类最多只被实例化一次。那么,如果我们可以将本地内部类定义和它的单个实例化组合成一种方便的语法形式,那将是很好的,如果我们不必为该类想出一个名称也会很好(无用的更少)你的代码包含的名称越多越好)。匿名内部类允许这两件事:

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

这是一个返回未扩展的未命名类的新实例的表达式 ParentClassName。你不能提供自己的构造函数;相反,一个是隐式提供的,只是调用超级构造函数,因此提供的参数必须适合超级构造函数。 (如果父级包含多个构造函数,则称为“最简单”的构造函数,“最简单”,由一组相当复杂的规则决定,不值得详细学习 - 只需注意NetBeans或Eclipse告诉您的内容。)

或者,您可以指定要实现的接口:

new *InterfaceName*() {*members*}

这样的声明创建了一个未命名类的新实例,它扩展了Object和implements InterfaceName中。同样,你不能提供自己的构造函数;在这种情况下,Java隐式地提供了一个no-arg,do-nothing构造函数(因此在这种情况下永远不会有构造函数参数)。

即使你不能给匿名内部类一个构造函数,你仍然可以使用初始化块(在任何方法之外放置一个{}块)进行任何你想要的设置。

要明确的是,匿名内部类只是一种使用一个实例创建本地内部类的灵活性较低的方法。如果你想要一个实现多个接口的本地内部类,或者在扩展某个类以外的其他类时实现接口 目的 或者它指定了自己的构造函数,你不得不创建一个常规命名的本地内部类。


525
2017-09-16 09:24



很棒的故事,谢谢。但它有一个错误。您可以通过Rhino.this.variableName从实例内部类访问外部类的字段。 - Thirler
虽然您无法为匿名内部类提供自己的构造函数,但您可以使用双括号初始化。 c2.com/cgi/wiki?DoubleBraceInitialization - Casebash
很好的解释,但我不同意静态的内部阶级毫无价值。看到 rwhansen.blogspot.com/2007/07/... 对于构建器模式的巨大变化,它很大程度上取决于静态内部类的使用。 - Mansoor Siddiqui
我也不同意静态内部类是没有价值的:如果你想在内部类中使用枚举,你必须使内部类静态化。 - Someone Somewhere
私有静态嵌套类也非常有用:你想拥有它们,但是你不想公开它们。像LinkedList <T>中的Entry <T>,或者Activity(Android)中的AsyncTasks等... - Lorenzo Dematté


我不认为上述答案中真正的区别变得清晰。

首先要使条款正确:

  • 嵌套类是一个类,它包含在源代码级别的另一个类中。
  • 如果你用它声明它是静态的 静态的 修改。
  • 非静态嵌套类称为内部类。 (我留在非静态嵌套类。)

到目前为止,马丁的答案是正确的。但是,实际问题是:声明嵌套类静态的目的是什么?

你用 静态嵌套类 如果你只是想把你的类放在一起,如果它们属于一起局部放置,或者嵌套类是专门用在封闭类中的话。静态嵌套类与每个其他类之间没有语义差异。

非静态嵌套类 是一个不同的野兽。与匿名内部类相似,这种嵌套类实际上是闭包。这意味着他们捕获周围的范围及其封闭的实例,并使其可访问。或许一个例子可以澄清这一点。看到这个容器的存根:

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);
    }
}

在这种情况下,您希望从子项目到父容器的引用。使用非静态嵌套类,这可以在没有一些工作的情况下工作。您可以使用语法访问Container的封闭实例 Container.this

更多核心解释如下:

如果查看编译器为(非静态)嵌套类生成的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。这是在构造函数中设置的,该构造函数具有Container类型的附加参数以指定封闭实例。您无法在源中看到此参数,但编译器会为嵌套类隐式生成它。

马丁的例子

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

会被编译成类似的调用(在字节码中)

new InnerClass(outerObject)

为了完整起见:

一个匿名的课程  非静态嵌套类的完美示例,它没有与之关联的名称,以后不能引用。


124
2017-09-16 09:12



“静态嵌套类和其他所有类之间没有语义差异。”除了嵌套类可以看到父类的私有字段/方法,父类可以看到嵌套的私有字段/方法。 - Brad Cupit
非静态内部类是否可能导致大量内存泄漏?就像在每次创建监听器时一样,你创建了一个漏洞? - G_V
@G_V肯定存在内存泄漏的可能性,因为内部类的实例保持对外部类的引用。这是否是实际问题取决于对外部类和内部类的实例的引用的位置和方式。 - jrudolph


我认为上述答案都没有向您解释嵌套类和静态嵌套类在应用程序设计方面的真正区别:

概观

嵌套类 在每种情况下都可以是非静态的或静态的 是在另一个类中定义的类嵌套类应该只存在于封闭类中,如果嵌套类对其他类(不仅是封闭的)有用,则应声明为顶级类。

区别

非静态嵌套类 :与包含类的封闭实例隐式关联,这意味着可以调用封闭实例的方法和访问变量。非静态嵌套类的一个常见用途是定义Adapter类。

静态嵌套类 :无法访问封闭的类实例并在其上调用方法,因此应在嵌套类不需要访问封闭类的实例时使用。静态嵌套类的一个常见用途是实现外部对象的组件。

结论

因此,从设计的角度来看,两者之间的主要区别是: 非静态嵌套类可以访问容器类的实例,而静态则不能


82
2018-05-27 07:43



:从你的结论“虽然静态不能”,甚至不是容器的静态实例?当然? - VedX
静态嵌套类的常见用法是RecyclerView和ListView中的ViewHolder设计模式。 - Hamzeh Soboh
在许多情况下,简短的答案更清晰,更好。这是一个例子。 - Eric Wang


简单来说,我们需要嵌套类主要是因为Java不提供闭包。

嵌套类是在另一个封闭类的主体内定义的类。它们有两种类型 - 静态和非静态。

它们被视为封闭类的成员,因此您可以指定四个访问说明符中的任何一个 - private, package, protected, public。我们没有这种奢侈的顶级课程,只能申报 public 或包私密。

内部类也称为非堆栈类可以访问顶级类的其他成员,即使它们被声明为私有,而静态嵌套类也无法访问顶级类的其他成员。

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

Inner1 是我们的静态内部阶级和 Inner2 是我们的内在阶级,不是静态的。他们之间的关键区别,你不能创建一个 Inner2 没有外面的实例,你可以创建一个 Inner1 对象独立。

你什么时候使用内心课?

想想一下这种情况 Class A 和 Class B 有关系, Class B 需要访问 Class A 成员,和 Class B 只与...有关 Class A。内部课程进入了画面。

要创建内部类的实例,需要创建外部类的实例。

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

要么

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

你什么时候使用静态内部类?

当您知道它与封闭类/顶级类的实例没有任何关系时,您将定义一个静态内部类。如果你的内部类不使用外部类的方法或字段,那只是浪费空间,所以将它静态化。

例如,要为静态嵌套类创建对象,请使用以下语法:

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

静态嵌套类的优点是它不需要包含类/顶级类的对象。这可以帮助您减少应用程序在运行时创建的对象数。


29
2017-10-17 22:35



你的意思是 OuterClass.Inner2 inner = outer.new Inner2();? - Erik Allik
static inner 这是一个矛盾。 - user207421
内部类也不称为“非堆栈类”。不要对非代码的文本使用代码格式,并将其用于文本。 - user207421


我认为,通常遵循的惯例是:

  • 静态类 在顶级课程中是一个 嵌套类
  • 非静态类 在顶级课程中是一个 内心阶级,进一步 还有两种形式:
    • 当地班级  - 在块内部声明的命名类,如方法或构造函数体
    • 匿名课  - 未命名的类,其实例在表达式和语句中创建

但是,其他很少 指出要记住 是:

  • 顶级类和静态嵌套类在语义上是相同的,除了在静态嵌套类的情况下,它可以对其外部[父]类的私有静态字段/方法进行静态引用,反之亦然。

  • 内部类可以访问Outer [parent]类的封闭实例的实例变量。但是,并非所有内部类都包含实例,例如静态上下文中的内部类,如静态初始化程序块中使用的匿名类,则不然。

  • 默认情况下,匿名类扩展父类或实现父接口,并且没有其他子句可以扩展任何其他类或实现更多接口。所以,

    • new YourClass(){};手段 class [Anonymous] extends YourClass {}
    • new YourInterface(){};   手段 class [Anonymous] implements YourInterface {}

我觉得更大的问题仍然是开放哪一个使用什么时候?那么这主要取决于你正在处理的场景,但阅读@jrudolph给出的答案可能会帮助你做出一些决定。


24
2017-12-16 11:38





以下是Java内部类和静态嵌套类之间的主要差异和相似之处。

希望能帮助到你!

内心阶级

  • 可以访问 到外层 实例和静态 方法和领域
  • 与封闭类的实例相关联 所以要实例化它首先需要一个外部类的实例(注意  关键字地点):

    Outerclass.InnerClass innerObject = outerObject.new Innerclass();
    
  • 不能 定义任何 静态成员 本身

  • 不能 有  要么 接口 宣言

静态嵌套类

  • 无法访问 外层阶级  方法或领域

  • 与封闭类的任何实例无关 所以要实例化它:

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

相似

  • 内课 甚至可以访问 私人领域和方法 的 外层阶级
  • 也是 外课 可使用 私人领域和方法 的 内部阶级
  • 这两个类都可以具有私有,受保护或公共访问修饰符

为什么使用嵌套类?

根据Oracle文档,有几个原因(完整的文档):

  • 这是一种逻辑分组仅在一个地方使用的类的方法: 如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“帮助类”使得它们的包更加简化。

  • 它增加了封装: 考虑两个顶级类A和B,其中B需要访问A的成员,否则这些成员将被声明为私有。通过将类B隐藏在类A中,可以将A的成员声明为私有,并且B可以访问它们。另外,B本身可以隐藏在外面。

  • 它可以使代码更具可读性和可维护性: 在顶级类中嵌套小类会使代码更接近于使用它的位置。


23
2017-12-31 20:53





嵌套类:类内部的类

类型:

  1. 静态嵌套类
  2. 非静态嵌套类[Inner class]

区别:

非静态嵌套类[Inner class]

在非静态嵌套类中,内部类的对象存在于外部类的对象中。因此外部类的数据成员可以被内部类访问。因此要创建内部类的对象,我们必须首先创建外部类的对象。

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

静态嵌套类

在内部类的静态嵌套类对象中不需要外部类的对象,因为单词“static”表示不需要创建对象。

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

如果要访问x,请编写以下内部方法

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

13
2018-04-04 10:14





在创建外部类的实例时,将创建内部类的实例。因此,内部类的成员和方法可以访问外部类的实例(对象)的成员和方法。当外部类的实例超出范围时,内部类实例也不再存在。

静态嵌套类没有具体实例。它刚刚在第一次使用时加载(就像静态方法一样)。它是一个完全独立的实体,其方法和变量无法访问外部类的实例。

静态嵌套类不与外部对象耦合,它们更快,并且它们不占用堆/堆栈内存,因为它不需要创建此类的实例。因此经验法则是尝试定义静态嵌套类,尽可能限制范围(private> = class> = protected> = public),然后将其转换为内部类(通过删除“静态”标识符)并放松范围,如果真的有必要的话。


10
2017-09-16 08:58



第一句话不正确。没有这样的东西'该 内部类的实例',并且可以在实例化外部类之后的任何时间创建它的实例。第二句不是从第一句开始。 - user207421


关于嵌套静态类的使用有一些微妙之处,这些静态类在某些情况下可能很有用。

而静态属性在通过构造函数实例化类之前实例化, 嵌套静态类中的静态属性似乎直到之后才被实例化 类的构造函数被调用,或者至少在第一次引用属性之后才被调用, 即使它们被标记为“最终”。

考虑这个例子:

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");
    }
}

即使'nested'和'innerItem'都被声明为'static final'。那个设定 在实例化类之后(或至少在类之后),才会发生nested.innerItem 直到首次引用嵌套静态项之后,就像你自己看到的那样 通过评论和取消注释我在上面引用的行。同样不成立 对于'outerItem'是真的。

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


10
2018-03-26 12:38