Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java
类和接口是Java编程语言的核心。它们是抽象的基本单位。该语言提供了许多强大的元素,可使用它们来设计类和接口。本章包含指导原则,帮助你充分利用这些元素,使你的类和接口是可用的、健壮的和灵活的。程序员
将设计良好的组件与设计不佳的组件区分开来的最重要的因素是,组件将其内部数据和其余组件的其余实现细节隐藏起来。一个设计良好的组件隐藏了它的全部实现细节,干净地将它的API与它的实现分离开来。而后,组件只经过它们的API进行通讯,而且对彼此的内部工做一无所知。这一律念,被称为信息隐藏或封装,是软件设计的基本原则[Parnas72]。编程
信息隐藏很重要有不少缘由,其中大部分来源于它将组成系统的组件分离开来,容许它们被独立地开发,测试,优化,使用,理解和修改。这加速了系统开发,由于组件能够并行开发。它减轻了维护的负担,由于能够更快速地理解组件,调试或更换组件,而不用担忧损害其余组件。虽然信息隐藏自己并不会致使良好的性能,但它能够有效地进行性能调整:一旦系统完成而且分析肯定了哪些组件致使了性能问题(条目 67),则能够优化这些组件,而不会影响别人的正确的组件。信息隐藏增长了软件重用,由于松耦合的组件一般在除开发它们以外的其余环境中证实是有用的。最后,隐藏信息下降了构建大型系统的风险,由于即便系统不能运行,各个独立的组件也多是可用的。数组
Java提供了许多机制来帮助信息隐藏。 访问控制机制(access control mechanism)[JLS,6.6]指定了类,接口和成员的可访问性。 实体的可访问性取决于其声明的位置,以及声明中存在哪些访问修饰符(private,protected和public)。 正确使用这些修饰符对信息隐藏相当重要。安全
经验法则很简单:让每一个类或成员尽量地不可访问。换句话说,使用尽量低的访问级别,与你正在编写的软件的对应功能保持一致。编程语言
对于顶层(非嵌套的)类和接口,只有两个可能的访问级别:包级私有(package-private)和公共的(public)。若是你使用public修饰符声明顶级类或接口,那么它是公开的;不然,它是包级私有的。若是一个顶层类或接口能够被作为包级私有,那么它应该是。经过将其设置为包级私有,能够将其做为实现的一部分,而不是导出的API,你能够修改它、替换它,或者在后续版本中消除它,而没必要担忧损害现有的客户端。若是你把它公开,你就有义务永远地支持它,以保持兼容性。模块化
若是一个包级私有顶级类或接口只被一个类使用,那么能够考虑这个类做为使用它的惟一类的私有静态嵌套类(条目 24)。这将它的可访问性从包级的全部类减小到使用它的一个类。可是,减小没必要要的公共类的可访问性要比包级私有的顶级类更重要:公共类是包的API的一部分,而包级私有的顶级类已是这个包实现的一部分了。性能
对于成员(属性、方法、嵌套类和嵌套接口),有四种可能的访问级别,在这里,按照可访问性从小到大列出:学习
在仔细设计你的类的公共API以后,你的反应应该是让全部其余成员设计为私有的。 只有当同一个包中的其余类真的须要访问成员时,须要删除私有修饰符,从而使成员包成为包级私有的。 若是你发现本身常常这样作,你应该从新检查你的系统的设计,看看另外一个分解可能产生更好的解耦的类。 也就是说,私有成员和包级私有成员都是类实现的一部分,一般不会影响其导出的API。 可是,若是类实现Serializable接口(条目 86和87),则这些属性能够“泄漏(leak)”到导出的API中。测试
对于公共类的成员,当访问级别从包私有到受保护级时,可访问性会大大增长。 受保护(protected)的成员是类导出的API的一部分,而且必须永远支持。 此外,导出类的受保护成员表示对实现细节的公开承诺(条目 19)。 对受保护成员的需求应该相对较少。
有一个关键的规则限制了你减小方法访问性的能力。 若是一个方法重写一个超类方法,那么它在子类中的访问级别就不能低于父类中的访问级别[JLS,8.4.8.3]。 这对于确保子类的实例在父类的实例可用的地方是可用的(Liskov替换原则,见条目 15)是必要的。 若是违反此规则,编译器将在尝试编译子类时生成错误消息。 这个规则的一个特例是,若是一个类实现了一个接口,那么接口中的全部类方法都必须在该类中声明为public。
为了便于测试你的代码,你可能会想要让一个类,接口或者成员更容易被访问。 这没问题。 为了测试将公共类的私有成员指定为包级私有是能够接受的,可是提升到更高的访问级别倒是不可接受的。 换句话说,将类,接口或成员做为包级导出的API的一部分来促进测试是不可接受的。 幸运的是,这不是必须的,由于测试能够做为被测试包的一部分运行,从而得到对包私有元素的访问。
公共类的实例属性不多公开(条目 16)。若是一个实例属性是非final的,或者是对可变对象的引用,那么经过将其公开,你就放弃了限制能够存储在属性中的值的能力。这意味着你放弃了执行涉及该属性的不变量的能力。另外,当属性被修改时,就放弃了采起任何操做的能力,所以公共可变属性的类一般不是线程安全的。即便属性是final的,而且引用了一个不可变的对象,经过使它公开,你就放弃切换到不存在属性的新的内部数据表示的灵活性。
一样的建议适用于静态属性,但有一个例外。 假设常量是类的抽象的一个组成部分,你能够经过public static final
属性暴露常量。 按照惯例,这些属性的名字由大写字母组成,字母用下划线分隔(条目 68)。 很重要的一点是,这些属性包含基本类型的值或对不可变对象的引用(条目 17)。 包含对可变对象的引用的属性具备非final属性的全部缺点。 虽然引用不能被修改,但引用的对象能够被修改,并会带来灾难性的结果。
请注意,非零长度的数组老是可变的,因此类具备公共静态final数组属性,或返回这样一个属性的访问器是错误的。 若是一个类有这样的属性或访问方法,客户端将可以修改数组的内容。 这是安全漏洞的常见来源:
// Potential security hole! public static final Thing[] VALUES = { ... };
要当心这样的事实,一些IDE生成的访问方法返回对私有数组属性的引用,致使了这个问题。 有两种方法能够解决这个问题。 你可使公共数组私有并添加一个公共的不可变列表:
private static final Thing[] PRIVATE_VALUES = { ... }; public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
或者,能够将数组设置为private,并添加一个返回私有数组拷贝的公共方法:
private static final Thing[] PRIVATE_VALUES = { ... }; public static final Thing[] values() { return PRIVATE_VALUES.clone(); }
要在这些方法之间进行选择,请考虑客户端可能如何处理返回的结果。 哪一种返回类型会更方便? 哪一个会更好的表现?
在Java 9中,做为模块系统(module system)的一部分引入了两个额外的隐式访问级别。模块包含一组包,就像一个包包含一组类同样。模块能够经过模块声明中的导出(export)声明显式地导出某些包(这是module-info.java的源文件中包含的约定)。模块中的未导出包的公共和受保护成员在模块以外是不可访问的;在模块中,可访问性不受导出(export)声明的影响。使用模块系统容许你在模块之间共享类,而不让它们对整个系统可见。在未导出的包中,公共和受保护的公共类的成员会产生两个隐式访问级别,这是普通公共和受保护级别的内部相似的状况。这种共享的需求是相对少见的,而且能够经过从新安排包中的类来消除。
与四个主要访问级别不一样,这两个基于模块的级别主要是建议(advisory)。 若是将模块的JAR文件放在应用程序的类路径而不是其模块路径中,那么模块中的包将恢复为非模块化行为:包的公共类的全部公共类和受保护成员都具备其普通的可访问性,无论包是否由模块导出[Reinhold,1.2]。 新引入的访问级别严格执行的地方是JDK自己:Java类库中未导出的包在模块以外真正没法访问。
对于典型的Java程序员来讲,不只程序模块所提供的访问保护存在局限性,并且在本质上是很大程度上建议性的;为了利用它,你必须把你的包组合成模块,在模块声明中明确全部的依赖关系,从新安排你的源码树层级,并采起特殊的行动来适应你的模块内任何对非模块化包的访问[Reinhold ,3]。 如今说模块是否会在JDK以外获得普遍的使用还为时尚早。 与此同时,除非你有迫切的须要,不然彷佛最好避免它们。
总而言之,应该尽量地减小程序元素的可访问性(在合理范围内)。 在仔细设计一个最小化的公共API以后,你应该防止任何散乱的类,接口或成员成为API的一部分。 除了做为常量的公共静态final属性以外,公共类不该该有公共属性。 确保public static final
属性引用的对象是不可变的。