代碼寫的越急,程序跑得越慢。—— 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的參數傳遞是引用傳遞的話,請他來看一下我這篇博客,我把他勸退一下。