JAVA深入探索-参数传递
按值传参
JAVA中的参数传递只存在传值方式一种,但也有传引用的概念。
这是java参数传递的核心说明。java不像C/C++那样可以通过指针符或地址符来区分传值还是传引用,因为它只有一种参数传递的方式,那就是传值方式。这对初学java的人来说很难理解,通过下面的例子可以看出真正的传值在java中是如何实现的。
public static void swap(int a,int b){//交换两个变量的值
int temp = a;
a = b;
b = temp;
System.out.println("swap:"+a+","+b);
}
......
int a = 3;
int b = 5;
swap(a,b);
System.out.println(a +" "+b);//3 5
......
程序打印结果:
swap:5,3
3 5
由程序的结果可以看到,事实上,经过交换的方法swap()之后,主程序中的变量a,b的值并没有被改变。
分析过程如下:在调用swap()方法的时候,程序把a、b的副本送到swap()方法中的变量a、b中去,而主程序中的变量a、b中的内容并没有被改变。所以就会出现上面的结果。
其实,不仅是简单的数据类型如此 ,如果传递的参数是一个对象,也会出现这样的结果,这与简单数据类型的情况类似,都可以划为按值传参一类中。我们看下面的程序及其分析过程:
public class User{
static void swap(User user1,User user2){
User user = user1;
user1 = user2;
user2 = user;
}
public static void main(String[]args){
User user3 = new User("ding", 20);
User user4 = new User("zhao", 18);
swap(user3, user4);
/*进行函数swap(user1,user2)的调用,但是并不会真正把user3和user4所指向的内存空间
*进行交换,只是在调用的时候,会把user3中存放的内存地址复制一份传给user1,把user4中存放的
*内存地址复制一份传给user2,相当于只是给user1和user2了一个副本,而真正的对象
*user3和user4并没有在swap()函数中被触及。所以函数处理的结果是使user1和user2所指向的内存空间
*进行交换,而user3和user4中的存放内容仍是原来的内容。*/
System.out.println(user3.username + " " +user3.age);//ding 20
System.out.println(user4.username + " " +user4.age);//zhao 18
}
程序的运行结果:
ding 20
zhao 18
传递引用
既然说java中的参数传递只有by value一种,为什么还要说传递引用呢?实际上,java与C++一样,同样存在对一个对象的引用进行传递的问题,但java中的引用传递机制是,把原来变量中保存的内存地址传递作为一个参数进行传递,而不是直接把引用传过去。所以在java中仍把它称做按值传参。
同样采用例子的方式来解释java中的引用传递的问题:
.......
public static void changAge(User user){
User temp = user;//这时,temp中存放了和原对象相同的内存地址
temp.age = temp.age + 20;//对user的年龄进行增加的操作,
/*是对temp所指向的内存空间中的值直接进行操作,所以会对原对象的值造成影响。就相当于是传递了原对象的引用*/
}
......
User user = new User("li",25);//仍采用上例中的User
changAge(user);//改变user的年龄
System.out.println(user.age);//45
.......
有了上面例子的说明,我们可能就会想到,如果我们的程序中私有变量是一个对象类型的变量时,在主程序中有了这个私有变量的拷贝,是不是就有可能在修改这个拷贝时不小心把原来的私有变量的值也给改变了呢?
我们来看下面的例子:
class Test{
private Date date=new Date();
public Date getDate(){
return date;
}
public static void main(String[] args){
Test tt=new Test();
Date myDate=tt.getDate();//返回了一个私有变量的拷贝
System.out.println(myDate);
myDate.setTime(new Date().getTime()-(long)(10*365.25*24*3600*1000));//对新产生的拷贝进行修改
System.out.println(tt.getDate());
System.out.println(myDate);
}
}
先来猜一下运行的结果,是前两句输出一样呢还是后两句输出一样(最后一句输出比第一句输出的日期早十年)?很多人都会说是前两句输出一样.实际上,你会惊奇地发现,输出结果显示后两者输出一样,私有变量在程序外部被改变了,程序的封装性遭到了破坏。
出错的原因很微妙.因为myDate和tt指向了同一个对象,对myDate的引用更改方法自动地改变了这个类的私有方法状态。经过测试可以知道,如果myDate被重新赋值(比如myDate=new Date()),就不会出现上面的结果。但是现在的这个程序,私有变量还是在程序外部被改变了。
如果需要返回一个指向可变对象的引用,我们就需要克隆它,这样就不会导致上面的私有变量被更改。
上面程序就应更改为:return (Date)date.clone();就可以防止私有变量被修改的麻烦了。
再来执行上面的程序,就会出现不一样的结果。