一、傳值和傳引用
C++里面有傳值和傳引用的說法,而java里面卻不一樣,公司讓我出一道考查相關(guān)的東西,于是出了下面這道題:
class Number {
int i;
}
public class Assignment {
public static void main (String [] args) {
Number n1 = new Number();
Number n2 = new Number();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i + ", n2.i:" + n2.i);//1: n1.i: 9, n2.i:47
n1 = n2;
System.out.println("2: n1.i: " + n1.i + ", n2.i:" + n2.i);//2: n1.i: 47, n2.i:47
n1.i = 27;
System.out.println("3: n1.i: " + n1.i + ", n2.i:" + n2.i);//3: n1.i: 27, n2.i:27
test(n1, n2);
System.out.println("4: n1.i: " + n1.i + ", n2.i:" + n2.i);//4: n1.i: 6, n2.i:6
StringBuffer sb1 = new StringBuffer ("A");
StringBuffer sb2 = new StringBuffer ("B");
test(sb1,sb2);
System.out.println(sb1 + "." + sb2);//AB.B
sb2 = sb1;
System.out.println(sb1 + "." + sb2);//AB.AB
String s1 = new String("A");
String s2 = new String("B");
test(s1, s2);
System.out.println("String: "+ s1 + "." + s2);//String: A.B
}
static void test (Number n1, Number n2)
{
n1.i = 6;
n2 = n1;
}
static void test (String s1, String s2)
{
s1 = s2; //賦值時,s1就重新指向了s2指向的內(nèi)容,但是實參的內(nèi)容沒有改變
s1 = "C";
}
static void test (StringBuffer sb1, StringBuffer sb2)
{
sb1.append ("B");//沒有產(chǎn)生新對象
sb2 = sb1;
}
}
在Java中,事實上底層工作原理不存在傳引用的概念,這也象《Practical Java》中所說的那樣,Java中只有傳值。這句話理解起來需要費一定的周折。
傳值和傳引用的問題一直是Java里爭論的話題。與C++不同的,Java里面沒有指針的概念,Java的設(shè)計者巧妙的對指針的操作進(jìn)行了管理。
下面舉個簡單的例子,說明什么是傳值,什么是傳引用。
//例1
void method1(){
int x=0;
this.change(x);
System.out.println(x);
}
void int change(int i){
i=7;
}
很顯然的,在mothod1中執(zhí)行了change(x)后,x的值并不會因為change方法中將輸入?yún)?shù)賦值為1而變成1,也就是說在執(zhí)行
change(x)后,x的值z依然是0。這是因為x傳遞給change(int i)的是值。這就是最簡單的傳值。
同樣的,進(jìn)行一點簡單的變化。
//例2
void method1(){
StringBuffer x=new StringBuffer("Hello");
this.change(x);
System.out.println(x);
}
void int change(StringBuffer i){
i.append(" world!");
}
看起來沒什么變化,但是這次mothed1中執(zhí)行了change (x)后,x的值不再是"Hello"了,而是變成了"Hello world!"。這
是因為x傳遞給change(i)的是x的引用。這是最經(jīng)典的傳引用。
似乎有些奇怪了,兩段程序沒有特別的不同,可是為什么一個傳的是值而另一個傳的是引用呢?
Java 提出的思想,在Java里面任何東西都是類。但是Java里面同時還有簡單數(shù)據(jù)類型:int,byte,char,boolean,與這些數(shù)據(jù)類型相對應(yīng)的類是Integer,Byte,Character,Boolean,這樣做依然不會破壞Java關(guān)于任何東西都是類的提法。這里提到數(shù)據(jù)類型和類似乎和我們要說的傳值和傳引用的問題無關(guān),但這是我們分辨?zhèn)髦岛蛡饕玫幕A(chǔ)。
我們分析一下上面的幾個例子:
先看例1,即使你不明白為什么,但是你應(yīng)該知道這樣做肯定不會改變x的值。為了方便說明,我們給例子都加上行號。
//例1
1 void method1(){
2 int x=0;
3 this.change(x);
4 }
5
6 void int change(int i){
7 i=7;
8}
讓我們從內(nèi)存的存儲方式看一下x和I之間到底是什么關(guān)系。
在執(zhí)行到第2行的時候,變量x指向一個存放著int 0的內(nèi)存地址。
變量x---->[存放值0]
執(zhí)行第3行調(diào)用change(x)方法的時候,內(nèi)存中是這樣的情形:x把自己值在內(nèi)存中復(fù)制一份,然后變量i指向這個被復(fù)制出來的0。
變量x---->[存放值0]
↓進(jìn)行了一次值復(fù)制
變量i---->[存放值0]
這時候再執(zhí)行到第7行的時候,變量i的被賦值為7,而這一步的操作已經(jīng)跟x沒有任何關(guān)系了。
變量x---->[存放值0]
變量i---->[存放值7]
說到這里應(yīng)該已經(jīng)理解為什么change(x)不能改變x的值了吧?因為這個例子是傳值的。
那么,試著分析一下為什么例三中的switchValue()方法不能完成變量值交換的工作?
再看例2。
//例2
1void method1(){
2 StringBuffer x=new StringBuffer("Hello");
3 this.change(x);
4}
5
6 void change(StringBuffer i){
7 i.append(" world!");
8}
例2似乎和例1從代碼上看不出什么差別,但是執(zhí)行結(jié)果卻是change(x)能改變x的值。依然才從內(nèi)存的存儲角度來看看例2的蹊蹺在哪里。
在執(zhí)行到第2行時候,同例1一樣,x指向一個存放"Hello"的內(nèi)存空間。
變量x---->[存放值"Hello"]
接下來執(zhí)行第三行change(x),注意,這里就與例1有了本質(zhì)的不同:調(diào)用change(x)時,變量i也指向了x指向的內(nèi)存空間,而不是指向x的一個拷貝。
變量x "
-->[存放值"Hello"]
變量i /
于是,第7行對i調(diào)用append方法,改變i指向的內(nèi)存空間的值,x的值也就隨之改變了。
變量x "
-->[追加為"Hello World!"]
變量i /
為什么x值能改變呢?因為這個例子是傳引用的。
對于參數(shù)傳遞,如果是簡單數(shù)據(jù)類型,那么它傳遞的是值拷貝,對于類的實例它傳遞的是類的引用。
需要注意的是,這條規(guī)則只適用于參數(shù)傳遞。為什么這
么說呢?我們看看這樣一個例子:
//例5
String str="abcdefghijk";
str.replaceAll("b","B");
這兩句執(zhí)行后,str的內(nèi)容依然是"abcdefghijk",但是我們明明是對str操作的,為什么是這樣的呢?因為str的值究竟會不會被改變完全取
決于replaceAll這個方法是怎么實現(xiàn)的。類似的,有這樣一個例子:
//例6
1 void method1() {
2 StringBuffer x = new StringBuffer("Hello");
3 change1(x);
4 System.out.println(x);
5 }
6
7 void method2() {
8 StringBuffer x = new StringBuffer("Hello");
9 change2(x);
10 System.out.println(x);
11 }
12
13 void change1(StringBuffer sb) {
14 sb.append(" world!");
15 }
16
17 void change2(StringBuffer sb) {
18 sb = new StringBuffer("hi");
19 sb.append(" world!");
20 }
調(diào)用method1(),屏幕打印結(jié)果為:"Hello world!"
調(diào)用method2(),我們認(rèn)為結(jié)果應(yīng)該是"hi world",因為sb傳進(jìn)來的是引用。可是實際執(zhí)行的結(jié)果是"Hello"!
難道change2()又變成傳值了?!其實change1()和change2()的確都是通過參數(shù)傳入引用,但是在方法內(nèi)部因為處理方法的不同而使結(jié)果大相徑庭。
所以,還有一條不成規(guī)則的規(guī)則:對于函數(shù)調(diào)用,最終效果是什么完全看函數(shù)內(nèi)部的實現(xiàn)。比較標(biāo)準(zhǔn)的做法是如果會改變引用的內(nèi)容,則使用void作為方法返回值,而不會改變引用內(nèi)容的則在返回值中返回新的值。
二、相等判斷
在使用vector中的contains方法時,重載了vector中對象的類DataDictionry的equals
方法,方法如下:
public boolean equals(Object obj) {
if(this.code == ((DataDictionry)obj).code)
return true;
else
return false;
}
相等的條件根據(jù)業(yè)務(wù),code字符串內(nèi)容屬性相等即可。即如果有一個DataDictionry對象和另一個DataDictionry的code相等即兩個對象為同一個對象。但是發(fā)現(xiàn)運行結(jié)果有問題,即使兩個的code的內(nèi)容相等
程序也判斷是false。但是改成如下:
public boolean equals(Object obj) {
if(this.code.equals(((DataDictionry)obj).code))
return true;
else
return false;
}
則結(jié)果是true。
因為對于字符串來說用這兩種方式判斷的結(jié)果是一樣的?什么情況下兩個字符串用equals判斷是相等的,但是用“==”判斷則不相等呢?
其實并不是字符串的比較“==”和equals方法的結(jié)果是一樣的,而是根據(jù)字符串的初始化相關(guān)。
1 public class TestString {
2
3 public static void main(String[] args) {
4 String a = "0010";
5 String b = "0010";
6
7 if(a == b)
8 System.out.println("equals");
9 else
10 System.out.println("not equals");
11 /* print equals */
12
13 if(a.equals(b))
14 System.out.println("equals");
15 else
16 System.out.println("not equals");
17 /* print equals */
18
19 String s1 = new String("abc");
20 String s2 = new String("abc");
21
22 if(s1 == s2)
23 System.out.println("equals");
24 else
25 System.out.println("not equals");
26
27 /* print not equals */
28
29 if(s1.equals(s2))
30 System.out.println("equals");
31 else
32 System.out.println("not equals");
33
34 /* print not equals */
35
36 }
37
38 }
所以如果不能肯定是new產(chǎn)生的還是直接賦值得到的字符串進(jìn)行比較,都使用equals方法是沒有問題的。
本質(zhì)上說,“ ==”還是比較的引用地址,equals比較具體的內(nèi)容(String等那些重載了equals方法的類)。