开门见山
发现:代码对一个参数进行赋值。
解决:以一个临时变量取代该参数的位置。
//重构前
int dicount(int inputVal, int quantity, int yearToDate){
if(inputVal > 50) inputVal-=10;
}
//重构后
int dicount(final int inputVal, int quantity, int yearToDate){
int result = inputVal;
if(result > 50) result-=10;
}
动机
我想你很清楚“对参数赋值”这个说话的意思。如果把一个名称为fool的对象作为参数传递给某个函数,那么“对参数赋值”意味改变fool,使它引用另一个对象。但是,如果在“被传入对象”身上进行什么操作,那没问题,我们经常会这么做。这里只针对“fool被改变而指向另一个对象”这种情况来讨论:
void test(Object fool){
fool.changedBySomeWay(); //that's ok
fool=anotherObject; //trouble will appear
}
我们之所不这样做,是因为它降低了代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式。JAVA只采用按值进行传递。
在按值传递的情况下,对参数的任何修改,都不会对调用端造成任何影响。如果你只以参数表示“被传递进来的东西”,那么代码会清晰的多,因为这种用法在所有语言中都表现出相同的语义。
在Java中,一般不要对参数赋值:如果你看到手上的代码已经这么做了,就应该使用本文的方法。
做法
(1)建立一个临时变量,把待处理的参数值赋赋予它。
(2)以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”。
(3)修改赋值语句,使其改为对新建之临时变量赋值。
(4)编译,测试。
(如果代码的语义是按照引用传递的,需在调用端检查调用后是否还使用了这个参数。也要检查有多少个按引用传递的参数被赋值后又被使用。应该尽量以return方式返回一个值。如果返回值有多个,可考虑将需返回的一大堆数据变为对象,或者为每个返回值设定一个独立的函数)
示例
我们从一个简单代码开始:
int dicount(int inputVal, int quantity, int yearToDate){
if(inputVal > 50) inputVal-=5;
if(quantity > 100) quantity-=10;
if(yearToDate > 1000) yearToDate-=100;
return inputVal;
}
以临时变量取代对参数的赋值动作,得到下列代码:
int dicount(int inputVal, int quantity, int yearToDate){
int result = inputVal;
if(result > 50) result-=5;
if(quantity > 100) quantity-=10;
if(yearToDate > 1000) yearToDate-=100;
return result;
}
可以为参数加上final关键词,强制其遵循“不对参数赋值”这一惯例:
int dicount(final int inputVal, final int quantity, final int yearToDate){
int result = inputVal;
if(result > 50) result-=5;
if(quantity > 100) quantity-=10;
if(yearToDate > 1000) yearToDate-=100;
return result;
}
JAVA的按值传递
我们应该都知道,JAVA使用按值传递的函数调用方式,这常常也会使大家迷惑。在所有地点,JAVA都会遵循严格按值传递:
//JAVA按值的传递
class Params{
public static void main(String[] args) {
int x = 10;
triple(x);
System.err.println("x after triple:" + x);
}
private static void triple(int arg){
arg = arg * 3;
System.err.println("arg in triple:" +arg );
}
}
//输出
//arg in triple:30
//x after triple:10
上面代码是使用基本数据类型进参数传递,还不至于让人糊涂。但如果参数中传递的是对象,就可能把人弄糊涂。如果在程序中以Date对象表示日期,下列程序所示:
//以对象为参数
class Params{
public static void main(String[] args) {
Date d1 = new Date(2015,1,1);
nextDateUpdate(d1);
System.err.println("d1 after nextday:" + d1);
Date d2 = new Date(2015,1,1);
nextDateReplace(d2);
System.err.println("d2 after nextday:" + d2);//61380864000000
}
private static void nextDateUpdate(Date d) {
d.setDate(d.getDate()+1);
System.err.println("arg in nextday d1 : "+d);
}
private static void nextDateReplace(Date d) {
d = new Date(d.getYear(),d.getMonth(),d.getDate()+1);
d=null;
System.err.println("arg in nextday d2: "+d);
}
}
//输出
/*
arg in nextday d1 : Tue Feb 02 00:00:00 CST 3915
d1 after nextday: Tue Feb 02 00:00:00 CST 3915
arg in nextday d2: Tue Feb 02 00:00:00 CST 3915
d2 after nextday: Mon Feb 01 00:00:00 CST 3915
*/
从本质上说,对象的引用是按值传递的。因为可以修改参数对象的内部状态,但对参数对象重新赋值是没有意义的。