Java内部类和静态嵌套类

新手上路,请多包涵

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

原文由 Omnipotent 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 690
2 个回答

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

原文由 Martin 发布,翻译遵循 CC BY-SA 4.0 许可协议

Java教程说

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

在通常的说法中,术语“嵌套”和“内部”被大多数程序员互换使用,但我将使用正确的术语“嵌套类”,它涵盖了内部和静态。

类可以 无限 嵌套,例如,A 类可以包含 B 类,B 类包含 C 类,C 类又包含 D 类,等等。但是,多于一层的类嵌套很少见,因为这通常是糟糕的设计。

创建嵌套类的三个原因:

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

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

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

让我详细说明一下。

静态类

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

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个衣架,它使用包含类作为其命名空间, _例如_,在包 pizza 中声明为类 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 被称为合格,但在包含类内部,它可以通过其简单名称来识别。但是,内部类的每个实例都绑定到其包含类的特定实例:上面,在 jerry 中创建的 Goat 隐式绑定到 jerry 中的 Rhino 实例 this 。否则,我们在实例化 Goat 时将关联的 Rhino 实例显式化:

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

(请注意,在奇怪的 语法中,您仅将内部类型称为 Goat :Java 从 rhino 部分推断出包含类型。而且,是的, new rhino.Goat() 对我来说也更有意义。)

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

 public class Rhino {

    private String barry;

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

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

局部内部类

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

因为局部内部类既不是类的成员也不是包的成员,所以它没有声明访问级别。 (但是要清楚,它自己的成员具有与普通类一样的访问级别。)

如果在实例方法中声明了局部内部类,则内部类的实例化在实例创建时绑定到包含方法的 this 持有的实例,因此包含类的实例成员可以像在实例中一样访问内部类。本地内部类仅 通过 其名称进行实例化, _例如_,本地内部类 Cat 被实例化为 new Cat() ,而不是您可能期望的 new this.Cat() 。

匿名内部类

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

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

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

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

 new *InterfaceName*() {*members*}

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

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

要清楚匿名内部类只是一种使用一个实例创建本地内部类的不太灵活的方法。如果您想要一个实现多个接口的本地内部类,或者在扩展 Object 以外的某个类或指定其自己的构造函数的同时实现接口,那么您将无法创建一个常规的命名本地内部类。

原文由 Jegschemesch 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题