嵌套类是在另一个类中定义的类。嵌套类应该只存在于为其封闭的类中提供服务。如果一个嵌套类在其他上下文中有用,它就应该是顶级类。有四种嵌套类:静态成员类,非静态成员类,匿名类和本地类。除了第一个以外的所有类型都称为内部类。该条目将告诉你什么适合使用哪种嵌套类及其原因。
静态成员类是最简单的嵌套类。最好将其视为普通类,这样恰好在另一个类中声明,并且可以访问所有封闭类的成员,甚至是那些声明为private的成员。静态成员类是其封闭类的静态成员,并遵守与其他静态成员相同的可访问性规则。如果它被声明为private,那么它只能在其封闭类中访问,以此类推。
静态成员类的一个常见用途是作为公共帮助程序类,仅与其外部类一起使用。例如,考虑一个描述计算器支持的操作的枚举(item34) .Operation枚举应该是Calculator类的公共静态成员类。然后Calculator的客户端可以使用诸如Calculator.Operation.PLUS 和 Calculator.Operation.MINUS.之类的名称来引用操作。
从语法上讲,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有静态修饰符。尽管语法相似,这两种嵌套类还是非常不同的。非静态成员类的每个实例都与其包含类的封闭实例有隐式关联。在非静态成员类的实例方法中,你可以使用受限制的this的构造[JLS, 15.8.4]调用封闭实例上的方法或获取对封闭实例的引用。如果嵌套类的实例可以与其封闭类的实例隔离存在,那么嵌套类必须是静态成员类:如果没有封闭实例,那么创建非静态成员类的实例是不可能的。
非静态成员类实例与其封闭实例之间的关联在创建成员类实例时建立,此后无法修改。通常,通过从封闭类的实例方法中调用非静态成员类构造方法来自动建立关联。尽管少见,但可以使用表达式enclosingInstance.new MemberClass(args)手动建立关联。正如你所期望的那样,该关联占用了非静态成员类实例中的空间并为其构造增加了时间。
非静态成员类的一个常见用途是用以定义一个适配器[Gamma95],它允许将外部类的实例视为某个不相关类的实例。例如,Map接口的实现通常使用非静态成员类来实现其集合视图,这些视图由Map的keySet,entrySet和values方法返回。类似地,集合接口的实现(比如Set和List)通常使用非静态成员类来实现它们的迭代器:
如果声明一个不需要访问封闭实例的成员类,请始终将静态修饰符放在其声明中,使之成为静态而不是非静态成员类。如果你忽略这个修饰符,每个实例都将对其封闭实例具有隐藏的无关引用。如前所述,存储此引用需要时间和空间。更严重的是,它可能导致封闭实例被保留,否则它将有资格进行垃圾收集(item7)。由此产生的内存泄漏可能是灾难性的。它通常难以检测,因为引用是不可见的。
私有静态成员类的常见用法是表示由其封闭类表示的对象的组件。例如,考虑一个Map实例,它将键与值相关联。许多Map实现都为map中的每个键值对有一个内部Entry对象。虽然每个entry都与一个map相关联,但entry上的方法(getKey,getValue和setValue)不需要map的访问。因此,使用非静态成员类来表示entry是浪费的:私有静态类是最好的。如果你不小心在entry声明中省略了静态修饰符,map仍然能工作,但每个entry都将包含对map的多余引用,这会浪费空间和时间。
如果讨论类是导出类的公共成员或受保护成员,则在静态成员类和非静态成员类之间的正确选择是非常重要的。在这种情况下,成员类是导出的API元素,在给i也需要版本中不能从非静态成员类更改为静态成员类,而不会违反向后兼容性。
正如你所料,匿名类没有名字。它不是它封闭类的成员。它不与其他成员一起声明,而是在使用时同时声明和实例化。在表达式合法的代码中的任何位置都允许使用匿名类。当且仅当它们出现在非静态上下文中时,匿名类才具有封闭实例。但即使它们出现在静态上下文中,它们也不能有除常量变量之外的任何静态成员,这些变量是初始化为常量表达式的基本类型或字符串字段[JLS, 4.12.4]。
匿名类的适用性有很多限制。你不能实例化它们,除非在声明它们的时候。你无法使用instanceof测试或执行其他任何需要命名类的测试。你不能声明匿名类来实现多个接口或同时继承一个类和实现一个接口。匿名类的客户端不能调用除超类继承的成员之外的任何成员。因为匿名类出现在表达式中,它们必须保持段-大约十行或更少-或者可读性会受到影响。
在lambda添加到Java之前(第六章),匿名类是动态创建小函数对象和处理对象的首选方法,但是现在lambda是首选(item42) 。匿名类的另一个常见用法是实现静态工厂方法(查阅item20 的intArrayAsList)
本地类是四种嵌套类中使用最少的类。几乎可以在声明局部变量的任何地方声明本地类,并遵循相同的作用域规则。本地类具有与其他各种嵌套类相同的属性。与成员类一样,它们具有名称并且可以重复使用。与匿名类一样,只有在非静态上下文中定义实例时,它们才会包含实例,并且它们不能包含静态成员。和匿名类一样,它们应该保持简短,以免损害可读性。
回顾一下,有四种嵌套类,每一种都有它们的位置。如果嵌套类需要在单个方法之外可见或太长而无法在方法中舒适地使用,使用成员类。如果成员类地每个实例都需要对其封闭实例地引用,请将其设置为非静态;否则,使之静态。假设类属于方法内部,如果你只需要从一个位置创建实例,并且存在一个能够描述类的现有类型,那么就将其设为匿名类;否则,将其设为局部类。
本文写于2019.4.24,历时天
网友评论