Effective Java 第三版——24. 优先考虑静态成员类

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。ide

Effective Java, Third Edition

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

静态成员类是最简单的嵌套类。 最好把它看做是一个普通的类,刚好在另外一个类中声明,而且能够访问全部宿主类的成员,甚至是那些被声明为私有类的成员。 静态成员类是其宿主类的静态成员,并遵循与其余静态成员相同的可访问性规则。 若是它被声明为private,则只能在宿主类中访问,等等。测试

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

在语法上,静态成员类和非静态成员类之间的惟一区别是静态成员类在其声明中具备static修饰符。 尽管句法类似,但这两种嵌套类是很是不一样的。 非静态成员类的每一个实例都隐含地与其包含的类的宿主实例相关联。 在非静态成员类的实例方法中,能够调用宿主实例上的方法,或者使用限定的构造[JLS,15.8.4]得到对宿主实例的引用。 若是嵌套类的实例能够与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有宿主实例的状况下建立非静态成员类的实例。code

非静态成员类实例和其宿主实例之间的关联是在建立成员类实例时创建的,而且以后不能被修改。 一般状况下,经过在宿主类的实例方法中调用非静态成员类构造方法来自动创建关联。 尽管不多有可能使用表达式enclosingInstance.new MemberClass(args)手动创建关联。 正如你所预料的那样,该关联在非静态成员类实例中占用了空间,并为其构建添加了时间开销。对象

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

// 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)。 由此产生的内存泄漏多是灾难性的。 因为引用是不可见的,因此一般难以检测到。继承

私有静态成员类的常见用法是表示由它们的宿主类表示的对象的组件。 例如,考虑将键与值相关联的Map实例。 许多Map实现对于映射中的每一个键值对都有一个内部的Entry对象。 当每一个entry都与Map关联时,entry上的方法(getKeygetValuesetValue)不须要访问Map。 所以,使用非静态成员类来表示entry将是浪费的:私有静态成员类是最好的。 若是意外地忽略了entry声明中的static修饰符,Map仍然能够工做,可是每一个entry都会包含对Map的引用,浪费空间和时间。接口

若是所讨论的类是导出类的公共或受保护成员,则在静态和非静态成员类之间正确选择是很是重要的。 在这种状况下,成员类是导出的API元素,若是不违反向后兼容性,就不能在后续版本中从非静态变为静态成员类。ip

正如你所指望的,一个匿名类没有名字。 它不是其宿主类的成员。 它不是与其余成员一块儿声明,而是在使用时同时声明和实例化。 在表达式合法的代码中,匿名类是容许的。 当且仅当它们出如今非静态上下文中时,匿名类才会封装实例。 可是,即便它们出如今静态上下文中,它们也不能有除常量型变量以外的任何静态成员,这些常量型变量包括final的基本类型,或者初始化常量表达式的字符串属性[JLS,4.12.4]。

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

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

局部类是四种嵌套类中使用最少的。 一个局部类能够在任何能够声明局部变量的地方声明,并遵照相同的做用域规则。 局部类与其余类型的嵌套类具备共同的属性。 像成员类同样,他们有名字,能够重复使用。 就像匿名类同样,只有在非静态上下文中定义它们时,它们才会包含实例,而且它们不能包含静态成员。 像匿名类同样,应该保持简短,以避免损害可读性。

回顾一下,有四种不一样的嵌套类,每一个都有它的用途。 若是一个嵌套的类须要在一个方法以外可见,或者太长而不能很好地适应一个方法,使用一个成员类。 若是一个成员类的每一个实例都须要一个对其宿主实例的引用,使其成为非静态的; 不然,使其静态。 假设这个类属于一个方法内部,若是你只须要从一个地方建立实例,而且存在一个预置类型来讲明这个类的特征,那么把它做为一个匿名类; 不然,把它变成局部类。

相关文章
相关标签/搜索