代码写的越急,程序跑得越慢。—— Roy Carlson
时间过得真快,2020已经过去了一半,但是疫情好像还没有真正的消灭,人们出行还是得带着口罩,天气越来越热,受罪啊。
言归正传,都2020年了,居然还有人认为JAVA的参数传递方式是引用传递,今天我就来讲一讲java的参数传递,好好看,写的不对的地方,请大声说出来,反正我也不会改,憋坏了就不好了。
我们先来看一个普通的例子
package com.ymy.param;
/**
* @ProjectName: demo
* @Package: com.ymy.param
* @ClassName: BaseTypeTest
* @Author: 流星007
* @Description: 基本数据类型传递
* csdn:https://blog.csdn.net/qq_33220089
* 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
* @Date: 2020/7/5 12:52
* @Version: 1.0
*/
public class BaseTypeTest {
public static void main(String[] args) {
int a = 1;
dosomthing(a);
System.out.println("主函数a的值 = "+a);
}
private static void dosomthing(int a) {
a = a-1;
System.out.println("修改过后,a = "+a);
}
}
1234567891011121314151617181920212223242526272829
这是一个很简单的一个方法,在主函数main中对变量进行了初始化a=1,然后将a传递给dosomthing(),然后再dosomthing中输出了修改之后的值,最后在主函数中打印a的值,你们觉得这几句输出中a的值分别是多少呢?
第一种:修改过后,a = 0主函数a的值 = 1第二种:修改过后,a = 0主函数a的值 = 0第三种:修改过后,a = 1主函数a的值 = 1
想要得到答案的话就得先明白参数传递的两个类型:值传递和引用传递。
什么是引用传递?在C++中,函数参数的传递方式有引用传递。所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
什么是值传递?值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
我们再回过头来看上面的例子,如果是引用传递的话打印结果应该是第二种情况,如果是值传递,打印结果应该是第一种情况,所以到底打印的结果是什么呢?
我们一起看一看控制台输出
Connected to the target VM, address: '127.0.0.1:59333', transport: 'socket'
修改过后,a = 0
主函数a的值 = 1
Disconnected from the target VM, address: '127.0.0.1:59333', transport: 'socket'
Process finished with exit code 0
123456
这就是第一种情况,很明显,在dosomthing函数中修改了a的值,但是主函数中的a并没有受到影响,所以肯定不会是引用传递,如果是引用传递,主函数的a应该会变成0,只有在参数传递的时候将主函数的中参数复制一份给dosomthing,才能在dosomthing中修改a不会对主函数造成影响,所以从基本数据类型来看,java的参数传递方式为:值传递。
这个时候你可能会有疑问了,这只是基本数据类型的传递方式,其他的参数类型呢?下面我们一起来看看引用类型和对象类型的传递方式。
follow me !!!!!
我们都知道java中的String类型不属于基本数据类型,它是一个引用类型,也可以说是一个对象,那么它的传递方式是什么呢?
我们还是先来看例子
package com.ymy.param;
/**
* @ProjectName: demo
* @Package: com.ymy.param
* @ClassName: StringTypeTest
* @Author: 流星007
* @Description: String类型传递
* csdn:https://blog.csdn.net/qq_33220089
* 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
* @Date: 2020/7/5 14:22
* @Version: 1.0
*/
public class StringTypeTest {
public static void main(String[] args) {
String a = "hello";
dosomthing(a);
System.out.println("主函数a的值 = "+a);
}
private static void dosomthing(String a) {
a = a+" bug";
System.out.println("修改过后,a = "+a);
}
}
123456789101112131415161718192021222324252627282930
打印结果
修改过后,a = hello bug
主函数a的值 = hello
Process finished with exit code 0
1234
我们发现主函数的a并没有受到dosomthing函数的影响,所以这并不是引用传递,这个时候你说是因为a = a+" bug";这行代码生成了新的对象,所以才会导致数据不一致,我们先来看看a的赋值情况吧
// class version 52.0 (52)
// access flags 0x21
public class com/ymy/param/StringTypeTest {
// compiled from: StringTypeTest.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 14 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/ymy/param/StringTypeTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
// parameter args
L0
LINENUMBER 17 L0
LDC "hello"
ASTORE 1
L1
LINENUMBER 18 L1
ALOAD 1
INVOKESTATIC com/ymy/param/StringTypeTest.dosomthing (Ljava/lang/String;)V
L2
LINENUMBER 19 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "\u4e3b\u51fd\u6570a\u7684\u503c = "
INVOKEVIRTUAL java/lang/StringBuilder.Append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 21 L3
RETURN
L4
LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
LOCALVARIABLE a Ljava/lang/String; L1 L4 1
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0xA
private static dosomthing(Ljava/lang/String;)V
// parameter a
L0
LINENUMBER 24 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " bug"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 0
L1
LINENUMBER 25 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "\u4fee\u6539\u8fc7\u540e\uff0ca = "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 27 L2
RETURN
L3
LOCALVARIABLE a Ljava/lang/String; L0 L3 0
MAXSTACK = 3
MAXLOCALS = 1
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
这是上面代码的字节码代码,我们可以清楚的看到a在赋值的时候都调用了StringBuilder的同String方法,现在我们来看看这个神奇的同String方法。
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
12345
这是StringBuilder中的toString方法,确实是new了一个新的对象,就算是a变成了一个新的对象,如果是引用传递,主函数的a就不会受影响吗?这点我会讲完对象类型传递之后在进行讲解,请继续往下看。
其实上面的两种其实很好区分,很多人都知道是值传递,很多人说java的传递方式是引用传递的原因就是出自这里:传递的参数为对象。
有些人看到对象传递的时候会改变主函数的值,就认为java的参数传递是引用传递,有些人又因为基本数据类型不会对主函数的值造成修改,所以他们的结论是:基本数据类型为值传递;对象类型为引用传递,想法很好,那我们现在一起来解开对象传递的神秘面纱,它到底是引用传递还是值传递呢?
go go go !!!!
还是老规矩,我们一起来看一个例子
package com.ymy.param.vo;
/**
* @ProjectName: demo
* @Package: com.ymy.param.vo
* @ClassName: LolVo
* @Author: 流星007
* @Description: lol英雄属性
* csdn:https://blog.csdn.net/qq_33220089
* 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
* @Date: 2020/7/5 15:11
* @Version: 1.0
*/
public class LolVo {
/**
* 姓名
*/
private String name;
/**
* 职业
*/
private String profession;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
@Override
public String toString() {
return "LolVo{" +
"name='" + name + ''' +
", profession='" + profession + ''' +
'}';
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
package com.ymy.param;
import com.ymy.param.vo.LolVo;
/**
* @ProjectName: demo
* @Package: com.ymy.param
* @ClassName: ObjectTypeTest
* @Author: 流星007
* @Description: 对象类型传递
* csdn:https://blog.csdn.net/qq_33220089
* 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
* @Date: 2020/7/5 15:16
* @Version: 1.0
*/
public class ObjectTypeTest {
public static void main(String[] args) {
LolVo lolVo = new LolVo();
lolVo.setName("无极剑圣");
lolVo.setProfession("刺客");
dosomthing(lolVo);
System.out.println("主函数 lolVo = "+lolVo);
}
private static void dosomthing(LolVo lolVo) {
lolVo.setProfession("战士");
System.out.println("dosomthing lolVo = "+lolVo);
}
}
1234567891011121314151617181920212223242526272829303132333435
结果如下:
dosomthing lolVo = LolVo{name='无极剑圣', profession='战士'}
主函数 lolVo = LolVo{name='无极剑圣', profession='战士'}
Process finished with exit code 0
1234
主函数中剑圣的职业是刺客,在dosomthing中将他修改成战士,我们发现主函数中剑圣的职业也被修改成战士了,显然这符合引用传递的条件,被调用方修改会影响到调用方也就是主函数,所以这个时候很多人就认为java的对象传递的方式为引用传递,如果你也是这么认为,那么你就要认真看一下我后面的分析。
我们先来一个否定它是引用传递的例子,请看好,不要眨眼
package com.ymy.param;
import com.ymy.param.vo.LolVo;
/**
* @ProjectName: demo
* @Package: com.ymy.param
* @ClassName: ObjectTypeTest
* @Author: 流星007
* @Description: 对象类型传递
* csdn:https://blog.csdn.net/qq_33220089
* 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
* @Date: 2020/7/5 15:16
* @Version: 1.0
*/
public class ObjectTypeTest {
public static void main(String[] args) {
LolVo lolVo = new LolVo();
lolVo.setName("无极剑圣");
lolVo.setProfession("刺客");
dosomthing(lolVo);
System.out.println("主函数 lolVo = "+lolVo);
}
private static void dosomthing(LolVo lolVo) {
lolVo = new LolVo();
lolVo.setProfession("战士");
System.out.println("dosomthing lolVo = "+lolVo);
}
}
123456789101112131415161718192021222324252627282930313233343536
做了小小的改动,仅仅只是在dosomthing方法中加了一行代码:lolVo = new LolVo();
我们再来看运行结果是什么呢?还会和上面一样吗?
dosomthing lolVo = LolVo{name='null', profession='战士'}
主函数 lolVo = LolVo{name='无极剑圣', profession='刺客'}
Process finished with exit code 0
1234
我们发现主函数中剑圣的属性变回了刺客,并没有受到dosomthing函数的影响,如果是引用传递的话,主函数中剑圣的职业应该是战士而不是刺客。这是为什么呢?为什么是应用传递主函数中剑圣的职业应该是战士呢?
下面我们一起来分析一波
我们假设对象的传递方式为引用传递
这是堆栈中的信息,当我们将对象lolVo传递给dosomthing的时候,是克隆了一个对象出来还是原来的那个对象呢?我们知道,不管传递的是不是它本身,值都是内存的地址引用,我们现在先假设主函数没有复制,是直接将lolVo传递给了dosomthing,那图形应该是什么样的呢?
如果是引用传递,格式是不是应该是这样呢?main主函数和dosomthing函数共用一个lolVo,这个时候我们在dosomthing函数中执行了一句:lolVo = new LolVo();那又会变成什么样呢?
在dosomthing方法中我们new了一个新的LolVo对象,并且将这个新的对象赋值给了lolVo,那是不是代表着main主函数核dosomthing函数中的lolVo应该是一样的呢,我们再来回顾一下上面的代码
private static void dosomthing(LolVo lolVo) {
lolVo = new LolVo();
lolVo.setProfession("战士");
System.out.println("dosomthing lolVo = "+lolVo);
}
123456
我们做了修改之后,主函数剑圣的职业并没有修改成战士,所以,说java是引用传递是说不通的,那我们再来看看它正确的流程应该是什么样的呢?
尽管dosomthing对参数的修改会影响调用方,但是它还是属于值传递,会影响调用方是因为java转递的时候拷贝的是对象的引用地址。
举个栗子:比如某公司开发了一套员工的内部管理系统,有一个管理员的账号,你把这个账号给了你的同事,他直接使用你这个账号,这就是引用传递,如果你是在用户管理中添加了一条管理员的用户,再将这个账号给你的同事,这就是值传递,还需要解释一下,为什么值传递会影响调用方,如果你给的账号,你同事改了用户名称,这对你是不是没有影响,但如果他手抖把员工全删了,你这边还能看到员工信息吗?就是这个道理,lolVo引用地址相当于管理员账号,系统内的功能相当于LolVo对象,你修改自己的账号对别人当然没有影响,但是你把系统搞没了,你觉得有影响吗?这就是为什么java的参数传递方式为值传递却能影响调用方。
如果还有人和你说java的参数传递是引用传递的话,请他来看一下我这篇博客,我把他劝退一下。