这节比较简单,都是各种面试考烂了的东西。

类和属性的访问控制应尽可能严格

访问控制是Java语言中最重要的概念之一,它使得对数据的封装成为可能。好的数据封装可以有效分隔一个系统中的不同模块,增加系统可靠性,便于维护,而且使各模块的开发和重构完全独立,实现细节上的改动不会影响对外开放的API。

对于类,有两个访问级别:package-private和public。其中前者是默认的,如果一个类没有被声明为public,那么它就是package-private,也就是说,它只在其自己所在的软件包内可见。这两个级别的选择没什么复杂的:只有要作为API开放的类,才声明为public,其余实现的细节,则尽可能都声明为package-private。如果一个非public类只被另外一个类使用,那么它应该被定义成使用者的私有内嵌类(private nested class),这样可以进一步减小它的可见范围,使其更好地被保护。

对于类的属性,则有四个访问级别:

private:仅所在类内可见

package-private:同一软件包内可见

protected:子类和同一软件包内可见

public:完全开放

四个级别的含义很简单,无需赘言。值得一提的,是一些小细节。

1. 对于public类的属性,从package到protected是可见性上的一种巨大提升。原因在于,一个package级别的属性,只能算实现的一部分,可以被开发者随意改动,而一旦它被开放为protected,就变成了API的一部分,需要被长期维护,任何与其有关的改动都会破坏用户代码,同时它也会向用户泄露API实现细节的信息。真正需要protected属性的时候并不多,所以尽可能不要声明这一级别的属性。

2. 根据Java语言规范,如果子类覆盖了父类的一个方法,那么被覆盖方法的可见性只能放宽而不能收紧。这是为保证在任何父类对象可用的场景,子类对象都同样可用。这一规则由编译器强化,如有违反,会有编译错误出现。

3. 对象属性永远不能是public的。原因显而易见:如果public对象属性不是immutable或者final,那么用户可以轻易改变其属性或者引用,对这样的改变,开发者无法控制,而这很容易破坏程序的实现。即便public对象属性是final而且immutable,它同样影响开发者对程序的维护,对这一属性的修改有可能破坏用户代码。这一规则同样适用于静态(static)属性,但有一个特例:常量,也就是public static final而且immutable的属性,可以存在。这种情况下常量名要全部大写,并用下划线连接各单词。

基于上面第三条规则,由于非空数组一定是mutable的,所以任何类都不应该有public static final的数组属性,或者返回这种属性的getter。这是一种最常见的安全漏洞的来源:

// Potential security hole!
public static final Thing[] VALUES = { ... };

有两种替代方案可以避免这个问题:

private static final Thing[] PRIVATE_VALUES = { ... };

public static final List<Thing> VALUES =
	Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

或者:

private static final Thing[] PRIVATE_VALUES = { ... };

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

可以依用户需求和使用习惯做出选择。