如下針對引用數據類型:
在內存中,類是靜態的概念,它存在於內存中的CodeSegment中。
當咱們使用new關鍵字生成對象時,JVM根據類的代碼,去堆內存中開闢一塊控件,存放該對象,該對象擁有一些屬性,擁有一些方法。可是同一個類的對象和對象之間並非沒有聯繫的,看下面的例子:數組
class Student{
static String schoolName;
String name;
int age;函數
void speak(String name){
System.out.println(name);
}佈局
void read(){
}
}this
class Test{
public statis void main(String[] args){
Student a = new Student();
Student b = new Student();
}
}spa
在上面的例子中,生成了兩個Student對象,兩個對象擁有各自的name和age屬性,而schoolName屬性因爲是static的,被全部的對象所共有,並且每一個對象都有權更改這個屬性。
而對於方法speak()和read()來說,它們被全部的對象所共有,但並非說哪一個對象擁有這些方法。方法僅僅是一種邏輯片斷而已,它不是實物,嚴格來說不能被擁有。
方法存在的意義就是對對象進行修改。(個人理解)
上述speak()方法,雖然兩個對象都擁有相同的方法,可是因爲其操做的對象不一樣,因此執行起來的效果也不一樣。再說一次,方法是看不見摸不着的,它僅僅是一種邏輯操做!只有看成用於具體的對象時,方法才具體化了!
方法在不執行的時候不佔用內存空間,只有在執行的時候纔會佔用內存空間。
就比如說一我的會翻跟斗,他翻跟斗的時候是須要空間的,可是他不翻跟斗的時候是不須要額外的空間的。可是無論他翻不翻跟斗,他始終是具備翻跟斗的技能的。
Java中的內存佈局(其餘面向對象的語言也是如此)code
Java中的內存空間分爲四種:
1. code segment(代碼段,是否是所謂的方法區?)
存儲class文件的內容,即咱們寫的代碼。
2. data segment(數據段)
存儲靜態變量
3. heap segment
堆空間,存儲new出來的對象
4. stack segment
棧空間,存儲引用類型的引用(注意,這裏存儲的不必定是對象所處的物理地址,可是必定可以根據這個內容在堆中找到對應的對象),局部變量和方法執行時的方法的代碼對象
以上面的speak(String name)方法的調用爲例來分析下內存:
調用該方法時,首先在棧控件開闢了一塊區域存放name引用。而後將傳入的那個對象的「地址」賦值給這個引用。因而出現了什麼狀況?兩個引用指向同一個對象。而咱們操做對象時是經過對象的引用來執行操做,因此當一個對象有一個以上的引用同時指向它時,就會出現一些比較混亂的事情了。內存
經過馬士兵在課上講的一個小例子來看看:get
public class Test {class
public static void main(String[] args) {
Test test = new Test();
int data = 10;
BirthDate b1 = new BirthDate(5,4,1993);
BirthDate b2 = new BirthDate(25,5,1992);
test.change(data);
test.change1(b1);
test.change2(b2);
System.out.println(data);
b1.display();
b2.display();
}
void change(int i){
i = 100;
}
void change1(BirthDate b){
b = new BirthDate(1,1,1);
}
void change2(BirthDate b){
b.setDay(100);
}
}
class BirthDate{
int day;
int month;
int year;
BirthDate(int day,int month,int year){
this.day = day;
this.month = month;
this.year = year;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public void display(){
System.out.println("day = "+day+"\nmonth = "+month+"\nyear = "+year);
}
}
輸出爲:
10
day = 5
month = 4
year = 1993
day = 100
month = 5
year = 1992
從輸出結果來看看發生了什麼:
1. 調用change(int i)
此時在棧空間中新建了一個i,並把data的值複製給i。這個時候在棧空間中有兩個int類型的數,兩者的值雖然都爲10,可是兩者毫無關係,棧空間中有兩個10.
在方法體中對i進行賦值,該操做是對i進行的,並不影響data,因此當方法結束時data仍是原來的data,連地址都沒變一下。同時在方法結束時i自動從棧空間中消失。
2. 調用change1(BirthDate b)
此時在棧空間中新建一個引用b,在調用該方法的時候,將傳進來的引用的值複製給b,即b和b1擁有相同的內容,指向同一個對象。在方法體中對b又進行賦值操做,首先在堆空間中new出一個新的對象,而後將b改成指向這個新的對象。該操做也未影響b1。方法結束後,引用b消失,剛纔new出來的新對象成了垃圾,等待GC的回收。
3. 調用change2(BirthDate b)
同上,先在對中新建一個引用,名爲b,而後將其指向b2所指向的對象。注意,此時調用了該對象的方法,修改了部分屬性,因此此操做改變了這個對象,而b2也指向這個對象,因此最後b2的輸出發生了變化。
從上面的三個方法能夠看出,當方法中的參數列表不爲空時:
- 若是參數是引用數據類型,該方法執行的過程首先是建立若干個引用,而後將這些引用的值和傳進來的引用的值一一對應複製。複製完以後,傳進來的參數(引用)的工做也就完成了。
- 若是參數是基本數據類型,那麼首先在棧中建立對應數量個變量,將這些變量的值和傳進來的參數一一對應複製。複製完以後也沒有外面什麼事了。
綜上:傳參時要注意,若是對傳進去的參數(其實是引用)進行了從新的賦值操做,那麼該方法應該有一個返回值,不然該方法是沒有意義的,如同上面的change1()。
方法通常有兩個做用:1. 對某變量進行改變。 2. 根據傳進來的參數返回另外一變量。
若是一個方法不想有返回值,只是想對某變量進行改變,不要將該對象做爲參數傳進去,而直接在方法中得到其訪問權限而後直接更改。方法的參數列表爲空。
若是一個方法要有返回值,最好先在方法內部new一個臨時變量,先將傳進來的參數複製一下,邏輯執行完後,把臨時變量return出去。
20170620更新:
其實以上問題涉及到的東西是值傳遞與引用傳遞。在C++中兩者都有,可是在Java中只有值傳遞。具體到實踐中分兩種狀況:
- 傳遞的是基本數據類型:
其實傳遞的是值的拷貝。在方法中對值進行操做,並不影響傳進去的那個值。如上面的change()方法,傳值進去時只是按照data的樣子從新建立了一個i,本質上data和i除了值相同之外,是兩個獨立的個體。
傳遞的是數組對象或者其餘對象:
實際上傳遞的是對象的引用,可是並非把引用傳過去,而是把引用複製過去。就像上面的change1()方法同樣,其本質是將傳參b1這個引用的值複製給引用b。b1和b除了值相同外,是兩個獨立的個體。可是因爲兩者值相同,因此指向了堆內存中的同一個對象,兩者均可以用來操做對象。
總結一下:
傳值,傳的都是棧中所儲存的東西的拷貝。若是傳進去的東西是基本數據類型,那麼就直接複製一份,對其操做不影響原來的數據。
若是傳進去的是一個引用,那麼其實也是複製一份,因此指向同一對象。當操做這個引用時,改變了這個引用所指向的對象,看起來會讓人以爲當時傳進去的是對象自己,否則怎麼在方法中對其修改會改變本來的對象呢?其實這是個假象。時刻記住,傳進函數的都是棧內存中的東西,堆內存的東西是不會被傳進去的。而函數內部能不能改變原來對象的值,就要看你是否是保持了原來傳進去的引用所指向的對象沒變。
PS:才疏學淺,若有錯誤請指出,謝謝!