协慌网

登录 贡献 社区

Java 内部类和静态嵌套类

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

答案

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() { ... }在静态上下文中定义内部类,并且没有封闭的实例。

Java 教程说

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

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

类可以无限制地嵌套,例如,类 A 可以包含类 B,其包含类 C,其包含类 D 等。然而,多于一级的类嵌套是罕见的,因为它通常是糟糕的设计。

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

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

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

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

让我详细说明一下。


静态类

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

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个使用包含类作为其命名空间的挂钩, 例如 ,在包披萨中声明为类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 的限定类,但在包含类中,它可以通过其简单名称来识别 。然而,一个内部类的每一个实例被绑定到其包含类的特定实例:以上, 山羊杰里创建,被隐式地绑定到犀牛实例 杰里 。否则,我们在实例化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);
        }
    }
}

在内部类中,您可以将包含类的this引用为Rhino.this ,您可以使用来引用其成员, 例如 Rhino.this.barry


本地内部课程

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

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

如果局部内部类是一个实例方法声明,内部类的一个实例是依赖于在实例的创建时由含有方法的这一举行的实例,所以包含类的实例成员都像一个实例访问内心阶级。本地内部类只是通过其名称实例化, 例如本地内部类Cat被实例化为新的 Cat() ,而不是你想象的新的 this.Cat()。


匿名内部类

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

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

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

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

new *InterfaceName*() {*members*}

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

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

要明确的是,匿名内部类只是一种使用一个实例创建本地内部类的灵活性较低的方法。如果你想要一个实现多个接口的本地内部类,或者在扩展某个类而不是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)

为了完整起见:

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