Java tutorial says:
术语:嵌套类是
分为两类:静态
和非静态的。嵌套类
被声明为静态的被简单地称为
静态嵌套类。非静态
嵌套类称为内部
类。
一般来说,术语“嵌套”和“内部”被大多数程序员互换使用,但我将使用正确的术语“嵌套类”,它涵盖内部和静态。
类可以嵌套无限,例如A 类可以包含 B 类,B 类包含 C 类,C 类包含 D 类等。但是,多于一级的类嵌套很少见,因为它通常是糟糕的设计。
创建嵌套类的原因有以下三个:
- 组织:有时将一个类分类到另一个类的命名空间中似乎是最明智的做法,尤其是当它不会在任何其他上下文中使用时
- 访问:嵌套类对其包含类的变量/字段具有特殊访问权限(确切地说,哪些变量/字段取决于嵌套类的类型,无论是内部类还是静态类)。
- 方便:必须为每种新类型创建一个新文件很麻烦,尤其是当该类型仅在一个上下文中使用时
Java 中有四种嵌套类。简而言之,它们是:
-
静态类:声明为另一个类的静态成员
-
内部类:声明为另一个类的实例成员
-
本地内部类:在另一个类的实例方法中声明
-
匿名内部类:类似于本地内部类,但写成返回一次性对象的表达式
让我详细说明一下。
静态类
静态类是最容易理解的,因为它们与包含类的实例无关。
静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个使用包含类作为其命名空间的吊架,eg 类 Goat 声明为类的静态成员 pizza 包中的 em>Rhino 名为 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 隐式地与 Rhino 相关联 实例 this 在 jerry 中。否则,我们在实例化 Goat 时明确关联的 Rhino 实例:
Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();
(请注意,您在奇怪的 new 语法中将内部类型称为 Goat:Java 从 rhino 部分推断包含类型. 而且,是的,new rhino.Goat() 对我来说也更有意义。)
那么这对我们有什么好处呢?好吧,内部类实例可以访问包含类实例的实例成员。这些封闭的实例成员在内部类 via 中仅引用它们的简单名称,而不是 via 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 所持有的实例相关联,因此包含类的实例成员可以像在实例内部类中一样访问。一个本地内部类被简单地实例化通过它的名字,eg本地内部类Cat被实例化为new Cat(),不像你想象的那样 new this.Cat()。
匿名内部类
匿名内部类是编写本地内部类的一种语法方便的方法。最常见的是,每次运行其包含方法时,本地内部类最多只实例化一次。那么,如果我们可以将本地内部类定义和它的单个实例化组合成一种方便的语法形式,那就太好了,如果我们不必为类想一个名字(越少无用您的代码包含的名称越好)。匿名内部类允许以下两种情况:
new *ParentClassName*(*constructorArgs*) {*members*}
这是一个返回未命名类的新实例的表达式,该类扩展了 ParentClassName。您不能提供自己的构造函数;相反,一个是隐式提供的,它只是调用超级构造函数,所以提供的参数必须适合超级构造函数。 (如果父级包含多个构造函数,则调用“最简单”的构造函数,“最简单”由一组相当复杂的规则决定,不值得费心去详细学习——只需注意 NetBeans 或 Eclipse 告诉你的内容。)
或者,您可以指定要实现的接口:
new *InterfaceName*() {*members*}
这样的声明创建了一个未命名类的新实例,它扩展了 Object 并实现了 InterfaceName。同样,您不能提供自己的构造函数;在这种情况下,Java 隐式提供了一个无参数、什么都不做的构造函数(因此在这种情况下永远不会有构造函数参数)。
即使您不能为匿名内部类提供构造函数,您仍然可以使用初始化程序块(放置在任何方法之外的 {} 块)进行任何您想要的设置。
要清楚,匿名内部类只是用一个实例创建本地内部类的一种不太灵活的方法。如果您想要一个实现多个接口的本地内部类,或者在扩展 Object 以外的某个类或指定自己的构造函数的同时实现接口,那么您将无法创建一个常规的命名本地内部类。