每当编写方法或者构造器时,应该考虑它的参数有哪些限制。应该把这些限制写到文档中,而且在这个方法体开头处,经过显示的检查来实施这些限制。养成这样的习惯很是重要。程序员
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); } }
注意,保护性拷贝是在检查参数的有效性以前进行的,而且有效性检查是针对拷贝以后的对象,而不是原始的对象。ide
可是改变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()); }
在内部组件被返回给客户端以前,对它们进行保护性拷贝也是一样的道理。测试
只要有可能,都应该使用不可变的对象做为对象内部的组件,这样就没必要再为保护性拷贝操心。ui
若是类具备从客户端获得或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件。
若是拷贝的成本受到限制,而且类信任它的客户端会恰当地修改组件,就能够在文档中指明客户端的职责使不得修改受到影响的组件,以此来代替保护性拷贝。this
首要目标是选择易于理解的。spa
第二目标是选择与大众承认的名称相一致的名称。
每一个方法都应该尽其所能。方法太多会使类难以学习、使用、文档化、测试和维护。
对于类和接口所支持的每一个动做,都提供一个功能齐全的方法。
目标是四个参数,或者更少。
有三种方法能够缩短过长的参数列表。
第一种是把方法分解成多个方法,每一个方法只须要这些参数的一个子集。
第二种是建立辅助类,用来保存参数的分组。
第三种是从对象构建到方法调用都采用Builder模式。
若是使用的是类而不是接口,则限制了客户端只能传入特定的实现,若是碰巧输入的数据是以其余的形式存在,就会致使没必要要的、可能很是昂贵的拷贝操做。
它是代码更易于阅读和编写,也使之后更易于添加更多的选项。
下面的程序试图根据一个集合是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返回值。