术语:
嵌套类(nested class):指被定义在另一个类的内部的类。
嵌套类存在的目的应该只是为了它的外围类提供服务。如果嵌套类将来可能会用于其他的某个环境中,它应该是顶层类。嵌套类一共有四种:静态成员类、非静态成员类、匿名类、局部类。除了静态成员类外,其他的三种被称为内部类。
静态成员类应该被看作是普通的碰巧被饭锅在另一个类的内部的类,它可以访问外部类的所有成员。静态成员类是外围类的一个静态成员,遵守着可访问性规则。如果它被声名为私有的,它就只能在外围类的内部才可以被访问。这种类的一种常见的用法就是作为公有的辅助类,仅当它的外部类一起使用时才有意义。例如,一个描述了计算器支持的各种操作的枚举。Operation枚举应该是Calculator类的公有静态成员类,然后Calculator类的客户端就可以用诸如Calculator.Operation.PLUS这样的名称来引用这些操作。
非静态成员类的每个实例都隐含着对外围类的一个外围实例,在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。如果嵌套类的实例可以在它外围类的实例之外单独存在,那么这个嵌套类就必须是静态成员类,否则它就必须与某个外围类的实例相关联。
当非静态成员类的实例被创建的时候,它和外围实例之间的关联关系也随之被建立起来,而且这种关系在以后也不能再被修改了。通常情况下,当在外围类的某个实例方法的内部调用非静态成员类的构造器时,这种关联关系就被自动建立起来。使用表达式enclosingInstance.new MemberClass(args)来手动建立这种关系也是有可能的。这种关联关系需要消耗非静态成员类实例的空间,并且增加了构造的时间开销。非静态成员类的一个常见的用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关的类的实例。比如说Map的keySet、entrySet等方法所返回的。
如果声名成员类不要求访问外围类的实例,那么就要始终把它声名为static的。如果忘记了static修饰符,那么每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却仍然得以保留(由于内部类的引用)。私有静态成员类的一种常见的用法是用来代表外围类所代表的对象的组件。如果相关的类是导出类的公有的或受保护的成员,那么在静态和非静态成员类之间做出正确的选择是非常重要的。在这种情况下,该成员类就是导出的API元素,因此也就面临了升级和兼容等一系列的问题。
匿名类不同于Java中其他任何语法单元,它没有名子。它不是外围类的一个成员,它并不与其他的成员一起被声名而是在使用的同时被声名和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态环境中时,它才有外围实例。但是即使它们出现在静态环境中,也不可能拥有任何静态成员。匿名类除了在它们被声名的时候是无法将它们实例化的。不能执行instanceof测试或者做任何需要命名类的其他事情。无法声名匿名类来实现多个接口,或者扩展一个类。匿名类的客户端也无法调用任何成员,除了从它的超类型中继承得到的之外。由于它们出现在表达式中,它们必须保持简短---大约10行或者更少些,否则会影响可读性。
局部类用的最少的嵌套类,在任何可以声名局部变量的地方都可以声名局部类。并且局部类也遵守着同样的作用域约定。总之,如果一个嵌套类需要在单个方法之外仍是可见的,或者它太长了,不适合放在方法内部,就应该使用成员类,如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的。否则就做成静态的。假设这个嵌套类属于一个方法的内部,如果只需要在一个地方创建实例并且已经有了一个预置的类型可以说明这个类的特征,那就做成匿名类,否则就做成局部类。