Java中的值传递和引用传递
Java中的数据类型
Java中的数据类型我们知道分为基本数据类型和引用数据类型两种,所以对应存储他们的变量也分为基本数据类型的变量和引用数据类型的变量。
基本数据类型变量之间的赋值(传递)
对于基本数据类型,基本数据类型变量之间的传递均是值传递。那什么是值传递?
以int
类型来说,看个例子:
public static void main(String[] args) {
int m = 44;
int n = m;
System.out.println("m = " + m + ",n = " + n);//m = 44,n = 44
m++;
System.out.println("m = " + m + ",n = " + n);//m = 45,n = 44
n = n + 4;
System.out.println("m = " + m + ",n = " + n);//m = 45,n = 48
changeBaseType(m,n);
System.out.println("m = " + m + ",n = " + n);//m = 45,n = 48
}
public static void changeBaseType(int a,int b) {
a = a + 5;
b = a + 10;
System.out.println("a = " + a + ",b = " + b);//a = 50,b = 56
}
先分析结果:
第一个输出语句是没什么问题,将m
赋值给n
,所以结果是:m = 44
,n = 44
。
第二个输出语句,输出前执行了m++;
,结果m
的值加1,变为45,没问题,那n
的值为什么还是44而不是45?
因为上面的int n = m;
这句赋值代码来说,其过程为:首先m
存储的值是44,等进行n = m
赋值时,系统会复制一份44这个值的副本,然后将这个副本赋值给n
,所以最后结果是m
和n
虽然存储的都是44这个值,但是并不是同一个44,两者的内存地址不同,是互不相干的两个对象。因此当其中一个发生变化时,并不会影响另一个。
所以第二个输出语句前虽然执行了m++
导致m
由原来的的44变为45,但n
的值依旧为44,所以结果为:m = 45
,n = 44
。
同理可得,n
变化时,m
不变,所以第三个输出语句结果为:m = 45
,n = 48
。
再来看第四个输出语句,第四个输出语句前,我们将m
和n
传给changeBaseType(int a,int b)
这个方法并执行了该方法,进入方法前我们先说方法的参数问题,changeBaseType(int a,int b)
带了两个参数a
和b
(方法中的参数我们称为形式参数),形参我们可理解为方法内另外定义的变量,所以,这里我们可理解为这方法中定义了a
和b
这两个变量,当我们将实参m
和n
传给方法时,系统通过a = m
和b = n
赋值,赋值过程我们上面说过了,最终a
和b
存储的分别是m
和n
所存储值的副本,所以方法里面都是对副本 进行操作,并不会影响m
和n
所存储的值。所以main方法中的第四个输出语句结果为m = 45
,n = 48
,而不是方法中a
和b
的值a = 50
,b = 56
。这里注意方法中的参数名我故意写成a
和b
而不是m
和n
是为了更方便理解,方法中的参数名可以随意取,写成m
和n
也可以,一般情况为了方便,习惯将形参名字和实参名字保持一致(即将a、b取名为m、n),但注意,实际并不是同一个东西。
引用数据类型变量间的赋值(引用传递)
引用数据类型变量之间的传递是引用传递(其实还是值传递,因为根本上来说,引用数据类型变量间的赋值,是内存地址的赋值,而这个内存地址的赋值是上面已经说过的值传递。
例如:
Person p1 = new Person("张三");
Person p2 = p1;
这两句代码实际上只创建了一个Person("张三")
实例。Person p1 = new Person("张三");
这句代码先是通过new
关键字申请内存创建了一个Person("张三")
实例,然后赋值给了p1
这个变量,赋值结果是p1
并没有直接存储Person("张三")
这个实例本身,而是存储了它所在的内存地址,p1
通过这个内存地址间接存储Person("张三")
这个实例。就好比你要找人,你得先知道人家在哪,知道人家具体位置才能找到人。虽然变量p1
存的是实例对象所在的内存地址,但我们对变量p1
做的操作全都会反映到p1
所指向的实例对象上。
Person p2 = p1;
这个赋值语句只是将变量p1
存储的内存地址复制了一个副本,然后将这个内存地址的副本赋值给了p2
,结果是p1
、p2
存储的内存地址相同,有没有似曾相识的感觉?没错,其实就是上面的值传递,既然存的内存地址相同,也就是说p1
、p2
指向的是同一块内存,因此两者指向的都是同一个Person("张三")
实例。整个过程下来只有一个实例对象,只是p1
、p2
这两个变量都指向了这个对象实例,就相当于一个人有两个外号一样。
接下来看下面的例子:
public void test(){
Person p1 = new Person("张三");
Person p2 = p1;
System.out.println("p1 = " + p1 + ",p2 = " + p2);//p1 = Person{name=张三},p2 = Person{name=张三}
p2.setName("张三111");
System.out.println("p1 = " + p1 + ",p2 = " + p2);//p1 = Person{name=张三111},p2 = Person{name=张三111}
p2 = new Person("李四");//更改引用,此时p2已经指向了另外一个实例对象
p2.setName("李四111");
System.out.println("p1 = "+ p1 +",p2 = "+ p2);//p1 = Person{name=张三111},p2 = Person{name=李四111}
}
第一打印语句结果应该没什么疑问。
第二个打印结果,打印前,我们通过代码p2.setName("张三111");
将p2
所指向实例的name
字段的值由原来的“张三”改为了“张三111”,结果发现p1
的name
字段也变成了“张三111”,这是为什么?这就得回到上面说的引用数据类型的变量赋值过程。上面我们说了Person p2 = p1;
这句代码的结果是p1
和p2
指向了同一个Person(“张三”)
实例,因此当我们通过p2
将它所指向对象中的name
字段由原来的“张三”改为“张三111”后,p1
自然的也会跟着变化。
第三个打印语句,打印前执行了p2 = new Person("李四");
和p2.setName("李四111");
然后在打印p1
,发现p1
没有发生改变,前面我们说了p1
、p2
指向的是同一个实例对象,p1
或p2
只要其中一个发生改变,另外一个也会跟着变化。但这里p2
发生了变化,p1
为什么没跟着改变?
这里注意,p2 = new Person("李四")
这句代码已经通过new
关键字重新创建了一个Person("李四")
实例,然后赋值给了p2
。这时p2
存储的是Person("李四")
这个实例的内存地址而不是之前Person("张三111")
这个实例的内存地址了,而p1
存储的还是Person("张三111")
这个实例的地址,此时p1
和p2
已经指向了不同的实例,两者已经不相关了,所以两者互不影响,后面p2.setName("李四111");
这句代码和p1
就更加没有关系了,p1
自然也就不会跟着变化。
至于调用带形参的方法,这个和上面的值传递一样,只要知道,方法的形参其实可以理解为方法内部另外定义的变量,传入实参时就是赋值操作,只是对基本数据类型的变量赋值是值传递,而对于引用数据类型的变量赋值只是把实例所在的内存地址赋值给变量而已,上面已经说了这个内存地址的赋值是值传递,这也是为什么大家都说Java中其实只有值传递没有引用传递的原因。