技术

Should be a static inner class

Tags:

-2019年3月19日

This class is an inner class, but does not use its embedded reference to the object which created it. This reference makes the instances of the class larger, and may keep the reference to the creator object alive longer than necessary. If possible, the class should be made static.

今天通过findbugs扫描项目,发现一处提示如上所示。仔细看了让我有点懵逼,查找了各种资料最后在Effective Java书的第四章-第21条找到了答案:

嵌套类( nested class)是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为它的外围类( enclosing class)提供服务。如果嵌套类将来可能会用于其他的某个环境中,它就
应该是顶层类( top-level class)。嵌套类有四种:静态成员类( static member class)、非静态成员类( nonstatic member class)、匿名类( anonymous class)和局部类( (local class。除了第一种之外,其他三种都被称为内部类( inner class)。本条目将告诉你什么时候应该使用哪种嵌套类,以及这样做的原因。

静态成员类是最简单的一种嵌套类。最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。如果它被声明为私有的,它就只能在外围类的内部才可以被访问,等等。

静态成员类的一种常见用法是作为公有的辅助类,仅当与它的外部类一起使用时才有意义。例如,考虑一个枚举,它描述了计算器支持的各种操作(见第30条)。 Operation枚举应该是 Calculator类的公有静态成员类, 然后, Calculator类的客户端就可以用诸如 Calculator.Operation.PLUS和 Calculator.Operation.MINUS这样的名称来引用这些操作。

从语法上讲,静态成员类和非静态成员类之间唯一的区别是,静态成员类的声明中包含修饰符 static。尽管它们的语法非常相似,但是这两种怅套类有很大的不同。非静态成员类的每个实例都隐含着与外围类的一个外围实例( enclosing instance)相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用[JLS,158.4],如果嵌套类的实例可以在它外围类的实例之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。

当非静态成员类的实例被创建的时候,它和外围实例之间的关联关系也随之被建立起来。而且,这种关联关系以后不能被修改。通常情况下,当在外围类的某个实例方法的内部调用非静态成员类的构造器时,这种关联关系被自动建立起来。使用表达式 enclosingInstance.new MemberClass(args)来手工建立这种关联关系也是有可能的,但是很少使用。正如你所预料的那样,这种关联关系需要消耗非静态成员类实例的空间,并且增加了构造的时间开销。

非静态成员类的一种常见用法是定义一个 Adapter[ Gamma95,p.139],它允许外部类的实例被看作是另一个不相关的类的实例。例如,Map接口的实现往往使用非静态成员类来实现它们的集合视图(collection view),这些集合视图是由Map的keySet, entrySet和values方法返回的。同样地,诸如set和Lis这种集合接口的实现往往也使用非静态成员类来实现它们的选代器(Iterator):

// Typical use of a nonstatic class
 public class My Set  extends AbstractSetcE> {
     // Bulk of the class omitted
     public Iterator iterator() {
         return new MyIteratoro;
     }
     private class MyIterator implements Iterator {
         ……
     }
 }



如果声明成员美不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类。如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,井且会导致外围实例在符合垃圾回收(见第6条)时却仍然得以保留。如果在没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。

私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。例如,考虑一个Map实例,它把键(key)和值( value)关联起来。许多Map实现的内部都有一个 Entry对象,对应于Map中的每个键-值对,虽然每个entry都与一个Map关联,但是entry上的方法(getKey,getValue和setValue)井不需要访问该Map。因此,使用非静态成员来表示entry是很浪费的:
私有的静态成员类是最佳的选择。如果不小心漏掉了entry声明中的static修饰符,该Map仍然可以工作,但是每个entry中将会包含一个指向该Map的引用,这样就浪费了空间和时间。

如果相关的类是导出类的公有的或受保护的成员,毫无疑问,在静态和非静态成员类之间做出正确的选择是非常重要的。在这种情况下,该成员类就是导出的API元素,在后续的版本中,如果不违反二进制兼容性,就不能从非静态成员类变为静态成员类。

匿名类不同于]ava程序设计语言中的其他任何语法单元。正如你所想像的,匿名类没有名字。它不是外围类的一个成员。它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中时,它才有外围实例,但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。匿名类的适用性受到诸多的限制,除了在它们被声明的时候之外,是无法将它们实例化的。你不能执行 instanceof测试,或者做任何需要命名类的其他事情,你无法声明一个匿名类来实现多个接口,或者扩展一个类,并同时扩展类和实现接口。匿名类的客户端无法调用任何成员,
除了从它的超类型中继承得到之外。由于匿名类出现在表达式当中,它们必须保持简短—大约10行或者更少些否则会影响程序的可读性。

如果声明成员美不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类。如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,井且会导致外围实例在符合垃圾回收(见第6条)时却仍然得以保留。如果在没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。

私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。例如,考虑一个Map实例,它把键(key)和值( value)关联起来。许多Map实现的内部都有一个 Entry对象,对应于Map中的每个键-值对,虽然每个entry都与一个Map关联,但是entry上的方法(getKey,getValue和setValue)井不需要访问该Map。因此,使用非静态成员来表示entry是很浪费的:
私有的静态成员类是最佳的选择。如果不小心漏掉了entry声明中的static修饰符,该Map仍然可以工作,但是每个entry中将会包含一个指向该Map的引用,这样就浪费了空间和时间。

如果相关的类是导出类的公有的或受保护的成员,毫无疑问,在静态和非静态成员类之间做出正确的选择是非常重要的。在这种情况下,该成员类就是导出的API元素,在后续的版本中,如果不违反二进制兼容性,就不能从非静态成员类变为静态成员类。

匿名类不同于]ava程序设计语言中的其他任何语法单元。正如你所想像的,匿名类没有名字。它不是外围类的一个成员。它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中时,它才有外围实例,但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。匿名类的适用性受到诸多的限制,除了在它们被声明的时候之外,是无法将它们实例化的。你不能执行 instanceof测试,或者做任何需要命名类的其他事情,你无法声明一个匿名类来实现多个接口,或者扩展一个类,并同时扩展类和实现接口。匿名类的客户端无法调用任何成员,
除了从它的超类型中继承得到之外。由于匿名类出现在表达式当中,它们必须保持简短—大约10行或者更少些否则会影响程序的可读性。

Leave a Comment