检查参数的有效性
每当编写方法或者构造器时,应该考虑它的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法体开头处,通过显示的检查来实施这些限制。养成这样的习惯非常重要。
必要时进行保护性拷贝
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
}
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
因为Date类本身时可变的,所以,
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // 这个操作把实例的内部信息修改了。
为了保护Period实例的内部信息避免受到这种攻击,对于构造器的每个可变参数进行保护性拷贝是必要的,并且使用备份对象作为Period实例的组件,而不是使用原始的对象。
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
}
}
注意,保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始的对象。
但是改变Period实例仍然是有可能的,因为它的访问方法提供了对其内部成员的访问能力。
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // 这个操作把实例的内部信息修改了。
为了防御这第二种攻击,只需改这两个访问方法,使它返回可变内部域的保护性拷贝即可。
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
在内部组件被返回给客户端之前,对它们进行保护性拷贝也是同样的道理。
只要有可能,都应该使用不可变的对象作为对象内部的组件,这样就不必再为保护性拷贝操心。
如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件。
如果拷贝的成本受到限制,并且类信任它的客户端会恰当地修改组件,就可以在文档中指明客户端的职责使不得修改受到影响的组件,以此来代替保护性拷贝。
谨慎设计方法签名
谨慎地选择方法的名称
首要目标是选择易于理解的。
第二目标是选择与大众认可的名称相一致的名称。
不要过于追求提供便利的方法
每个方法都应该尽其所能。方法太多会使类难以学习、使用、文档化、测试和维护。
对于类和接口所支持的每个动作,都提供一个功能齐全的方法。
避免过长的参数列表
目标是四个参数,或者更少。
有三种方法可以缩短过长的参数列表。
第一种是把方法分解成多个方法,每个方法只需要这些参数的一个子集。
第二种是创建辅助类,用来保存参数的分组。
第三种是从对象构建到方法调用都采用Builder模式。
对于参数类型,要优先使用接口而不是类
如果使用的是类而不是接口,则限制了客户端只能传入特定的实现,如果碰巧输入的数据是以其他的形式存在,就会导致不必要的、可能非常昂贵的拷贝操作。
对于boolean参数,要优先使用两个元素的枚举类型
它是代码更易于阅读和编写,也使以后更易于添加更多的选项。
慎用重载
下面的程序试图根据一个集合是Set、List,还是其他的集合类型来分类。
public class CollectionClassifier {
public static String classify(Set<?> set) {
return "Set";
}
public static String classify(List<?> list) {
return "List";
}
public static String classify(Collection<?> collection) {
return "Unknow Collection";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for(Collection<?> collection : collections) {
System.out.println(classify(collection));
}
}
}
可能期望会打印“Set”,接着是“List”,以及“Unknow Collection”,实际上不是这样。而是“Unknow Collection”打印三次。
因为classify方法被重载(overload)了,而要调用哪个重载方法是在编译时做出决定的。对于for循环中的三次迭代,参数的编译时类型都是相同的,Collection<?>。每次迭代的运行时类型都是不同的,但这并不影响对重载方法的选择。因为该参数的编译时类型为Collection<?>,所以,唯一合适的重载方法是第三个,classify(Collection<?>),在循环的每次迭代中,都会调用这个重载方法。
对于重载方法(overload method)的选择是静态的,而对于被覆盖的方法(overriden method)的选择则是动态的。
选择被覆盖的方法的正确版本是在运行时的,选择的依据时被调用方法所在对象的运行时类型。
再看看下面这个程序。
class Wine {
String name() {
return "wine";
}
}
class SparklingWine extends Wine {
@Override
String name() {
return "sparkling wine";
}
}
class Champagne extends SparklingWine {
@Override
String name() {
return "champagne";
}
}
public class Overriding {
public static void main(String[] args) {
Wine[] wines = {
new Wine(),
new SparklingWine(),
new Champagne()
};
for(Wine wine : wines) {
System.out.println(wine.name());
}
}
}
这个程序打印“wine”、“sparkling wine”和“champagne”。
可以对第一个程序修改一下,用单个方法替换三个重载的classify方法,如下。
public static String classify(Collection<?> collection) {
return collection instanceof Set ? "Set" :
collection instanceof List ? "List" : "Unknow Collection";
}
因为覆盖机制时规范,而重载机制是例外。
到底怎样才算是胡乱使用重载机制呢?这个问题仍有争议。安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。
慎用可变参数
Java 1.5发行版本中增加了可变参数方法,一般称作variable arity method(可匹配不同长度的变量方法)[JLS,8.4.1]。
可变参数方法接受零个或者多个指定类型的参数。可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法。
static int sum(int... args) {
int sum = 0;
for (int arg : args) {
sum += arg;
}
return sum;
}
当真正需要让一个方法带有不定数量的参数时,可变参数就非常有效。可变参数是为printf而设计的。printf和反射机制都从可变参数中极大地受益。
在重视性能地情况下,需要小心。可变参数方法的每次调用都会导致进行一次数组分配和初始化。
需要可变参数的灵活性,但又无法承受性能成本,可以这么做。
public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}
在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是不应该被过度滥用。如果使用不当,会产生混乱的结果。
返回零长度的数组或者集合,而不是null
对于一个返回null而不是零长度数组或者集合的方法,几乎每次调用该方法都需要曲折的处理方式。这样很容易出错,因为编写客户端程序的程序员可能会忘记写专门的代码来处理null返回值。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。