深入理解Java中方法的参数传递机制
形参和实参
我们知道,在java中定义方法时,是可以定义参数的,比如:
public static void main(string[] args){ }
这里的args就是一个字符串数组类型的参数。
在程序设计语言中,参数有形式参数和实际参数之分,先来看下它们的定义:
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,简称“形参”。
实际参数:在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”,简称“实参”。
举个栗子:
public class paramtest { public static void main(string[] args) { paramtest pt = new paramtest(); // 实际参数为“张三” pt.sout("张三"); } public void sout(string name) { // 形式参数为 name system.out.print(name); } }
上面例子中,paramtest类中定义了一个sout方法,该方法有个string类型的参数name,该参数即为形参。在main方法中,调用了sout方法,传入了一个参数“张三”,该参数即为实参。
那么,实参值是如何传入方法的呢?这是由方法的参数传递机制来控制的。
值传递和引用传递
参数传递机制有两种:值传递和引用传递。我们先来看下程序语言中是如何定义和区分值传递和引用传递的:
值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
那么,在我们大java中,到底是值传递还是引用传递呢?
java中是值传递还是引用传递?
有了上面的概念,我们就可以一起来探究一下,java中方法参数到底是值传递还是引用传递了。
先看如下代码:
public class parampass1 { public static void main(string[] args) { parampass1 p = new parampass1(); int i = 10; system.out.println("pass方法调用前,i的值为=" + i); p.pass(i); system.out.println("pass方法调用后,i的值为=" + i); } public void pass(int i) { i *= 3; system.out.println("pass方法中,i的值为=" + i); } }
上面代码中,我们在类中定义了一个pass方法,方法内部将传入的参数i的值增加至3倍,然后分别在pass方法和main方法中打印参数的值,输出结果如下:
pass方法执行前,i的值为=10 pass方法中,i的值为=30 pass方法执行后,i的值为=10
从上面运行结果来看,pass方法中,i的值是30,pass方法执行结束后,变量i的值依然是10。
可以看出,main方法里的变量i,并不是pass方法里的i,pass方法内部对i的值的修改并没有改变实际参数i的值,改变的只是pass方法中i的值(pass方法中,i=30),因为pass方法中的i只是main方法中变量i的复制品。
因此同学们很容易得出结论:java中,一个方法不可能修改一个基本数据类型的参数 ,所以是值传递。
然而,结论下的还太早,因为方法参数共有两种类型:
- 基本数据类型
- 引用数据类型
前面看到的只是基本数据类型的参数,那对于引用类型的参数,又是怎么样的呢?看如下代码:
public class parampass2 { public static void main(string[] args) { parampass2 p = new parampass2(); user user = new user(); user.setname("张三"); user.setage(18); system.out.println("pass方法调用前,user=" + user.tostring()); p.pass(user); system.out.println("pass方法调用后,user=" + user.tostring()); } public void pass(user user) { user.setname("李四"); system.out.println("pass方法中,user = " + user.tostring()); } } class user { /** * 姓名 */ private string name; /** * 年龄 */ private int age; public string getname() { return name; } public void setname(string name) { this.name = name; } public int getage() { return age; } public void setage(int age) { this.age = age; } @override public string tostring() { return "user{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
上面代码中,定义了一个user类,在main方法中,new了一个新的user对象user,然后给user对象的成员变量赋值,pass方法中,修改了传入的user对象的属性。
运行main方法,结果如下:
pass方法调用前,user= user{name='张三', age=18} pass方法中,user = user{name='李四', age=18} pass方法调用后,user= user{name='李四', age=18}
经过pass方法执行后,实参的值竟然被改变了!!!那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么?
有同学可能会说:难道在java的方法中,在传递基本数据类型的时候是值传递,在传递引用数据类型的时候是引用传递?
其实不然,java中传递引用数据类型的时候也是值传递。
为什么呢?
先给大家说一下概念中的重点:
值传递,是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递,是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
总结下两者的区别:
值传递 | 引用传递 | |
---|---|---|
根本区别 | 会创建副本 | 不会创建副本 |
所以 | 函数中无法改变原始对象 | 函数中可以改变原始对象 |
敲黑板:复制的是参数的引用(地址值),并不是引用指向的存在于堆内存中的实际对象。
main方法中的user是一个引用(也就是一个指针),它保存了user对象的地址值,当把user的值赋给pass方法的user形参后,即让pass方法的user形参也保存了这个地址值,即也会引用到堆内存中的user对象。
上面代码中,之所以产生引用传递的错觉,是因为参数保存的是实际对象的地址值,你改变的只是地址值指向的堆内存中的实际对象,并没有真正改变参数,参数的地址值没有变。
下面结合生活中的场景,再来深入理解一下值传递和引用传递。
你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?
我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙(地址值),而是钥匙打开的房子(地址值对应的实际对象)。
那我们如何真正的改变参数呢,看如下代码:
public class parampass3 { public static void main(string[] args) { parampass3 p = new parampass3(); user user = new user(); user.setname("张三"); user.setage(18); system.out.println("pass方法调用前,user= " + user.tostring()); p.pass(user); system.out.println("pass方法调用后,user= " + user.tostring()); } public void pass(user user) { user = new user(); user.setname("李四"); user.setage(20); system.out.println("pass方法中,user = " + user.tostring()); } } class user { /** * 姓名 */ private string name; /** * 年龄 */ private int age; public string getname() { return name; } public void setname(string name) { this.name = name; } public int getage() { return age; } public void setage(int age) { this.age = age; } @override public string tostring() { return "user{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
在这段代码中,pass方法中,我们真正的改变了user参数,因为它指向了一个新的地址(user = new user()),即参数的地址值改变了。运行结果如下:
pass方法调用前,user= user{name='张三', age=18} pass方法中,user = user{name='李四', age=20} pass方法调用后,user= user{name='张三', age=18}
从结果看出,对参数进行了修改,没有影响到实际参数。
所以说,java中其实还是值传递的,只不过对于引用类型参数,值的内容是对象的引用。