JAVA中物件的賦值與引用
前言
最近在學習紅黑樹,當我嘗試不使用遞迴來實現時,發現自己的大腦陷入了混亂。
究其原因,是對JAVA中的基本型別和引用型別有所誤解。
特地重新搜尋+實踐一番,漲個姿勢。
一番折騰
先找個物件過年
class Node {
int value;
Node left;
Node right;
Node(int value) {
this.value = value;
}
}
物件賦值,引用還是複製值?
我出現的問題就在於弄混了下面的程式碼,沒有理解清楚傳值和傳址。
Node root;
//初始化
root = new Node(8);
root.left = new Node(6);
root.right = new Node(10);
//我弄混的地方
root = root.left;
root.left = new Node(6);
經過初始化後,如圖所示
然後我理解錯誤的地方出現了,就是這句
root = root.left;
我的理解出現了偏差,執行後,請看下圖
為啥呢?
發現,new出來的物件被存的都是地址,而地址指向實際的物件。
也就是說,物件是引用型別。
再看對已有的物件用“=”賦值
現在,可以確認了,JAVA中物件賦值是傳遞地址的!
真·引用型別。
之前,我一直以為是傳值。
如果將物件的地址想象成房號,變數root原本在463號。
root = root.left;
按我之前的理解,這行程式碼是告訴root把463號的物品都扔了。
再按照465號房間裡的物品,買一份一模一樣的放在463號。
實際呢
是告訴root,搬到465號去。
463號不再是它的房間了。
區別就在於
如果原本還住463號,只是複製的物品,那麼它跟464號還是鄰居,還可以互相串門。
但若搬到465號就不再與464號相鄰,沒辦法串門到464號去,只能剩下465號一個房間。
小結
對物件使用“=”賦值,賦的是地址而不是值。
這麼基礎的問題,赤果果暴露了我的基礎水平- -
還是某領導說得好啊,“實踐是檢驗真理的唯一標準”。
僅僅聽說是不夠的,還需要親身實踐。
如果是方法之間的引數呢?
上面我們說到物件之間賦值,都是地址。
那麼如果把物件當作引數傳進方法裡,也是傳物件的地址嗎?
答案是,是的。
不過此處有個坑,請看以下程式碼
public class TestObject {
public static void main(String[] args) {
TestObject o = new TestObject();
int baseInt = 1;
Integer objInt = 1;
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
o.changeInt(baseInt);
o.changeInteger(objInt);
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
}
public void changeInt(int baseInt) {
baseInt = 3;
}
public void changeInteger(Integer objInt) {
objInt = 3;
}
}
結果:
baseInt: 1
objInt: 1
baseInt: 1
objInt: 1
不科學啊。如果傳的是地址進去,為什麼objInt沒有改變呢!
跟說好的不一樣是不是?
來看看到底發生了什麼
可以看到objInt確實是引用型別的物件,其地址為@464。
再看看進入到方法時,地址是什麼。
還是@464,說明的確按照地址傳去的。
但是執行完賦值語句後
地址變成了@468。。
我的猜測
objInt = 3;
的操作是
objInt = new Integer(3);
由於new關鍵字導致objInt改變了引用,從@464變成了@468。
所以改的不是@464裡的值。
執行完該方法後,回到主方法裡,地址變回了@464,也就看到了結果沒有發生改變。
那麼只要不建立新物件,改變是會生效的。
請看如下程式碼:
public class TestObject {
public static void main(String[] args) {
TestObject o = new TestObject();
int baseInt = 1;
Integer objInt = 1;
MyInt myInt = new MyInt(1);
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
System.out.println("myInt: " + myInt.v);
o.changeInt(baseInt);
o.changeInteger(objInt);
o.changeMyInt(myInt);
System.out.println("改變後....");
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
System.out.println("myInt: " + myInt.v);
}
public void changeInt(int baseInt) {
baseInt = 3;
}
public void changeInteger(Integer objInt) {
objInt = 3;
}
public void changeMyInt(MyInt myInt) {
myInt.setV(3);
}
}
class MyInt {
int v;
MyInt(int v) {
this.v = v;
}
public void setV(int v) {
this.v = v;
}
}
結果:
baseInt: 1
objInt: 1
myInt: 1
改變後....
baseInt: 1
objInt: 1
myInt: 3
生效了。
說明傳進方法裡的引數,只要不用new建立新物件,引用的就是傳進來的物件。
總結
物件之間使用=號進行賦值時,賦的是地址的值,擁有該地址的都是指向該地址對應的物件。
即多對一的關係。
如果用了new關鍵字,一定會有新物件產生,有新物件也肯定有新地址。
至於Integer這些包裝類,我猜它們的=號就是new新物件,同時其沒有提供對外的方法更改物件裡的值,所以可以看作基本型別。
我算是明白了一丟丟,繼續寫紅黑去- -