本文首發(fā)WindCoder:java漫談-Java只有值傳遞
《Head First Java》中關(guān)于 Java 參數(shù)傳遞的說明:
Java 中所傳遞的所有東西都是值杂靶,但此值是變量所攜帶的值甜无。引用對(duì)象的變量所攜帶的是遠(yuǎn)程控制而不是對(duì)象本身,若你對(duì)方法傳入?yún)?shù)唉侄,實(shí)際上傳入的是遠(yuǎn)程控制的拷貝个曙。
《Java編程思想 第四版》中第二章第一節(jié)“用引用操作對(duì)象”中寫到:
盡管一切都看作對(duì)象憔足,但操作的標(biāo)識(shí)符實(shí)際上是一個(gè)對(duì)象的引用(reference)酪夷。
文中用遙控器(引用)操作電視(對(duì)象)為例形象的說明了該引用名詞的含義贰镣,同時(shí)在對(duì)定義的“引用”該名詞的注釋中提到:
有人認(rèn)為:“很明顯呜象,它是一個(gè)指針”。”但是這種說法是基于底層實(shí)現(xiàn)的某種假設(shè)恭陡。并且,Java中的引用上煤,在語(yǔ)法上更接近C++的引用而不是指針休玩。
還是有很多人不同意用“引用”這個(gè)術(shù)語(yǔ)。我曾讀到的一本書中這樣說:“Java所支持的‘按址傳遞’是完全錯(cuò)誤的”劫狠,因?yàn)镴ava對(duì)象標(biāo)識(shí)符(按那位作者所說)實(shí)際上是“對(duì)象引用”拴疤。并且他接著說任何事物都是“按值傳遞”的。也許有人會(huì)贊成這種精準(zhǔn)卻讓人費(fèi)解的解釋独泞,但我認(rèn)為我的這種方法可以簡(jiǎn)化概念上的理解并且不會(huì)傷害到任何事物呐矾。
對(duì)于基本類型(int等)沒用爭(zhēng)議,肯定是值傳遞懦砂。但String凫佛、基本類型的封裝類(Integer等)、自定義類(如User等)傳遞的是一個(gè)地址孕惜,這就容易讓人聯(lián)想到C++中的指針傳遞和引用傳遞愧薛。以一個(gè)User類為例:
public class User {
private String name;
private int age;
private Integer height;
public User(String name, int age, Integer height) {
this.name = name;
this.age = age;
this.height = height;
}
// ......
// 省略了無(wú)參構(gòu)造函數(shù)、所有set/get函數(shù)衫画。
// ......
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height+'\'' +
", address=" + super.toString() +
'}';
}
}
示例1:
public class MainDemo {
public static void main(String[] args) {
User a = new User("a",10,10);
User b = new User("b",11, 11);
PrintUtill.println("交換前:");
PrintUtill.println("a: "+a);
PrintUtill.println("b: "+b);
PrintUtill.printlnRule();
swap(a,b);
PrintUtill.println("交換最后:");
PrintUtill.println("a: "+a);
PrintUtill.println("b: "+b);
PrintUtill.printlnRule();
change(a);
PrintUtill.println("修改最后:");
PrintUtill.println("a: "+a);
}
public static void swap(Object sa, Object sb){
Object sc = sa;
sa = sb;
sb = sc;
PrintUtill.println("交換中:");
PrintUtill.println("sa: " + sa);
PrintUtill.println("sb: " + sb);
PrintUtill.printlnRule();
}
}
結(jié)果:
交換前:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}
--------windcoder.com----------
交換中:
sa: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}
sb: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
--------windcoder.com----------
交換最后:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}
--------windcoder.com----------
不管是斷點(diǎn)跟蹤還是最終打印出的結(jié)果毫炉,swap方法中確實(shí)做了交換(sa和sb的地址做了交換),但并未對(duì)方法外有任何影響(a和b的地址指向依舊是原來(lái)的未變)削罩。
每個(gè)方法的運(yùn)行都會(huì)在Java虛擬機(jī)棧中創(chuàng)建一個(gè)棧幀瞄勾,里面存放了局部變量表等內(nèi)容,方法的運(yùn)行就是一個(gè)棧幀進(jìn)棧出棧的過程弥激。
如方法swap:
public static void swap(Object sa, Object sb)
此時(shí)的sa,sb屬于形參进陡,就是形式參數(shù),用于定義方法的時(shí)候使用的參數(shù)微服,用來(lái)接收調(diào)用者傳遞的參數(shù)趾疚。形參只有在方法被調(diào)用的時(shí)候,虛擬機(jī)才會(huì)分配內(nèi)存單元,在方法調(diào)用結(jié)束之后便會(huì)釋放所分配的內(nèi)存單元糙麦。
相關(guān)知識(shí):
JVM-Java內(nèi)存區(qū)域
JVM基礎(chǔ)小結(jié)
換種說法辛孵,當(dāng)調(diào)用swap方法時(shí),sa類似C++中的引用赡磅,成為地址2503dbd3的一個(gè)別名魄缚,亦既上面關(guān)于《思想》中的引用說的更接近引用而不是指針,不同之處是一旦執(zhí)行sa = sb;
焚廊,改變的僅是是sa的指向冶匹,對(duì)原地址2503dbd3不會(huì)造成任何影響。一旦方法執(zhí)行完咆瘟,sa被分配到內(nèi)存單元便會(huì)被釋放徙硅。該實(shí)例執(zhí)行如圖:
示例2:
在示例1中追加代碼后如下:
public class MainDemo {
public static void main(String[] args) {
User a = new User("a",10,10);
User b = new User("b",11, 11);
// ......
// 省略示例1中的代碼
// ......
change(a);
PrintUtill.println("修改最后:");
PrintUtill.println("a: "+a);
}
public static void swap(Object sa, Object sb){
// ...省略
}
public static void change(User sa){
sa.setName("a2");
sa.setAge(11);
sa.setHeight(12);
PrintUtill.println("修改中:");
PrintUtill.println("sa: " + sa);
}
}
結(jié)果:
// ......
// 省略之前的
交換最后:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}
--------windcoder.com----------
修改中:
sa: User{name='a2', age=11, height=12', address=Others.base.common.User@2503dbd3}
修改最后:
a: User{name='a2', age=11, height=12', address=Others.base.common.User@2503dbd3}
此時(shí)函數(shù)內(nèi)發(fā)生了變化影響到函數(shù)外的數(shù)據(jù),change方法外的a和方法的參數(shù)sa指向的均是地址2503dbd3搞疗,指向未發(fā)生變化嗓蘑,但函數(shù)中的操作導(dǎo)致所指向的地址2503dbd3中的內(nèi)容發(fā)生了變化,
示例3
public class ListDemo {
public static void main(String[] args) {
List<String> list = null;
// List<String> list = new ArrayList<String>();
add(list);
list.add("3");
list.add("4");
PrintUtill.println("list.size:" + list.size());
}
public static void add(List<String> list){
if (list==null){
list = new ArrayList<String>();
}
list.add("1");
list.add("2");
}
}
該示例中l(wèi)ist最開始為null匿乃,意味著沒有地址桩皿,當(dāng)作為實(shí)參傳進(jìn)add方法中,add.list沒有地址幢炸,從而執(zhí)行了list = new ArrayList<String>();
泄隔,add.list被分配了新的地址,當(dāng)執(zhí)行完add方法宛徊,add.list就會(huì)被釋放佛嬉,但main.list仍舊為空,從而導(dǎo)致NullPointerException(空指針異常)闸天。
擴(kuò)展
C++中函數(shù)參數(shù)的幾種類型: