一、前言
首先先说结论,Java中方法参数传递方式是按值传递。如果参数是基本类型,传递的是基本类型的字面量值的拷贝。如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。
接下来深入了解一下为什么是值传递,要想知道Java到底是传值还是传引用,首先要知道基本类型和引用类型的区别。
二、深入了解参数传递
1.基本类型 和 引用类型的不同之处
基本类型包括8种数据类型:int、short、long、byte、char、float、double、boolean,在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的;除了基本类型以外的都是引用类型:类、接口类型、数组类型、字符串类型都是引用类型,引用类型变量其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址(字符串类型比较特殊,涉及到字符串常量池,这里不做深入研究)。
示例:
int num = 10; String str = new String("hello");
注:该图的堆区是经过简化的,实际的情况会复杂点,这里只作示意
如图所示,num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象在堆中并保存着实际内容。
2.赋值运算符(=)的作用
num = 20; str = "world";注:该图的堆区是经过简化的,实际的情况会复杂点,这里只作示意 对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。 对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变。如上图所示,"world" 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)。 3.值传递和引用传递的区别 值传递是指在调用函数时将实际参数复制一份传递到函数中,引用传递是指在调用函数时将实际参数的地址直接传递到函数中,所以值传递和引用传递的根本区别是是否会复制一份副本。 4.实例 例子一(基本数据类型):
public static void main(String[] args) { Test test = new Test(); int i = 10; test.print(10); System.out.println("main方法输出i:" + i); } public void print(int j) { j = 20; System.out.println("print方法输出j:" + j); }
结果:
print方法输出j:20
main方法输出i:10
这个例子应该还是很好理解的,test.print(10)将10作为参数传给print方法, 将10拷贝一份给 j,修改 j 不会影响 i 。
例子二(引用数据类型):
public static void main(String[] args) { Test test = new Test(); User user = new User(); user.setName("Tom"); user.setAge("18"); test.print(user); System.out.println("main方法输出用户:" + user); } public void print(User user1) { user1.setName("Mike"); System.out.println("print方法输出用户:" + user1); }
结果:
print方法输出用户:User{name="Mike",age="18"} main方法输出用户:User{name="Mike",age="18"}
解释: test.print(user)将user对象的引用(即user对象的内存地址)拷贝一份给形参的user1,也就是说main方法中的user引用和print方法的user1引用都指向堆中的同一个user对象,所以user1修改user对象的数据,user的也会相应的改变。
user1.setName("Mike")执行前:
user1.setName("Mike")执行后:
注意:引用数据类型中的字符串类型比较特殊,String被设计成为了不可变类型,为String赋值时不会覆盖以前的对象而是引用一个新的字符串对象(如果新的字符串在常量池中直接返回其引用,否则创建一个字符串对象,详情可以去我的另一篇博客浅析Java常量池 - pluto_blog - 博客园 (cnblogs.com)),在这里我们不考虑新字符串在常量池的情况。 下面来看各String类型的例子:public static void main(String[] args) { Test test = new Test(); String name = new String("Tom"); test.print(name); System.out.println("main方法输出name:" + name); } public void print(String name1) { name1 = "Mike"; System.out.println("print方法输出name1:" + name1); }
结果:
print方法输出name1:Mike main方法输出name:Tom
解释:由于String被设计成为了不可变类型,为name赋值时不会覆盖以前的对象而是创建一个新的字符串对象并返回引用。
name1 = "Mike"执行前:
name1 = "Mike"执行后:
三、总结
Java中方法参数传递方式是按值传递。如果参数是基本类型,传递的是基本类型的数据拷贝。如果参数是引用类型,因为栈中存的是对象的地址值,所以传递的是该参量所引用的对象在堆中地址值的拷贝,除了特殊的String类型,形参对象可以影响实参对象的值。