Java 内部类和静态嵌套类

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

答案

Java 教程

嵌套类分为两类:静态和非静态。声明为静态的嵌套类简称为静态嵌套类。非静态嵌套类称为内部类。

静态嵌套类使用封闭的类名称访问:

OuterClass.StaticNestedClass

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

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

作为内部类实例的对象存在于外部类实例中。考虑以下类别:

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

InnerClass 的实例只能存在于 OuterClass 的实例中,并且可以直接访问其封闭实例的方法和字段。

要实例化内部类,必须首先实例化外部类。然后,使用以下语法在外部对象内创建内部对象:

OuterClass outerObject = new 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 教程说

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

通常,大多数程序员可以互换使用 “嵌套” 和 “内部” 这两个术语,但是我将使用正确的术语“嵌套类”,它涵盖内部和静态两个方面。

类可以无限嵌套,例如,类 A 可以包含类 B,其中类 B 包含类 C,类 C 包含类 D,等等。但是,很少有多个级别的类嵌套,这通常是不好的设计。

创建嵌套类的三个原因:

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

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

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

让我详细说明。


静态类

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

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,此类实际上只是一个使用包含类作为其命名空间的挂衣架, 例如 ,在披萨包中声明为Rhino类的静态成员的Goat类被称为pizza.Rhino.Goat

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

坦白地说,静态类是一个非常不值钱的功能,因为类已经被包划分为名称空间。创建静态类的唯一真正可能的理由是,此类可以访问其包含类的私有静态成员,但是我发现这对于存在静态类功能是一个很 la 脚的理由。


内部班级

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

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

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

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

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

(注意,您在奇怪的语法中将内部类型称为Goat :Java 从rhino部分推断出包含类型。而且,是的, 新的 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 Cat() ,而不是您可能期望的 new this.Cat()。


匿名内部类

匿名内部类是编写本地内部类的一种在语法上方便的方法。最常见的是,本地内部类每次在其包含方法运行时最多仅实例化一次。那么,如果我们可以将本地内部类的定义及其单个实例组合为一种方便的语法形式,那就太好了,如果我们不必为该类想一个名字(无用的次数越少),那也就越好。您的代码包含的名称更好)。匿名内部类允许这两种情况:

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

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

另外,您可以指定一个接口来实现:

new *InterfaceName*() {*members*}

这样的声明创建了一个未命名类的新实例,该实例扩展了 Object 并实现InterfaceName 。同样,您不能提供自己的构造函数。在这种情况下,Java 隐式提供了无参,无所事事的构造函数(因此在这种情况下永远不会有构造函数参数)。

即使您不能为匿名内部类提供构造函数,也可以使用初始化程序块(放置在任何方法外部的 {} 块)进行所需的任何设置。

需要清楚的是,匿名内部类只是使用一个实例创建本地内部类的一种较不灵活的方式。如果您想要一个实现多个接口的本地内部类,或者在扩展除Object之外的某个类或指定其自己的构造函数的同时实现接口的本地内部类,则必须创建一个常规的命名本地内部类。

我认为以上答案并没有真正的区别。

首先弄清楚条款:

  • 嵌套类是在源代码级别包含在另一个类中的类。
  • 如果使用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);
    }
}

在这种情况下,您需要从子项到父容器的引用。使用非静态嵌套类,此方法无需进行任何工作。您可以使用语法Container.this访问封闭的 Container 实例。

以下是更多硬性解释:

如果查看编译器为(非静态)嵌套类生成的 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)

为了完整性:

匿名类刚刚没有与之关联的名称,并且不能在以后引用非静态嵌套类的一个很好的例子。

我认为上述答案都不能向您说明在应用程序设计方面嵌套类和静态嵌套类之间的真正区别:

概观

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

区别

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

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

结论

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

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

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

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

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

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

Inner1是我们的静态内部类,而Inner2是我们的非静态内部类。它们之间的主要区别在于,如果没有 Outer,就无法创建Inner2实例,因为可以单独创建Inner1对象。

您什么时候使用内部课程?

考虑一下Class AClass B相关的情况, Class B需要访问Class A成员,而Class B Class A仅与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();

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

这是 Java 内部类和静态嵌套类之间的主要区别和相似之处。

希望能帮助到你!

内部阶层

  • 可以访问外部类的实例和静态方法及字段
  • 与封闭类的实例相关联,因此要实例化它首先需要一个外部类的实例(请注意新的关键字 place):

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

  • 不能接口声明

静态嵌套类

  • 无法访问外部类实例方法或字段

  • 与封闭类的任何实例都不相关,因此要实例化它:

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

相似点

  • 两个内部类都可以访问私有字段和 外部类的 方法
  • 此外, 外部类可以访问私有字段和 内部类的 方法
  • 这两个类都可以具有私有,受保护或公共访问修饰符

为什么要使用嵌套类?

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

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

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

  • 这可能会导致代码更具可读性和可维护性:将小类嵌套在顶级类中会使代码更靠近使用位置。

我认为,通常遵循的约定是:

  • 顶级类中的静态类嵌套类
  • 顶层中的非静态类内部类 ,它还具有另外两种形式:
    • 本地类 - 在诸如方法或构造函数体之类的块内声明的命名类
    • 匿名类 - 未命名类,其实例在表达式和语句中创建

但是, 要记住的其他几点是:

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

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

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

    • new YourClass(){};表示class [Anonymous] extends YourClass {}
    • new YourInterface(){};表示class [Anonymous] implements YourInterface {}

我觉得这个更大的问题仍然悬而未决,什么时候使用?好吧,这主要取决于您正在处理的情况,但是阅读 @jrudolph 给出的回复可能会帮助您做出一些决定。

嵌套类:类内类

类型:

  1. 静态嵌套类
  2. 非静态嵌套类 [内部类]

区别:

非静态嵌套类 [内部类]

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

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

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

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

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

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

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

考虑以下示例:

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 中看到的。