Effective Java笔记第三章类和接口
第一节使类和成员的可访问性最小化
1.设计良好的模块会隐藏所有的实现细节,把他的API与他的实现清晰地隔离开来。然后,模块之间只通过他们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏或封装,是软件设计的基本原则之一。
2.信息隐藏可以有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立的开发,测试,优化,使用,理解和修改。这样可以加快系统开发的速度,因为这些模块可以并行开发。
3.Java程序设计语言提供了许多机制来协助信息隐藏。访问控制机制决定了类,接口和成员的可访问性。实体的可访问性是由实体声明所在的位置,以及该实体声明中所出现的访问修饰符(private,protected和public)共同决定的。正确使用这些修饰符对于实现信息隐藏是非常关键的。
4.尽可能的使每个类或者成员不被外界访问。应该使用与你正在编写的软件的对应功能相一致的,尽可能最小的访问级别。
5.对于顶层(非嵌套的)的类和接口,只有两种可能的访问级别:包级私有的和公有的。如果你用public修饰符声明了顶层类或者接口,那他就是公有的,否则就是包级私有的。如果类或者接口能够被做成包级私有的,他就应该被做成包级私有的。通过把类或者接口做成包级私有,它实际上成了这个包的实现的一部分,而不是该包导出的API的一部分,以后的发行版本中,可以对他进行修改,替换或者删除,而无需担心会影响到现有的客户端程序。如果你把它做成公有的,你就有责任永远支持他,以保持他们的兼容性。
如果一个包级私有的顶层类(或者接口)只是在某一个类的内部被用到,就应该考虑使他成为唯一使用它的那个类的私有嵌套类(内部类)。这样就可以将他的可访问范围从包中的所有类缩小到使用他的类。然而降低不必要公有类的可访问性,比降低包级私有顶层类更重要的多,因为公有类是包的API的一部分,而包级私有的顶层类则已经是包的实现的一部分。
6.对于成员(域,方法,嵌套类和嵌套接口)有四种可能的访问级别,下面按照可访问的递增顺序罗列:
1)私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
2)包级私有(package-private):声明该成员的包的内部的任何类都可以访问这个成员。从技术上讲,他被称为"缺省访问级别",如果没有为成员指定访问修饰符,就采用这个访问级别。
3)受保护的(protected):声明该成员的内部类的子类可以访问这个成员(但有一些限制),并且,声明该成员的包内部的任何类也可以访问这个成员。
4)公有的(public):在任何地方都可以访问该成员。
7.有一条规则限制了降低方法的可访问性的能力。如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这样可以确保任何可使用超类的实例的地方都可以使用子类的实例。这条规则有种特殊的情形:如果一个类实现了一个接口,那么接口中的所有的类方法在这个类中也都必须被声明为公有的。这是因为接口中的所有方法都隐含着公有访问级别。
8.实例域决不能是公有的。如果域是非final的,或者是一个指向可变对象的final引用,那么一旦使这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力,这意味着你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也失去了对他采取任何行动的能力。因此,包含公有可变域的类并不是线程安全的,即使域是final的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了"切换到一种新的内部数据表示法"的灵活性。
对于静态域也同样适用,只有一种例外情况。假设常量构成了类提供的整个抽象中的一部分,可以通过公有的静态final域来暴露这些常量,按照惯例,这种域的名称由大写字母组成。单词之间用下划线分开。这些域要么包含基本类型的值,要么包含指向不可变对象的引用。如果final域包含可变对象的引用,他便具有非final域的所有缺点。虽然引用本身不能被修改,但是他所引用的对象却可以被修改,这会造成很严重的后果。
9.长度非零的数组总是可变的,类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。。如果类具有这样的域或者访问方法,客户端就能够修改数组中的内容。
public class Demo {
public static final String[] VALUE={"a","b"};
public static void main(String[] args) {
String[] value = Demo.VALUE;
value[0]="c";
for (String s : value) {
System.out.println(s);
}
String[] value1 = Demo.VALUE;
for (String s : value1) {
System.out.println(s);
}
}
}
输出:
c
b
c
b
很显然,数组中的数据被修改了,即使再次从类中获取数组,数据也已经修改,这是不可取的。
对此我们有两种方法:
1)可以使数组变成私有的,并增加一个公有的不可变列表。
public class Demo2 {
private static final String[] PRIVATE_VALUES = {"a", "b"};
public static final List<String> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
public static void main(String[] args) {
List<String> values = Demo2.VALUES;
System.out.println(values);
}
}
输出:
a
b
该集合是不可变的。
2)可以使数组变成私有的,并添加一个公有方法,他返回私有数组的一个备份。
public class Demo3 {
private static final String[] PRIVATE_VALUES = {"a", "b"};
public static final String[] values(){
return PRIVATE_VALUES.clone();
}
public static void main(String[] args) {
String[] values = Demo3.values();
values[0]="c";
for (String value : values) {
System.out.println(value);
}
String[] values1 = Demo3.values();
for (String value : values1) {
System.out.println(value);
}
}
}
输出:
c
b
a
b
可见我们修改的仅仅是备份文件,类中的数组并没有被修改。
10。我们应该始终尽可能地降低可访问性。在仔细地设计了一个最小的公有API之后,应该防止把任何散乱的类,接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所应用的对象都是不可变的。