前置知識地址:https://blog.csdn.net/wangfei8348/article/details/51383805
重點在後面的引用對比實驗(測試出內存地址,我很開心哈哈哈,客觀給個好評唄~~~)
java對象的聲明和初始化
java中,Object o 等價於C++中的 Obejct &o (改正:Object o等價於 Object* o),o本身是一個引用(其實是指針),在o未被初始化(對o進行賦值)前,o的引用爲空。也就是此時o爲null。進一步講,此時o僅是一個標識符,存在於java棧中,對象Obeject沒有被類加載器進行加載,也就不會有初始化的過程。
Object o = new Object()是一個聲明引用並對引用賦值,把對象進行初始化的過程。此時,java類加載器加載Obejct,堆中存在Object的Class對象。
初識Java引用
下面是java引用對象的案例,可見B = A;
後,B實質等價於A了。B修改對象的值A也會受到影響。此時A、B指向Java堆內同一個內存空間3594623912
。
class Goal {
public int x ;
public Goal(int x){
this.x = x;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
}
public static void testQuote(){
Goal A ;
Goal B ;
A = new Goal(1);
B = A;
B.setX(2);
System.out.println(A.getX());//2
System.out.println(B.getX());//2
A.setX(1);
System.out.println(A.getX());//1
System.out.println(B.getX());//1
System.out.println("Addess: " + Addresser.addressOf(A));//3594623912
System.out.println("Addess: " + Addresser.addressOf(B));//3594623912
}
~~~~~打個補丁,之前講java引用等同於C++引用是不嚴謹的。
java引用不同於C++引用
對比引用
java中的引用:“每種編程語言都有自己的數據處理方式。有些時候,程序員必須注意將要處理的數據是什麼類型。你是直接操縱元素,還是用某種基於特殊語法的間接表示(例如C/C++裏的指針)來操作對象。所有這些在 Java 裏都得到了簡化,一切都被視爲對象。因此,我們可採用一種統一的語法。儘管將一切都“看作”對象,但操縱的標識符實際是指向一個對象的“引用”(reference)。”這是java編程思想的原話。
c++中的引用:引用是已定義的變量的別名。
而觀察java引用於C++指針的工作流程
對於Java引用:先在棧中存對象的引用,再向堆中申請內存空間來存該類的對象,然後指向對象所在的內存。
對於c++的指針:先在棧中存指針,再向堆中申請內存空間存該指針所指向的元素,然後指向該地址。
發現java引用和C++指針非常相似。
案例分析
觀察下面的代碼1
public static void testQuote() {
Goal C = new Goal(3);
Goal D = new Goal(4);
swap(C, D);//交換沒有成功
System.out.println(C.getX());//3
System.out.println(D.getX());//4
}
public static void swap(Goal c, Goal d) {
Goal temp = c;//temp指向c指向的對象
c = d;//c指向d指向的對象
d = temp;//d執行temp指向的對象
// 但是c、d指向什麼東西與C、D沒有半毛錢關係。
}
上述代碼發生了什麼呢?如果c、d是C、D的引用(C++,別名),那麼交換應該成功的,但是沒成功啊?
利用工具查看C和D的指向的堆內對象地址,發現他們的引用地址並未發生改變。但是,形參c和d的指向的地址發生改變,指向了與原來不同的對象。(注意,初學C++的同學可能混淆指針的地址改變還是指針指向的地址(即指針的值)發生改變)
public static void testQuote() throws Exception {
Goal C = new Goal(3);
Goal D = new Goal(4);
System.out.println("Addess: " + Addresser.addressOf(C));//Addess: 3589856168
System.out.println("Addess: " + Addresser.addressOf(D));//Addess: 3589856184
swap(C, D);
System.out.println("Addess: " + Addresser.addressOf(C));//Addess: 3589856168
System.out.println("Addess: " + Addresser.addressOf(D));//Addess: 3589856184
System.out.println(C.getX());//3
System.out.println(D.getX());//4
}
//////////////////////////////////////////////////////////////////////////
public static void swap(Goal c, Goal d) throws Exception {
System.out.println("---------------------");
System.out.println("Addess: " + Addresser.addressOf(c));//Addess: 3589855840
System.out.println("Addess: " + Addresser.addressOf(d));//Addess: 3589855856
System.out.println(c.getX());//3
System.out.println(d.getX());//4
Goal temp = c;//temp指向c指向的對象
c = d;//c指向d指向的對象
d = temp;//d指向temp指向的對象
System.out.println("Addess: " + Addresser.addressOf(c));//Addess: 3589855856,c指向的地址發生改變,所以引用的對象也發生了改變。
System.out.println("Addess: " + Addresser.addressOf(d));//Addess: 3589855840,d指向的地址發生改變,所以引用的對象也發生了改變。
System.out.println(c.getX());//4
System.out.println(d.getX());//3
System.out.println("---------------------");
}
由結果可以看到,swap()
方法體內,c、d確實交換了對方的引用的對象。
代碼實質是交換了臨時引用變量c和d的指向的堆內的“地址”,c原來指向C即引用C後來引用D,d原來指向D即引用D後來引用C。
準確的講,Java只存在一種傳值方式便是值傳遞,c和d是形參並不能改變實參的值。形參是實參的複製,其引用實參指向的對象,但是其本身的修改不影響實參本身。實參沒有改變,而c、d的引用的地址值確實發生了交換,對應的引用的對象發生了改變證明了這種說法。
進一步探索Java“指針”
(這個部分統一將Java引用稱之爲Java指針)
其實,上面的交換的代碼等價於C++的這段代碼:
class Goal {
public:
int x;
Goal(int x) {
this->x = x;
}
int getX() {
return x;
}
void setX(int x) {
this->x = x;
}
};
void swap(Goal* c, Goal* d) {
cout<<c->getX()<<endl;//3
cout<<d->getX()<<endl;//4
Goal* temp = c; //temp指向c指向的對象
c = d;//c指向d指向的對象
d = temp;//d指向temp指向的對象
cout<<c->getX()<<endl;//4
cout<<d->getX()<<endl;//3
//但是c、d指向誰和C、D又沒什麼關係,所以C、D不變
}
int main() {
Goal *C = new Goal(3);
Goal *D = new Goal(4);
swap(C, D);
cout<<C->getX();//3
cout<<D->getX();//4
return 0;
}
由代碼結果知,上斷的結果與Java版本結果相同。以指針爲函數參數實現了與Java版本相同的效果。這給我們猜測Java引用是指針留下了依據。同時觀察對比C++的交換兩個數的實現:
void swap(int &a,int &b) //方法1改進 (引用傳遞) &a &b爲實參ab的地址,真交換
{
int temp=a;
a=b;
b=temp;
}
void swap(int *a,int *b) //方法1改進 (指針傳遞) 用指針,真交換
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
依據第一個函數,Java傳入的對象參數是引用這個就不成立了。至少Java傳入對象不同於C++的引用(別名)。而上文我們講過Java實質只存在一種傳參方式即值傳遞。衆所周知,C++中存在三種傳值方式:值傳遞、引用傳遞、地址傳遞(指針)。
在C++中,指針是一種變量,可以作爲參數傳遞,
指針最爲重要的作用是通過“解引用”實現對變量的間接使用。在Java中,函數傳入參數爲對象時,實參指向的對象可以被形參引用,形參通過這種方式也可以實現“間接使用”
從這個角度上講,任何對象如果被當作函數參數進行傳遞,那麼其就可以被看作一種“指針“
所以,java引用並非C++中的引用。
那麼又有一個問題,C++指針可以通過*
實現解引用,Java”指針“怎麼使用呢?
因爲Java中指針式對象,可以直接使用對象的方法,以此實現”解引用“
例如,如果想修改對象的成員的值,可以利用傳入對象(指針)的set方法修改,這種修改並不會隨着函數彈出Java棧而消失,是Java指針實現的”間接使用“的方式,如最開始的例子。
Q:那麼C++指針與Java指針還有什麼不同呢?
A:我的理解是,Java所有對象都可以是指針們就像所有對象都有”鎖“一樣,這一點不同於C++的顯示聲明指針對象。
最後是一個形參指針指向不同對象的例子,由結果知,該形參integer
原指向堆內位置爲3594611568
的對象,即var2,var1;
指向的對象,後來指向了新new出來的Integer(2);
對象。
代碼如下:
@Test
public void test3() throws Exception {
Integer var1=new Integer(1);
Integer var2=var1;
System.out.println("Addess: " + Addresser.addressOf(var1));//3594611568
System.out.println("Addess: " + Addresser.addressOf(var2));//3594611568
doSomething(var2);
System.out.println("Addess: " + Addresser.addressOf(var1));//3594611568
System.out.println("Addess: " + Addresser.addressOf(var2));//3594611568
System.out.println(var1.intValue());
System.out.println(var2.intValue());
System.out.println(var1==var2);
}
public static void doSomething(Integer integer) throws Exception {
System.out.println("Addess: " + Addresser.addressOf(integer));//3594611568
integer=new Integer(2);
System.out.println("Addess: " + Addresser.addressOf(integer));//3594634008
}
參考:
https://blog.csdn.net/mxd446814583/article/details/79599752 (獲取對象地址的方法)
https://blog.csdn.net/tianwei0822/article/details/78921823 (c++與java區別的理解(一)–引用)
https://www.cnblogs.com/coderising/p/5697986.html java中的值傳遞和引用傳遞問題
https://blog.csdn.net/cout_operator/article/details/82799372
https://blog.csdn.net/hackersuye/article/details/78875119