可能很多人都知道java中參數(shù)有形參和實(shí)參之分,卻不知道區(qū)別到底是什么;知道Java中內(nèi)存分為棧、堆、方法區(qū)等5片內(nèi)存,不知道每片內(nèi)存中保存的都是什么;關(guān)于參數(shù)的傳遞到底是值傳遞還是引用傳遞傻傻分不清楚。本文將為你逐一揭秘!
形參:就是定義方法時(shí),該方法攜帶的參數(shù)。比如定義如下方法:
public static void test(String name){
System.out.println(name);
}
test方法中的參數(shù)name就是形參,只有在test方法在被調(diào)用這個(gè)name的生命周期才開始,才會分配內(nèi)存空間,當(dāng)test方法調(diào)用完后,這個(gè)name也就不復(fù)存在。
實(shí)參:方法在被調(diào)用時(shí)實(shí)際傳入的參數(shù)值,實(shí)參在方法調(diào)用前就已經(jīng)被初始化。例子:
public static void main(String[] args){
String name = "劉亦菲";
test(name);
}
這個(gè)String name = "劉亦菲"中這個(gè)name,在test方法被調(diào)用之前就就已被創(chuàng)建并且初始化,在調(diào)用test方法時(shí),它就被當(dāng)作實(shí)際參數(shù)傳入,這就是實(shí)參。
Java中內(nèi)存分為5片,分別是棧、堆、方法區(qū)、程序計(jì)數(shù)器、本地方法棧。
1、棧:又稱虛擬機(jī)棧。特點(diǎn)是先進(jìn)后出。棧的線程是私有的,也就是線程之間的棧是隔離的。棧中有若干棧幀,每個(gè)棧幀對應(yīng)一個(gè)方法。也就是說,當(dāng)程序開始執(zhí)行一個(gè)方法時(shí),就會在棧中創(chuàng)建一個(gè)棧幀入棧,方法結(jié)束后,該棧幀出棧??聪旅娴膱D解:
局部變量表:存儲方法中的局部變量。當(dāng)局部變量是基本類型時(shí),存儲的是變量的值;當(dāng)變量是引用類型時(shí),存儲的是地址值。
運(yùn)行時(shí)常量池的引用:存儲程序執(zhí)行時(shí)可能會用到的常量的引用。
方法返回地址:存儲方法執(zhí)行完成后的返回地址。
2、堆:堆內(nèi)存用來存儲對象和數(shù)組。數(shù)組以及所有new出來的對象都存儲在堆內(nèi)存中。在JVM中只有一個(gè)堆,所以堆是被所有線程共享的。
3、方法區(qū):方法區(qū)也是所有線程共享的區(qū)域,主要存儲靜態(tài)變量、常量池等。
1、基本類型的存儲:
基本類型的局部變量:變量以及數(shù)值都是存儲在棧內(nèi)存中。比如在某個(gè)方法中定義有如下局部變量:
int age = 6;
int grade = 6;
int weight = 50;
先創(chuàng)建一個(gè)age變量,存儲在棧幀中的局部變量表,然后查找棧中是否有字面量值為6的內(nèi)容,如果有,直接把a(bǔ)ge指向這個(gè)地址,沒有開辟內(nèi)存空間來存儲"6"這個(gè)內(nèi)容,同時(shí)讓age指向它。當(dāng)創(chuàng)建grade變量時(shí),因?yàn)橐呀?jīng)有字面量為"6"的內(nèi)容了,所以直接拿過來用。所以棧中的數(shù)據(jù)在當(dāng)前線程下是共享的。上面的代碼在內(nèi)存中的圖解如下:
如果給age重新賦值:
age = 10;
難么就會在棧中查找是否有字面量為"10"的內(nèi)容,有就直接拿來用,沒有就開辟內(nèi)存空間存儲"10",然后age指向這個(gè)10。所以基本類型的變量,變量值本身是不會改變的,重新賦值后,只是指向了新的引用而已。
基本類型的成員變量:基本類型的成員變量的變量名和值都是存儲在堆內(nèi)存中的,其生命周期和對象是一致的??聪旅娴拇a:
public class User{
private int age;
private String name;
private int grade;
......
}
調(diào)用:
User user = new User();
在內(nèi)存中的存儲圖解:
基本類型的靜態(tài)變量:基本類型的靜態(tài)變量存儲于方法區(qū)的常量池中,隨著類的加載而加載。
2、引用類型的存儲:通過上圖可以發(fā)現(xiàn),執(zhí)行
User user = new User();
時(shí)分兩個(gè)過程:
User user;// 定義變量
user = new User();// 賦值
定義變量時(shí),會在棧中開辟內(nèi)存空間存放user變量;賦值時(shí)會在堆內(nèi)存中開辟內(nèi)存空間存儲User實(shí)例,這個(gè)實(shí)例會有一個(gè)地址值,同時(shí)把這地址值賦給棧中的user變量。所以引用類型的變量名存儲在棧中,變量值存儲的是堆中相對應(yīng)的地址值,并不是存儲的實(shí)際內(nèi)容。
關(guān)于參數(shù)的傳遞,可能有點(diǎn)難理解,到底是值傳遞還是引用傳遞?下面一起來學(xué)習(xí)一下:
值傳遞:方法調(diào)用時(shí),實(shí)際參數(shù)把它的值的副本傳遞給對應(yīng)的形式參數(shù),此時(shí)形參接收到的其實(shí)只是實(shí)參值的一個(gè)拷貝,所以在方法內(nèi)對形參做任何操作都不會影響實(shí)參??聪旅嬉欢未a:
public class Test {
public static void test(int age,String name){
System.out.println("傳入的name:"+name);
System.out.println("傳入的age:"+age);
age = 66;
name = "張馨予";
System.out.println("方法內(nèi)重新賦值的name:"+name);
System.out.println("方法內(nèi)重新賦值的age:"+age);
}
public static void main(String[] args){
String name = "劉亦菲";
int age = 44;
test(age,name);//調(diào)用方法
System.out.println("方法執(zhí)行后的name:"+name);
System.out.println("方法執(zhí)行后的age:"+age);
}
}
執(zhí)行結(jié)果如下:
從結(jié)果可以發(fā)現(xiàn),name和age在方法調(diào)用后并沒有改變,所以傳入方法的只是實(shí)參的拷貝。
引用傳遞:當(dāng)參數(shù)是對象的時(shí)候,其實(shí)傳遞的對象的地址值,所以實(shí)參的地址值傳給形參后,在方法內(nèi)對形參進(jìn)行操作會直接影響真實(shí)內(nèi)容??聪旅娴拇a:定義對象:
@Data
public class User {
private String name;
private int age;
}
測試:
public class Test {
public static void userTest(User user){
System.out.println("傳入的user:"+user);
user.setName("張馨予");
user.setAge(20);
System.out.println("方法內(nèi)重新賦值的user:"+user);
}
public static void main(String[] args){
User user = new User();
user.setName("劉亦菲");
user.setAge(18);
userTest(user);//調(diào)用方法
System.out.println("方法執(zhí)行后的user:"+user);
}
}
結(jié)果如下:
可以看到在方法內(nèi)對user重新賦值,直接影響這個(gè)對象,所以方法執(zhí)行完畢后輸出的是修改后的user。
對上面的測試方法稍作修改:
public class Test {
public static void userTest(User user){
System.out.println("傳入的user:"+user);
user = new User();//新增這行代碼
user.setName("張馨予");
user.setAge(20);
System.out.println("方法內(nèi)重新賦值的user:"+user);
}
public static void main(String[] args){
User user = new User();
user.setName("劉亦菲");
user.setAge(18);
userTest(user);//調(diào)用方法
System.out.println("方法執(zhí)行后的user:"+user);
}
}
執(zhí)行結(jié)果如下:
結(jié)果卻是,方法執(zhí)行后的user竟然沒改變。
分析一下這兩次的執(zhí)行過程:第一次:
第一次執(zhí)行過程如上圖,main方法進(jìn)棧后,在堆中new了一個(gè)user對象x0001,然后調(diào)用userTest方法,userTest方法進(jìn)棧,并且把user對象的地址值x0001傳入userTest方法,所以在userTest方法中對user進(jìn)行操作直接影響地址值為x0001的對象。所以就出現(xiàn)了第一次運(yùn)行結(jié)果。
第二次:
user = new User();
到了這里,又在堆中new了一個(gè)user對象x0002,然后讓棧中user變量指向新的user對象的地址值x0002。所以接下來在方法中對user的操作都是對地址值為x0002的對象的操作,自然不會影響到地址值為x0001的對象。所以就出現(xiàn)了第二次的運(yùn)行結(jié)果。
小結(jié):由上面的案例可以得出結(jié)論,基本類型傳的是值的副本,引用類型傳的是地址值,所以不論傳的是引用類型還是基本類型,其實(shí)都是值傳遞。