网上查了一些资料,有的说Java里面既有引用传递,也有值传递;也有的说Java只有值传递。其实两种说法都是对的,关键是看你怎么理解它们的含义。首先要先搞明白,什么是“值”,什么是“引用”?
一般我们认为“值”就是基本的数据类型:整数,浮点,字符和布尔。“引用”则是指向堆内存中对象的一个地址。
首先把“值”和“引用”区分开来理解下
Java中我们通常是这样定义一个变量的: Type variable = ?
“=” 号左边的比较好理解,Type
是一个兼容 “=” 号右边的一种类型,可以是基本类型,也可以是引用类型。variable
是我们定义的变量名。看下面的例子:
1 | int a = 0; |
上面的int a = 0;
,“=”号左边的int a
会告诉编译器我们定义了一个int
的基本数据类型和名为a
的变量,这个变量存在栈中,并且编译器也知道了这是一个基本类型的变量,然后“=”号右边的0
也是直接存储在栈中并赋值给变量a
。这时候我们可以理解为a
的值为0
。
再看User user = new User();
,“=”号左边的User user
告诉编译器定义我们定义了一个User
的对象(引用数据类型)和名为user
的变量,并且编译器知道了这是一个引用类型的变量,然后“=”号右边的new User()
会在堆内存中开辟一块空间存储User
这个对象,并生成一个唯一的地址值在存储在栈中,最后赋值给变量user
。此时user
是一个地址值,它指向堆中的User对象,user
这个变量它就是堆中的User
对象的引用值(不是像0
这样的基本数据值直接存在栈中的)。我们把这种连接关系称为“引用”。
讲到这里,大家应该就已经明白,所谓的“引用”和“值”是什么了。简单地说,“值”就是直接在栈中取出来就能访问并且直接修改,而“引用”虽然也是存储在栈中,但显然不能直接访问,需要通过.
这种语法来访问并修改堆中的对象各个属性的值。
先来看下基本类型的存取流程
1 | int a = 0; |
结果:a = 1, b = 0
上面给变量a
赋值为0
后,又把b
赋值为a
,那么问题来了,a
是什么?a
就是0
,所以b
就是0
。注意:b
不是a
!这是赋值操作。是把a
的值给了b
。紧接着马上给a
赋值为1
,这里其实是把a
原来为0
的值更换成了1
,而b
是不会受到a
的影响的,b
的值仍然是0
。
再来看下引用类型的存取流程(可能比较难理解)
1 | public static void change(User user) { |
上面输出的结果分别为:
1 | Mike |
如果能理解上面的注释,说明你已经理解了Java中的值和引用了。
这里再补充一下其它的知识点,change方法里面重新创建的User对象跑去哪里了?在执行完最后一行代码后System.out.println(user.getUsername());
,离开了这个方法块(也叫做 作用域),变量user
就出栈了,它已经不存在了,而在方法中 new User()
出来的这个新的对象还存在堆中,因为这个新的对象已经没有变量指向它了, 所以它会被当成垃圾,会在随后的一段时间里经过垃圾回收器的算法来自动释放掉。
再来看下change方法中第一行代码user.setUsername("Bob");
,这里根据传进来的“引用”修改了原来堆中的对象中的属性值,由于方法外部的user
和方法内部的’user’指向堆中的同一个对象,所以会看到最后的userName
的值为”Bob”,就是所谓的“引用传递”了。
最后我们把“值”和“引用”一起理解为“值”
再来看下有什么不同
1 | int a = 0;//a赋值为0 |
上面的代码很好理解,就是变量的值一直在改变,一直在进行赋值操作。
1 | public static void change(int a, int[] arr) { |
打印结果:
1 | a=0, arr=[1, 2] |
为什么a的值没有改变?
我是这么理解的,栈中的数据不是共享的,每块代码块之间的变量和值不会相互影响,并且超过作用域后就会消失。堆中的数据是共享的。,我们在外面传入了一个arr的“地址值”,里面的代码arr[1] = 10;
是根据arr
的“地址值”访问并操作了数组里面的元素值,arr
这个变量并没有重新赋值,外面在访问这个“地址值”就会看到它的元素值已经被修改。假如里面的代码是arr = {5,6}
,此时arr
变量被重新赋值为一个新的数组了,那么相当就会在堆中创建一个新的数组,然后将arr
指向这个新的“地址值”,而并没有访问原来“地址值”所对应的数组。
所以,上边也提到了,访问对象是需要通过.
来进行访问和修改的,如user.name
,数组元素是通过下标来访问的,如arr[0]
。而“=”号是赋值操作,会重新生成一个“值”。这个值要么就是基本数据的值,要么就是用来访问对象或数组的内存值(它们都是存在栈中),所以我们可以统一理解为“值”。
说了那么多,就看各位怎么理解了,“引用”也是值,但我们习惯叫它“引用”,就是因为它是一个特殊的“值”。
Java中还有一类特殊的引用类型,就是基本类型的包装类,如Integer,Boolean,Character,Float,Long等等,它们虽然也是引用类型,但是却不能直接访问并修改它们的值,只能通过特殊手段来修改,但这样是不规范的。可以看到它们的源码的类和字段都定义了
final
关键字。有兴趣的小伙伴可以自己动手试试。