前言
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,下面总结一下这个问题。Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。
基本类型的变量保存原始值,即它代表的值就是数值本身;
而引用类型的变量保存引用值,引用值
指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
基本类型包括:byte,short,int,long,char,float,double,Boolean
引用类型包括:类类型,接口类型和数组。
变量的基本类型和引用类型的区别
基本数据类型在声明时系统就给它分配空间:
1 | int a; |
引用则不同,它声明时只给变量分配了引用空间,而不分配数据空间:
1 | Date date;//执行实例化,开辟数据空间存放Date对象,然后把空间的首地址传给date变量 |
看一下下面的初始化过程,注意”引用”也是占用空间的,一个空Object对象的引用大小大概是4byte:
1 | Date a,b; //在内存开辟两个引用空间 |
值传递和引用传递
这里要用实际参数和形式参数的概念来帮助理解:
- 形式参数:定义方法时写的参数。
- 实际参数:调用方法时写的具体数值。
值传递
方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。
1 | public class Test { |
很显然输出的 是10,10。传递的是值得一份拷贝,这份拷贝与原来的值没什么关系。内存分析:
引用传递
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。
1 | public class Test { |
显然输出结果为10 50。实际传递的是引用的地址值。内存分析:
再看一个引用传递的例子:
1 | class Emp { |
输出为:100 50 50。内存分析:
String类型传递
这里要特殊考虑String,以及Integer、Double等几个基本类型包装类,它们都是immutable类型,因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待,可以认为是和基本数据类型相似,传值操作。
先看一个String和StringBuffer的例子:
1 | public class ReferencePkValue1 { |
String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。
1 | public static void main(String[] args) { |
String是一个类,类是引用数据类型,做为参数传递的时候,应该是引用传递。但是从结果看起来却是值传递。原因:
String的API中有这么一句话:their values cannot be changed after they are created
, 意思是:String的值在创建之后不能被更改。 API中还有一段:
1 | String str = "abc"; |
也就是说:对String对象str的任何修改 等同于 重新创建一个对象,并将新的地址值赋值给str。String对象做为参数传递时,走的依然是引用传递,只不过String这个类比较特殊。 String对象一旦创建,内容不可更改。每一次内容的更改都是重现创建出来的新对象。 当change方法执行完毕时,s所指向的地址值已经改变。而s本来的地址值就是copy过来的副本,所以并不能改变str1的值。
String类型类似情况:
1 | public void call(Test t) { |
java并没有c++中指针、地址的概念,它只有句柄(handler)的概念。
总共构建了两个Test对象,假设称main方法构建的对象为对象1
,call方法构建的对象为对象2
, 在main方法中,变量obj获得了对象1
的句柄, 在参数传递中,变量obj把这个句柄传递给变量t, 在call方法中,变量t首先改变了对象1
的属性,然后变量t又获得了对象2
的句柄(但obj仍然是对象1
的句柄), call方法返回后,由于对象2
失去了唯一的句柄,不可避免的进入垃圾收集器的视线。而obj仍然是对象1
的句柄,由于对象1
的属性已经被重新设置,所以我们可以看到打印出来的结果是abc
。
所以:在Java中方法参数的传递,对象是传递引用,基本数据类型是传递值。对于值类型的参数来说,传递的是值的拷贝。对于引用类型的参数来说,传递的是引用本身的拷贝。
值传递和引用传递的区别
总结:值传递和引用传递的区别在于引用传递是否对对象的属性进行修改操作,若无修改操作,则类似于“值传递”,对形参的修改不会影响实参;若对对象的属性进行了修改操作,因为引用本身和引用的拷贝指向同一块地址,所以修改操作会影响到实际对象。