支持使用静态成员类而不是非静态类

嵌套类(nested class)是在另一个类中定义的类。 嵌套类应该只存在于其宿主类(enclosing class)中。

如果一个嵌套类在其他一些情况下是有用的,那么它应该是一个顶级类。

有四种嵌套类:

  1. 静态成员类
  2. 非静态成员类
  3. 匿名类
  4. 局部类

除了第一种以外,剩下的三种都被称为内部类(inner class)。

静态成员类

静态成员类是最简单的嵌套类。

最好把它看作是一个普通的类,恰好在另一个类中声明,并且可以访问所有宿主类的成员,甚至是那些被声明为私有类的成员。

静态成员类是其宿主类的静态成员,并遵循与其他静态成员相同的可访问性规则。

如果它被声明为 private,则只能在宿主类中访问,等等。

静态成员类的一个常见用途是作为公共帮助类,仅在与其外部类一起使用时才有用。

例如,考虑一个描述计算器支持的操作的枚举类型(详见第 34 条)。 Operation 枚举应该是 Calculator 类的公共静态成员类。 Calculator 客户端可以使用 Calculator.Operation.PLUSCalculator.Operation.MINUS 等名称来引用操作。

非静态成员类

在语法上,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有 static 修饰符。

尽管句法相似,但这两种嵌套类是非常不同的。

非静态成员类的每个实例都隐含地与其包含的类的宿主实例相关联。 在非静态成员类的实例方法中,可以调用宿主实例上的方法,或者使用限定的构造获得对宿主实例的引用。

如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有宿主实例的情况下创建非静态成员类的实例。

非静态成员类实例和其宿主实例之间的关联是在创建成员类实例时建立的,并且之后不能被修改。

通常情况下,通过在宿主类的实例方法中调用非静态成员类构造方法来自动建立关联。 尽管很少有可能使用表达式 enclosingInstance.new MemberClass(args) 手动建立关联。 正如你所预料的那样,该关联在非静态成员类实例中占用了空间,并为其构建添加了时间开销。

非静态成员类的一个常见用法是定义一个 Adapter ,它允许将外部类的实例视为某个不相关类的实例。

例如,Map 接口的实现通常使用非静态成员类来实现它们的集合视图,这些视图由 MapkeySetentrySetvalues 方法返回。

示例代码Item23Example01.java:集合接口使用非静态成员类来实现它们的迭代器。

匿名类

一个匿名类没有名字。 它不是其宿主类的成员。 它不是与其他成员一起声明,而是在使用时同时声明和实例化。 在表达式合法的代码中,匿名类是允许的。 当且仅当它们出现在非静态上下文中时,匿名类才会封装实例。 但是,即使它们出现在静态上下文中,它们也不能有除常量型变量之外的任何静态成员,这些常量型变量包括 final 的基本类型,或者初始化常量表达式的字符串属性。

匿名类的适用性有很多限制。 除了在声明的时候之外,不能实例化它们。 你不能执行 instanceof 方法测试或者做任何其他需要你命名的类。 不能声明一个匿名类来实现多个接口,或者继承一个类并同时实现一个接口。 匿名类的客户端不能调用除父类型继承的成员以外的任何成员。 因为匿名类在表达式中出现,所以它们必须保持简短 —— 约十行或更少 —— 否则可读性将受到影响。

在将 lambda 表达式添加到 Java 之前,匿名类是创建小函数对象和处理对象的首选方法,但 lambda 表达式现在是首选。 匿名类的另一个常见用途是实现静态工厂方法(请参阅 Item20Example02.java )。

局部类

局部类是四种嵌套类中使用最少的。

一个局部类可以在任何可以声明局部变量的地方声明,并遵守相同的作用域规则。

局部类与其他类型的嵌套类具有共同的属性。 像成员类一样,他们有名字,可以重复使用。

就像匿名类一样,只有在非静态上下文中定义它们时,它们才会包含实例,并且它们不能包含静态成员。

像匿名类一样,应该保持简短,以免损害可读性。

总结

回顾一下,有四种不同的嵌套类,每个都有它的用途。

如果一个嵌套的类需要在一个方法之外可见,或者太长而不能很好地适应一个方法,使用一个成员类。

如果一个成员类的每个实例都需要一个对其宿主实例的引用,使其成为非静态的; 否则,使其静态。

假设这个类属于一个方法内部,如果你只需要从一个地方创建实例,并且存在一个预置类型来说明这个类的特征,那么把它作为一个匿名类;否则,把它变成局部类。