Java内部类和静态嵌套类

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


从Java教程:

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

静态嵌套类可以使用封闭类名来访问:

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教程说:

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

按照一般的说法,术语“嵌套”和“内部”可以被大多数程序员交换使用,但是我将使用正确的术语“嵌套类”,它涵盖内部和静态。

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

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

  • 组织:有时,将一个类排序到另一个类的名称空间似乎是最明智的,尤其是当它不会用于其他任何环境时
  • 访问:嵌套类对其包含的类的变量/字段有特殊的访问权(正是哪些变量/字段取决于嵌套类的类型,无论是内部还是静态)。
  • 方便:不得不为每个新类型创建一个新文件都很麻烦,特别是当这个类型只用于一个上下文时
  • Java中四种嵌套类 。 简而言之,它们是:

  • 静态类 :声明为另一个类的静态成员
  • 内部类 :声明为另一个类的实例成员
  • 本地内部类 :在另一个类的实例方法声明
  • 匿名内部类 :像本地内部类一样,但是写成一个返回一次性对象的表达式
  • 让我详细说明一下。


    静态类

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

    静态类是被声明为另一个类的静态成员的类。 就像其他静态成员一样,这样的类实际上只是一个挂钩,它使用包含类作为它的名称空间,例如,声明为类Rhino的静态成员的类Goat在包中比萨由名称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实例显式化:

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

    (请注意,您将新内核类型称为Goat,并使用新奇妙的语法:Java推断来自犀牛部分的包含类型,并且,新的rhino.Goat()对我来说也会更有意义。)

    那么这对我们有什么好处呢? 那么,内部类实例可以访问包含类实例的实例成员。 这些封闭的实例成员通过它们的简单名称在内部类中引用,而不是通过this(在内部类中引用内部类实例,而不是关联的包含类实例):

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

    在内部类中,可以将这个包含类称为Rhino.this,并且可以使用它来引用其成员,例如Rhino.this.barry。


    本地内部类

    本地内部类是在方法体中声明的类。 这样的类只在其包含的方法中是已知的,所以它只能被实例化,并且在其包含的方法中访问其成员。 增益是一个本地内部类实例绑定并可以访问其包含方法的最终局部变量。 当实例使用其包含方法的最后一个局部变量时,即使变量超出了范围(这实际上是Java原始的,限制版本的闭包),变量仍会保留实例创建时的值。

    因为本地内部类既不是类或包的成员,也不会声明具有访问级别。 (但是,要清楚自己的成员具有像普通课程那样的访问级别。)

    如果在实例方法中声明了一个本地内部类,那么在创建实例时,内部类的实例与包含方法this所持有的实例相关联,因此可以像在实例中一样访问包含类的实例成员内心阶层。 一个本地的内部类通过它的名字实例化,例如本地内部类Cat被实例化为新的Cat(),而不是像你期望的那样新建this.Cat()。


    匿名内部类

    匿名内部类是编写本地内部类的语法便捷方式。 最常见的情况是,每次运行包含的方法时,最多只会实例化一次本地内部类。 那么,如果我们能够将本地内部类定义和它的单一实例化合并成一个方便的语法形式,那将是很好的,如果我们不需要为该类考虑一个名字(更不利用您的代码包含的名称越多越好)。 一个匿名的内部类允许这些东西:

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

    这是一个表达式,返回一个扩展ParentClassName的未命名类的新实例。 你不能提供你自己的构造函数; 而是隐式提供的,它只是简单地调用超级构造函数,所以提供的参数必须适合超级构造函数。 (如果父项包含多个构造函数,那么调用“最简单”的构造函数,这是由一组相当复杂的规则确定的“最简单”规则,不值得深入研究 - 只需注意NetBeans或Eclipse告诉您的内容。)

    或者,您可以指定一个接口来实现:

    new *InterfaceName*() {*members*}
    

    这样的声明创建了一个扩展Object并实现InterfaceName的未命名类的新实例。 再次,你不能提供你自己的构造函数; 在这种情况下,Java隐含地提供了一个无参数,无所作为的构造函数(所以在这种情况下永远不会有构造函数参数)。

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

    要明确一个匿名的内部类是一种不太灵活的创建一个本地内部类的方法。 如果你想要一个实现多个接口的本地内部类,或者实现接口的同时扩展一些除Object之外的类或者指定它自己的构造函数,那么你就不能创建一个常规的命名本地内部类。


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

    首先让条款正确:

  • 嵌套类是在源代码级别包含在另一个类中的类。
  • 如果使用静态修饰符声明它是静态的
  • 一个非静态的嵌套类被称为内部类。 (我留在非静态嵌套类。)
  • 马丁的回答是正确的。 但是,实际的问题是:声明一个嵌套类是否静态的目的是什么?

    如果您只想将类保留在一起,或者如果嵌套类仅在封闭类中使用,则可以使用静态嵌套类。 静态嵌套类与其他类没有语义上的区别。

    非静态嵌套类是不同的野兽。 类似于匿名内部类,这样的嵌套类实际上是闭包。 这意味着他们捕捉周围的范围和他们的封闭实例并使其可访问。 也许一个例子会澄清这一点。 查看容器的这个存根:

    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)
    

    为了完整起见:

    匿名类一个非静态嵌套类的完美例子,它只是没有与之关联的名称,并且以后不能被引用。

    链接地址: http://www.djcxy.com/p/2681.html

    上一篇: Java inner class and static nested class

    下一篇: what are the legacy classes in Java?