在Java程序中,方法与属性是类中最基本的成员。下面的问题与方法有关:
实现一个方法的第一步是什么?
说到这里我突然想起侯耀文、赵丽蓉两位艺术家在小品《英雄母亲的一天》中有这样一段台词:
导演:您起床后做的第一件事是什么?可以随便说。
大娘一听可以随便说,笑了。
大娘(凑到导演的耳朵边,轻声地):上厕所。
在这里,我也想做一回导演,把上面的台词改一下:
编程新手:您在实现类中的一个方法时,第一步要做的事是什么?可以随便说。
编程老手一听可以随便说,笑了。
编程老手(凑到新手的耳朵边,轻声地):参数有效性检查。
大娘的回答铁定不是导演希望听到的,编程老手的回答恐怕也不是新手想要得到的。原因很简单,与正题无关。但是参数有效性检查之于方法实现的重要性决不亚于早上起来上厕所,个中原因只可意会,不可言传。这里要讨论的是如何在Java中进行参数有效性检查。
说到参数有效性检查,不得不说一下运行时异常。Java有两种异常:一般异常与运行时异常。继承自Exception的异常称为一般异常,继承自RuntimeException的异常称为运行时异常。与一般异常最大的不同之处在于,函数调用方可以不必处理实现方抛出的运行时异常。 但是异常的抛出通常会被当成一种错误的发生。如果调用方可以不用处理运行时异常,那就意味着调用方必须尽量避免实现方抛出运行时异常。 例如:
- public static boolean equalObject(Object obj1, Object obj2) {
- return obj1.equals(obj2);
- }
上述代码在obj1为null的场合下会抛出NullPointerException,为了避免这种情况发生,正确的写法应该是:
- public static boolean equalObject(Object obj1, Object obj2) {
- return (obj1 != null && obj1.equals(obj2));
- }
可以看出,运行时异常是对函数调用方一种无形的约束,这种约束方式正好符合参数有效性检查的要求,即调用方法时必须提供正确的参数,否则就会导致系统崩溃。例如我们通过物体的质量计算其重力值。实现方法如下:
- public class Test {
- public static void main(String[] args) {
- System.out.println(getGravity(1.0));
- }
- public static double getGravity(double mass) {
- return mass * 9.82;
- }
- }
我们知道一般情况下一个1.0千克的物体其重力为9.82牛顿,但质量为负数的物体呢?物理常识告诉我们,世界上不存在质量为0或小于0的物体,据此我们应该如何完善重力的计算方法呢?
计算重力的第一步应该是对质量进行参数有效性检查。Java SDK中提供了许多种运行时异常,与参数有效性检查相关的异常是IllegalArgumentException。代码实现可以如下:
- public static double getGravity(double mass) {
- // 参数有效性检查
- if (mass <= 0) {
- throw new IllegalArgumentException("无效质量:" + mass);
- }
- return mass * 9.82;
- }
当我们调用这样一个计算重力的方法时,应该尽量传递有效的质量值。当我们传递一个负值给此方法时,系统将会以抛出异常的方式退出。如:
- public class Test {
- public static void main(String[] args) {
- System.out.println(getGravity(1.0)); // 有效的质量
- // 我们应该尽量避免下面的调用
- // System.out.println(getGravity(-1.0)); // 无效的质量
- }
- public static double getGravity(double mass) {
- // 参数有效性检查
- if (mass <= 0) {
- throw new IllegalArgumentException("无效质量:" + mass);
- }
- return mass * 9.82;
- }
- }
参数有效性检查应该作为常识来掌握,特别是Java中特有的参数有效性检查。上面介绍的是最基本的用法,当然我们也可以利用第三方程序库(如Apache Commons Lang 库的Validate类)来简化参数有效性检查的过程。
每当编写方法或者构造器的时候,应该考虑他的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法体的开头处,通过显式的检查来实施这些限制。养成这样的习惯是非常重要的。
demo:
1\对于公有的方法,要用Javadoc的@throws标签(tag)在文档中说明违反参数值限制会抛出异常。
手工抛出异常,并且添加@throws注解说明原因
- /**
- * hello.....
- * @param m
- * @return
- * @throws NullPointerException if m is null
- * @throws ArithmeticException if m is less than or equals to 0
- */
- public BigInteger mod(BigInteger m) {
- if(m == null){
- throw new NullPointerException("m is null:" + m);
- }
- if (m.signum() <= 0) {
- throw new ArithmeticException("Modulus <= 0: " + m);
- }
- // Do something
- return null;
- }
2\对于未被导出的方法(unexported method),作为包的创建者,你可以控制这个方法将在哪些情况下被使用,因此你可以,也应该确保只将有效的参数传递进来。
因此,非公有的方法通常应该使用断言(assertion)来检查他们的参数。
通过将-ea(或者-enableassertions)标记传递给java解释器,来启动他们。
(一般来说 assert 在开发的时候是检查程序的安全性的,在发布的时候通常都不使用 assert 。)
- /**
- *
- * @param a
- * @param offset
- * @param length
- */
- private static void sort(long[] a,int offset,int length){
- assert a != null;
- assert offset >= 0 && offset <= a.length;
- System.out.println("sort do something");
- }
不同于junit里的断言方法:
- private static void sort2(long[] a, int offset, int length) {
- Assert.assertTrue("a is null", a != null);
- Assert.assertTrue(offset >= 0 && offset <= a.length);
- System.out.println("sort do something");
- }