java代码优化——方法

检查方法中参数的有效性

请在编写方法时考虑参数的有效性,一般的需要在方法体的开头校验参数的有效性。例如ArrayList的get方法中参数i是一个非负数,如果输入一个负数将会报错:ArrayIndexOutOfBoundsException,这是因为在get方法体首先对i做了检查:

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
	RangeCheck(index);

	return (E) elementData[index];
    }

如果小于0或者大于数组长度将会抛出异常,这样做的好处是避免错误的参数在被使用时抛出的另类异常

公共方法需要在JavaDoc中增加@Throws 声明抛出哪些异常。

But!但是也有一些例外的情况,有些时候检查参数有效性代价昂贵,在方法体内已经对参数进行有效性检查,这时候再检查参数有效性显然是做无用功。例如Collections.sort(List)排序之前需要判断list中的元素是否类型相同,才作比较,但是这个操作是在sort方法内部已经实现过了,因此不必再重复检查参数有效性。

 

必要时进行保护性拷贝

请观察下面的代码有什么地方不妥?

public final class Period {

	private final Date start;
	private final Date end;
	
	public Period(Date start,Date end){
		if(start.compareTo(end)>1){
			throw new IllegalArgumentException(start+"after"+end);
		}
		this.start = start;
		this.end = end;
	}

	public Date getStart() {
		return start;
	}

	public Date getEnd() {
		return end;
	}

}

乍一看,构造器方法复合上一条说的需要检查参数的有效性,并且这个类是不可变的。但是成员变量Date类型是可变的,客户端可以通过修改Date来攻击该类,下面这段客户端的代码可以改变Period中的start的值。

	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		Date start = new Date();
		Date end = new Date();
		Period period = new Period(start, end);
		System.out.println(period.getStart());//2019-1-1 14:22:35
		period.getStart().setYear(1900);
		System.out.println(period.getStart());//2020-1-1 14:22:35
	}
	

为了保护Period实例的内部信息免遭攻击,需要对构造器进行保护性拷贝,以及确保客户端不能改变该类的内部信息。

public final class Period{

	private final Date start;
	private final Date end;
	
	public Period(Date start,Date end){
		this.start = new Date(start.getTime());
		this.end = new Date(end.getTime());
	}

	public Date getStart() {
		return new Date(start.getTime());
	}

	public Date getEnd() {
		return new Date(end.getTime());
	}

}

总结:如果客户端能从类实例中获得可变的成员变量,就需要对该变量进行保护性拷贝,如果变量拷贝的成本大,就需要确保该变量不会被客户端修改,并且在javadoc中注明该变量是不得修改的。

 

谨慎设计方法签名

谨慎地选择方法名称

方法名称要能体现该方法的作用是什么。还要注意一些框架中特定的命名规范,例如,struts2的action类中的方法不能命名为getXXX方法,因为struts2中对get方法是用来注入成员变量用的。

不要过于追求提供便利的方法

每个方法都应该尽其所能,方法太多会难以记住。

避免过长的参数列表

参数过长不利于我们记住方法的调用,可以将参数封装在一个对象中。

对于参数类型,要优先使用接口而不是类

例如Map<String,String> map = new HashMap<String,String>();绝对不要使用HashMap作为参数类型,这样会限定实例化类型只能为参数类型。

 

慎用重载

考虑下面的代码,它将根据传入的参数类型List、Set、Collection来进行分类。

public class CollectionClassifier {
	public static String classfy(Set<?> s){
		return "set";
	}
	public static String classfy(List<?> list){
		return "list";
	}
	public static String classfy(Collection<?> c){
		return "unkonwn Collection";
	}
	public static void main(String[] args) {
		Collection<?> collecton[] = {new HashSet<String>(),new ArrayList<String>(),new HashMap<String,String>().values()};
		for (Collection<?> c : collecton) {
			System.out.println(classfy(c));
		}
	}
}

我们期望会打印set、list、unkonwn Collection。但是打印为3个unkonwn Collection!因为选择哪一种重载方法是在编译时已经决定了,所以参数类型都是Collection<?>,打印的都是unknown Collection。

对于重载方法的选择是静态的,即在编译时才确定用哪一个重载方法。

对于覆盖方法的选择是动态的,即在代码运行时根据实例化子类对象的类型来确定覆盖方法的。

观察下面的例子,它将list、set中增加值,然后删除非负数:

	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<Integer>();
		Set<Integer> set = new TreeSet<Integer>();
		for (int i = -3; i < 3; i++) {
			list.add(i);
			set.add(i);
		}
		System.out.println(list.toString());
		System.out.println(set.toString());
		for (int i = 0; i < 3; i++) {
			list.remove(i);
			set.remove(i);
		}
		System.out.println(list.toString());
		System.out.println(set.toString());
	}

我们期望它打印的结果是list:-3,-2,-1.set:-3,-2,-1

但是结果确实list:-2,0,2.set:-3,-2,-1.

这是因为list.remove()方法有两个重载方法remove(int index)和remove(E),我们期望使用remove(E)的方法,但是结果却是用remove(ine index)。

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
	RangeCheck(index);

	modCount++;
	E oldValue = (E) elementData[index];

	int numMoved = size - index - 1;
	if (numMoved > 0)
	    System.arraycopy(elementData, index+1, elementData, index,
			     numMoved);
	elementData[--size] = null; // Let gc do its work

	return oldValue;
    }
    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
	if (o == null) {
            for (int index = 0; index < size; index++)
		if (elementData[index] == null) {
		    fastRemove(index);
		    return true;
		}
	} else {
	    for (int index = 0; index < size; index++)
		if (o.equals(elementData[index])) {
		    fastRemove(index);
		    return true;
		}
        }
	return false;
    }

将上面的list的删除操作用下面的替换之,则达到期望的结果:

list.remove((Integer)i);

慎用可变参数

可变参数:方法参数中用‘...’来表示某一种参数类型数量可变。

 

返回零长度的数组或者集合,而不是null值

程序员往往会忘记判断返回值的非空性,而且这种操作是完全可以避免的,所以方法返回零长度的数组或者集合,那么就不要再客户端代码中做额外的处理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值