原帖:http://topic.csdn.net/t/20060605/13/4801113.html
(UnAgain)
最近看了一個文章,問“java到底是按值傳遞還是按引用傳遞?”。本來覺得很簡單,為了能說的準確一點,我還專門就這個問題看了看langspec3.0。一看收穫還真不小,就寫了這篇文章。
我還不敢確定自己的觀點對不對,所以貼在這裡,希望大家一起討論。
另外,貼在blog上了,在那裡的效果比這裡好。
http://blog.csdn.net/UnAgain/archive/2006/06/05/774039.aspx
1 資料類型
1.1 PrimitiveType(簡單類型)
1.2 ReferenceType(參考型別)
2. 變數
2.1 簡單類型變數
2.2 參考型別變數
3.賦值與傳遞
3.1 對象的賦值
3.2 傳遞
3.3 final變數能改變嗎?
3.4 封裝類的賦值與傳遞
1 資料類型
java的資料類型有兩類:
PrimitiveType(簡單類型)
ReferenceType(參考型別)
1.1 PrimitiveType(簡單類型)
(參考:langspec-3.0/typesValues.html#4.2)
PrimitiveType的分類如下所示:
PrimitiveType:
NumericType
boolean
NumericType:
IntegralType
FloatingPointType
IntegralType: one of
byte short int long char
FloatingPointType: one of
float double
PrimitiveType是java預定義的類型,並且使用保留字命名。比如int、long、float等。由此看來其封裝類不算PrimitiveType。
1.2 ReferenceType(參考型別)
(參考:langspec-3.0/typesValues.html#4.3)
ReferenceType有三種類型:類、介面、和數組。
2. 變數
(參考:langspec-3.0/typesValues.html#4.12)
A variable is a storage location and has an associated type, sometimes called its compile-time type, that is either a primitive type (§4.2) or a reference type (§4.3).
變數是關聯於特定類型的儲存單元,所關聯的類型有時叫做變數的編譯時間類型,即,既可以是簡單類型也可以是參考型別。
2.1 簡單類型變數
A variable of a primitive type always holds a value of that exact primitive type.
簡單類型的變數總是執持簡單類型的值。
2.2 參考型別變數
A variable of a class type T can hold a null reference or a reference to an instance of class T or of any class that is a subclass of T. A variable of an interface type can hold a null reference or a reference to any instance of any class that implements the interface.
類型是T的類的變數可以執持null引用,或者類T及其子類的執行個體引用。介面類型的變數可以執持null引用,或者任何實現該介面的類的執行個體引用。
註:與langspec2.0不同的是,3.0引入了泛型的概念,其中有Type Variable的概念,上面的T就是一個Type Variable。
3.賦值與傳遞
如上所述,可以得出下面結論:
1) 對於簡單類型變數的賦值是按值傳遞。就是說直接把數值存放到變數的儲存單元裡。
2) 對於參考型別的變數,賦值是把原對象的引用(可以理解為入口地址),存放在變數的儲存單元裡。
3.1 對象的賦值
簡單類型的賦值很容易理解,這裡僅討論對象的賦值。所有參考型別的執行個體就是我們常說的對象。
可以這樣說,除了null以外,任何變數的初始賦值都是分兩步:
1) 建立對象執行個體
2) 把對象執行個體的引用賦值給變數。
比如:
Object o1 = new Object();
3.2 傳遞
傳遞是通過變數之間的賦值實現的。在以前的回貼中我說過這樣一句話,單純從變數的角度看,變數之間的賦值是值傳遞。現在我解釋一下我的觀點。
先舉一個例子:
// java中所有的類的基類預設為Object,在此不贅述。
class Object1 {}
class Object2 {}
Object o1, o2;
o1 = new Object1();
o2 = o1;
o2 = new Object2();
這時候,o1的類型是什嗎?是Object1還是Object2?正確答案是Object1。
再舉一個例子:
class Word {
String word;
public Word(String word){
this.word = word;
}
public void print(){
System.out.println(word);
}
}
Word o1, o2;
o1 = new Word( "Every Day ");
o2 = o1;
o2 = new Word( "Every Night! ");
w1.print();
會出現什麼結果? "Every Day " 還是 "Every Night! "?仍然是 "Every Day "。
這裡面有一個很多人特別是初學者忽視了的觀點 ―― 變數可以引用對象,但變數不是對象。什麼是對象?對象初始化之後,會佔用一塊記憶體空間,嚴格意義上講,這段記憶體空間才是對象。對象建立於資料區段,而變數存在於程式碼片段;對象的入口地址是不可預知的,所以程式只能通過變數來訪問對象。
回到我們的問題上來,第一句
o1 = new Word( "Every Day ");
首先建立一個Word執行個體,即對象,然後把“引用”賦值給o1。
第二句
o2 = o1;
o1把對象的引用賦值給o2,注意賦的值是對象的引用而不是o1自身的引用。所以,在的三句
o2 = new Word( "Every Night! ");
就是又建立一個新對象,再把新對象的引用賦值給o2。
因為o1和 o2之間是值傳,所以,對o2的改變絲毫不會影響到o1。
也有一種情況好像是影響到了o1,我們繼續上面的例子,給Word增加一個方法
class Word {
String word;
public Word(String word){
this.word = word;
}
public void print(){
System.out.println(word);
}
public void setWord(String word){
this.word = word;
}
}
Word o1, o2;
o1 = new Word( "Every Day ");
o2 = o1;
o2.set Word( "Every Night! ");
o1.print();
這時的結果是 "Every Night! "。
那麼,這是改變了o1嗎?從嚴格意義上講,不是。因為o1隻是儲存對象的引用,執行之後,o1還是持有該對象的引用。所以,o1沒變,變的是o1所引用的對象。
3.3 final變數能改變嗎?
好了,我再出道題目:
final Word o3 = new Word( "Every Day! ");
o3.setWord( "Every Night! ");
能通過編譯嗎?對於final的定義大家都知道,o3是相當於一個常量,既然是常量,怎麼能再改變呢?
答案是肯定的,能。道理我想大家也明白,這裡不羅嗦了。
3.4 封裝類的賦值與傳遞
以前看過文章說,對於java基本類型及其封裝類採用值傳遞,對於對象採用引用傳遞。從langspec看,首先封裝類不是PrimitiveType,那就只能是ReferenceType,而ReferenceType的變數儲存的是引用。既然儲存的是引用,也就無從傳遞數值。那麼,這兩個觀點矛盾嗎?
首先,肯定是langspec正確。
其次,雖然前一觀點在原理上有錯誤,但卻不影響正常使用。
為什麼會出現這種情況?這是因為這些封裝類具有一個簡單類型的特徵,即,不可改變。以String為例,看一下API Specification,不會找到能夠改變String對象的方法。任何輸出上的改變都是重建新的String對象,而不是在原對象基礎上改變。改變的是變數的內容,即,不同對象的引用。
------------------------------------------------------------------------
(::一騎絕塵::) :
Java中總是使用傳值調用。
在Java中,方法可以改變對象參數的狀態,卻無法改變這個對象引用(Object reference)本
身.也就是當一個對象的執行個體被建立的時候,like this: Apple a = new Apple(); a 存的就是這個對象執行個體的地址。而這個地址,也就是a的值作為參數傳到某個函數中的時候,a本身是不會改變的。
電視機和遙控器可以很形象的描敘和解釋這個問題。
可以把遙控器看作是電視機的一個引用拷貝,只要電視機存在,也就是用遙控器對準一台電視機,按遙控器上面的各種按扭(function)可以對電視機產生各種影響,但是你換一個遙控器對電視機來說是不會產生影響的,電視機並不會因為遙控器換了而變成別的電視機。同時遙控器也可以不對準原來的電視機,轉去對準別的電視機,這對原來的電視機也是不會產生影響的。一台電視機可以有多個遙控器,並且只要某個遙控器對準了這台電視機,這個遙控器就可以通過它上面的按扭改變電視機的狀態。
下面是正面只有按值傳遞的程度代碼:
//Test.java
class Test {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//TestTransferParameter.java
public class TestTransferParameter{
public static void main(String[] args) {
Test a = new Test();
a.setName( "a ");
Test b = new Test();
b.setName( "b ");
System.out.println( "before swap: " + "a= " + a.getName() + "; b= " + b.getName());
swap(a,b);
System.out.println( "after swap: " + "a= " + a.getName() + "; b= " + b.getName());
}
private static void swap(Test a, Test b) {
Test temp;
temp = a;
a = b;
b = temp;
System.out.println( "swaping: " + "a= " + a.getName() + "; b= " + b.getName());
}
}
輸出結果:
before swap: a=a; b=b
swaping: a=b; b=a
after swap: a=a; b=b