《Effective Java》笔记13:使类和成员的可访问性最小化

  1. 成员
    1. 接口
    2. 实例域
    3. public final
    4. 数组
  2. 总结

要区别设计良好的模块与设计不好的模块,最后重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部了数据和其他实现细节。设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不要知道其他模块的内部工作情况。这个概念被称为信息隐藏或封装,也是软件设计的基本原则之一。

封装有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立地开发、测试与维护,还提高了软件的可重用性。

Java里的封装是通过访问控制符来加以限制的。

规则很简单:尽可能地使每个类或者成员不被外界访问,即使用尽可能最小的访问级别。

对于顶层类(非嵌套的)类和接口,只有两种可能的访问级别:包级私有的和公有的。如果使用 pulbic 修饰符声明了顶层类或者接口,那它就是公有的;否则,它将是包级私有的。如果顶层类或者接口能够被做成包级私有的,它就应该被做成包级私有,这样类或者是接口就成了这个包的实现的一部分,而不是该包导出的API的一部分,在以后的版本中,可以对它进行修改、替换、或者删除,而无需担心会影响到现有的客户端程序。

如果一个包级私有的顶层类(或者接口)只是在某一个类的内部用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类。这样可以将它的可访问范围从包中的所有类缩小到了使用它的那个类。然而,降低不必要公有类的可访问性,比降低包级私有的顶层类的更重要得多:因为公有类是包的API的一部分,而包级私有的顶层类只是这个包的实现的一部分,包级是我们可以掌控的一部分。

成员

对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别,下面按照可访问性的递增顺序罗列出来:

  1. 私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
  2. 包级私有的:声明该成员的包内部的任何的任何类都可以访问这个成员。这也是“缺省”访问级别,如果没有为成员指定访问修饰符,就采用这个访问级别。
  3. 受保护的(protected):声明该成员的类的子类可以访问这个成员(但有一些限制[JLS,6.6.2]),并且声明该成员的包内部的任何类也可以访问这个成员。
  4. 公有的(public):在任何地方都可以访问该成员。

只有当同一个包内的另一个类真正需要访问一个成员的时候,你才应该删除 private 修饰符,使该成员变成包私有的。

私有成员和包级私有成员都是一个类的实现中的一部分,一般不会影响它的导出的 API。然而,如果这个类实现了 Serializable 接口,这些域就有可能会被“泄漏”到导出的 API 中。

对于公有类的成员,当访问级别从包级私有变成保护级别时,会大大增强可访问性。受保护的成员是类的导出的 API 的一部分,必须永远得到支持。导出的类的受保护成员也代表了该类对于某个实现细节的公开承诺。受保护的成员应该尽量少用

如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这样就可以确保任何可使用超类的实例的地方也都可以使用子类的实例。

接口

如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须被声明为公有的。之所以如此,是因为接口中的所有方法都隐含着公有访问级别。

实例域

实例域决不能是公有的。如果域是非 final 的,或者即是 final 但指向的却是可变对象,那么一旦使这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力;这意味着,你也放弃了强制这个域不可变的能力。因此,包含公有可变域的类并不是线程安全的。即使域是 final 的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了“切换换到一种新的内部了数据表示法”(比如将这个字段删除掉,或者使用多个字段来表示等)的灵活性。

public final

public final 修饰的域要么包含基本类型的值,要么包含指向不可变对象的引用(同样的建议也适用于静态域)。如果final 域包含可变对象的引用,它更具有非 final 域的所有缺点,虽然引用本身不能被修改,但是它所引用的对象却可以被修改。

数组

长度非零的数组总是可变的,所以,类具有公有的静态 final 数组域,或者返回这种域的访问方法(这与方法返回的是局部数组是不一样的,因为这个是共享的,而返回的局部数组是单个线程共享的),这几乎总是错误的。如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容,这是安全漏洞的一个常见根源:

public static final Thing[] VALUES={…};

修正这个问题有两种方法,可以使公有数组变成私有的,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_VALUES={…};
public static final LIST VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

另一种方法是,可以使用数组变成私有的,并添加一个公有方法,它返回私有的数组的一个备份:

private static final Thing[] PRIVATE_VALUES ={…};
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}

但依我个人看,上面的克隆还是不起根本作用(如果数组元素是基本类型的,就没有问题):虽然各个元素引用已被复制,但是它们所引用的对象却是同一份,如果指向的对象是可变的,则还是可以通过引用去修改它们所指向对象的问题,所以上面的修改也有潜在的问题。

总结

从上面分析来看 public static final 最好修改的是基本类型的变量,或者是不可变的类。

总而言之,你应该始终尽量可能地降低可访问性。除了公有静态 final 域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态 final 域所引用的对象都是不可变的。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Effective Java》笔记13:使类和成员的可访问性最小化

文章字数:1.9k

本文作者:Bin

发布时间:2018-08-25, 13:06:22

最后更新:2019-08-06, 00:43:01

原始链接:http://coolview.github.io/2018/08/25/Effective-Java/%E3%80%8AEffective%20Java%E3%80%8B%E7%AC%94%E8%AE%B013%EF%BC%9A%E4%BD%BF%E7%B1%BB%E5%92%8C%E6%88%90%E5%91%98%E7%9A%84%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%9C%80%E5%B0%8F%E5%8C%96/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录