Effective Java 第三版读书笔记——条款 24:优先使用静态成员类而不是非静态成员类

嵌套类(nested class)是在另一个类中定义的类。嵌套类应该只存在于其外部类(enclosing class)中。如果一个嵌套类在其他一些情况下是有用的,那么它应该是一个顶级类。有四种嵌套类:静态成员类,非静态成员类,匿名类和局部类。除了第一种以外,剩下的三种都被称为内部类(inner class)。这个条款告诉你什么时候使用哪种类型的嵌套类以及为什么使用。

静态成员类是最简单的嵌套类。最好把它看作是一个普通的类,恰好在另一个类中声明,并且可以访问外部类的所有成员,甚至是那些被声明为私有的成员。静态成员类是其外部类的静态成员,并遵循与其他静态成员相同的可访问性规则。如果它被声明为 private,则只能在外部类中访问。

静态成员类的一个常见用途是作为公共帮助类,仅在与其外部类一起使用时才有用。例如,考虑一个描述计算器支持的操作的枚举类型(条款 34)。 Operation 枚举应该是 Calculator 类的公共静态成员类。Calculator 客户端可以使用 Calculator.Operation.PLUSCalculator.Operation.MINUS 等名称来引用操作。

在语法上,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有 static 修饰符。尽管语法相似,但这两种嵌套类是非常不同的。非静态成员类的每个实例都隐含地与其外部类实例相关联。在非静态成员类的实例方法中,可以调用外部类实例上的方法,或者使用限定的 this 获得对外部类实例的引用。如果嵌套类的实例可以与其外部类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有外部类实例的情况下创建非静态成员类的实例。

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

非静态成员类的一个常见用法是定义一个 Adapter,它允许将外部类的实例视为某个不相关类的实例。例如,Map 接口的实现通常使用非静态成员类来实现它们的集合视图,这些视图由 Map 的 keySetentrySetvalues 方法返回。同样,集合接口(如 Set 和 List)的实现通常使用非静态成员类来实现它们的迭代器:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    @Override public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

如果你声明了一个不需要访问外部类实例的成员类,那么总是把 static 修饰符放在它的声明中,使它成为一个静态成员类,而不是非静态的成员类。如果你忽略了这个修饰符,每个实例都会有一个隐藏的外部引用到它的外部类实例。如前所述,存储这个引用需要占用时间和空间。更严重的是,这会导致即使外部类在满足垃圾回收的条件时仍然驻留在内存中(条款 7)。由此产生的内存泄漏可能是灾难性的。

正如你所期望的,一个匿名类没有名字。它不是其外部类的成员。它不是与其他成员一起声明的,而是在使用时同时声明和实例化。 匿名类的使用有很多限制。除了在声明的时候之外,不能在其他地方实例化它们。你不能执行 instanceof 方法测试或者做任何其他需要类的名字的操作。不能声明一个匿名类来实现多个接口,或者继承一个类并同时实现一个接口。 因为匿名类在表达式中出现,所以它们必须保持简短——约十行或更少——否则可读性将受损。

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

局部类是四种嵌套类中使用最少的。一个局部类可以在任何可以声明局部变量的地方声明,并遵守相同的作用域规则。局部类与其他类型的嵌套类具有共同的属性。像成员类一样,他们有名字,可以重复使用。像匿名类一样,应该保持简短,以免损害可读性。

回顾一下,有四种不同的嵌套类,每个都有它的用途。如果一个嵌套的类需要在一个方法之外可见,或者太长而不能很好地放在一个方法内部,使用一个成员类。如果一个成员类的每个实例都需要一个对其外部类实例的引用,使其成为非静态的;否则,使用静态成员类。假设这个类属于一个方法内部,如果你只需要从一个地方创建实例,并且存在一个预置类型来说明这个类的特征,那么把它作为一个匿名类; 否则,把它变成局部类。

posted @ 2019-02-01 14:38  LeeFire  阅读(357)  评论(0编辑  收藏  举报