java $ 参数_Java - 方法的参数声明

给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数、对于某个关键对象引用不能为null,否则会进行一些处理,比如抛出相应的异常信息。

对于这些参数限制,方法的提供者必须在文档中注明,并且在方法开头时检查参数,并在失败时提供明确的信息,即:

detect errors as soon as possible after they occur

这将成为准确定位错误的一大保障。

如果没有做到这一点,最好的情况是方法在处理过程中失败并抛出了莫名其妙的异常,错误的源头变得难以定位,但这是最好的情况。

更差的情况是方法执行通过,没有发生任何错误,只是得出的结果和方法描述完全不符,最后在某个关键的部分看到奇怪的数据时才亡羊补牢。

对于参数违反有效性时使用的异常类,我们通常抛出IllegalArgumentException,IndexOutOfBoundsException,NullPointerException。

而对于违反约束时抛出的异常类型,需要用Javadoc的@throws标签对其进行说明。

另外,并不是所有参数检查都需要做到这种地步。

如果方法或构造器不对外导出,则可以简单使用assert来保证参数的有效性。

比如java.util.Collections$CopiesList:

private static class CopiesList

extends AbstractList

implements RandomAccess, Serializable{

//...

CopiesList(int n, E e) {

assert n >= 0;

this.n = n;

element = e;

}

//...

}

但是,有效性检查并不都是简单的,这一操作的代价也可能非常大甚至不切实际,于是有些操作直接将参数检查隐含在计算过程中。

比如java.util.Collections的sort()方法,列表中的元素当然是都可比较的,而方法并没有在开头检查有效性,而是计算中遇到问题时抛出异常。

与这种方式相比,提前检查有效性的意义确实不大。

但在计算途中检查参数的有效性需要考虑一点,即方法的原子性。

说道参数就不得不说方法重载,首先上一段例子:

import java.math.BigInteger;

import java.util.ArrayList;

import java.util.Collection;

import java.util.HashMap;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

public class CollectionClassifier {

public static String classify(Set> s) {

return "Set";

}

public static String classify(List> lst) {

return "List";

}

public static String classify(Collection> c) {

return "Unknown Collection";

}

public static void main(String[] args) {

Collection>[] collections = { new HashSet(),

new ArrayList(),

new HashMap().values() };

for (Collection> c : collections)

System.out.println(classify(c));

}

}

很常见的笔试题,会输出三次"Unknown Collection",编译时已决定了将调用的方法。

与重载方法的静态选择(为什么当初会这样设计重载? overriding is the norm and overloading is the exception,似乎没有人希望这种做法 )相对的是覆盖方法,overridden 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());

}

}

鉴于重载方法的这种特征,而语言本身对其也没有特别的限制,作者建议,而对于可变参数则不要考虑重载。

关于这个建议的不错的例子就是ObjectOutputStream,对于其write方法,设计者并没有提供相同参数数量的重载,而是提供了诸如writeInt,writeBoolean,writeLong等方法,而且read方法也是与write对称的。

这种方式不适用于构造器,我们无法对构造器进行命名,但我们可以对构造器进行私有化并导出静态工厂。

但也并不能说必须严格遵守这种规则,比如为fetchSalaryInfo()提供了两种方法重载,分别是int uid和User userInfo,很难想象会出现调用错误的情况。

破坏这一规则不仅仅是出现在导出某个方法的时候,更新现有类的时候也有可能。

比如String类有一个since 1.4的contentEquals(StringBuffer),另外还有一个since 1.5的contentEquals(CharSequence)。

(Java 1.5版本中增加了CharSequence接口,并作为StringBuffer,StringBuilder,CharBuffer,String等类的公共接口)

但这并不会带来危害,因为这个例子的两个重载方法的行为是完全一样的。

对于泛型还有下面这种有趣的情况:

import java.util.ArrayList;

import java.util.List;

import java.util.Set;

import java.util.TreeSet;

public class SetList {

public static void main(String[] args) {

Set set = new TreeSet();

List list = new ArrayList();

for (int i = -3; i < 3; i++) {

set.add(i);

list.add(i);

}

for (int i = 0; i < 3; i++) {

set.remove(i);

list.remove(i);

}

System.out.println(set + " " + list);

}

}

槽点:remove里的参数是index还是element?

执行结果是[-3,-2,-1][-2,0,2],也就是说 list.remove中的参数是index,而不是被自动装箱。

应该说问题根本原因是List同时提供了remove(int)和remove(E)吗?

但作为API的使用者,我们能做的仅仅是对Java 5的这一大特性持更谨慎的态度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值