Java之路:物件的宣告和使用
一、物件的宣告
下面定義了由類產生物件的基本形式:
類名 物件名 = new 類名();
建立屬於某類的物件,需要通過下面兩個步驟實現:
⑴ 宣告指向“由類所建立的物件”的變數。
⑵ 利用new建立新的物件,並指派給先前所建立的變數。
class Person { 定義一個Person類
private String name;
private int age;
public void talk();
}
Person p = new Person();
// 也可以用下面這個方法
Person p1; // 先宣告Person類物件p1
p1 = new Person(); // 再用new關鍵字例項化Person的物件p1
物件只有在例項化之後才能被使用,而例項化物件的關鍵字就是new。
物件例項化過程如下:
從圖中可以看出,當語句執行到Person p1的時候,只是在“棧記憶體”中聲明瞭一個Person物件p1的引用,但是這個時候p1並沒有在“堆記憶體”中開闢空間。
物件的“引用”本質上就是一個物件在堆記憶體的地址,所不同的是,在Java中,使用者無法向C/C++那樣直接操作這個地址。
本質上,“new Person()”就是使用new關鍵字,來呼叫構造方法Person(),建立一個真實的物件,並把這個物件在“堆記憶體”中的佔據的記憶體首地址賦予p1,這時p1才能稱為一個例項化的物件。
二、物件的使用
如果要訪問物件裡的某個成員變數或方法,可以通過下面的語法來實現:
物件名稱.屬性名 //訪問屬性
物件名稱.方法名() //訪問方法
例如,想訪問Person類中的name和age屬性,可用如下方法來訪問:
p1.name; //訪問Person類中的name屬性
p1.age; //訪問Person類中的age屬性
因此若想將Person類的物件p中的屬性name賦值為“張三”,年齡賦值為25,則可採用下面的寫法:
p1.name = "張三" ;
p1.age = 25 ;
如果想呼叫Person中的talk()方法,可以採用下面的寫法:
p1.talk(); //呼叫Person類中的talk()方法
三、匿名物件
匿名物件是指就是沒有名字的物件。
實際上,根據前面的分析,對於一個物件例項化的操作來講,物件真正有用的部分是在堆記憶體裡面,而棧記憶體只是儲存了一個物件的引用名稱(嚴格來講是物件在堆記憶體的地址)。
所以,所謂的匿名物件就是指,只開闢了堆記憶體空間,而沒有棧記憶體指向的物件。
public class NoNameObject {
public void say() {
System.out.println("面朝大海,春暖花開!");
}
public static void main(String[]args) {
new NoNameObject().say(); // 這是匿名物件,沒有被其他物件所引用
}
}
【結果】
由於“new NoNameObject()”建立的是匿名物件,所以就用“NoNameObject()”整體來作為新構造匿名物件的引用,它訪問類中的方法,就如同普通物件一下,使用點操作符(.):
NoNameObject().say();
匿名物件有如下兩個特點:
⑴ 匿名物件是沒有被其他物件所引用,即沒有棧記憶體指向。
⑵ 由於匿名物件沒有棧記憶體指向,所以其只能使用一次,之後就變成無法找尋的垃圾物件,故此會被垃圾回收器收回。
注
當建立一個物件後,Java虛擬機器(JVM)就會給這個物件分配一個自身的引用——this。由於this是和物件本身相關聯的,所以this只能在類中的非靜態方法中使用。
靜態屬性及靜態方法屬於類,它們與具體的物件無關,所以靜態屬性及靜態方法是沒有this的。
public class StaticThis {
static int i;
public static void init() {
this.i = 10; // 錯誤,this只能在非靜態方法中使用
}
public static int f() {
init();
i++;
return i;
}
public void print() {
System.out.println(this.f()); // 雖然這樣訪問靜態方法不會報錯,但是不推薦使用
System.out.println(StaticThis.f()); // 因為靜態方法與靜態屬性是屬於類的,所以直接用類名訪問比較好
}
public static void main(String[] args) {
StaticThis st = new StaticThis();
st.print();
}
}
同一個類定義下的不同物件,每個物件都有自己的this,雖然都叫this,但指向的物件不同。
四、棧記憶體和堆記憶體的區別
1、棧
在Java中,棧(stack)是由編譯器自動分配和釋放的一塊記憶體區域,主要用於存放一些基本型別(如int、 float等)的變數、指令程式碼、常量及物件控制代碼(也就是物件的引用地址)。
棧記憶體的操作方式類似於資料結構中的棧(僅在表尾進行插入或刪除操作的線性表)。
棧的優勢在於,它的存取速度比較快,僅次於暫存器,棧中的資料還可以共享。
其缺點表現在,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。
2、堆
堆(heap)是一個程式執行動態分配的記憶體區域。
在Java中,構建物件時所需要的記憶體從堆中分配。 這些物件通過new指令“顯式”建立,放棄分配方式類似於資料結構中的連結串列。
堆記憶體在使用完畢後,是由垃圾回收(Garbage Collection,簡稱GC)器“隱式”回收的。 在這一點上,是和C/C++是有顯著不同的,在C/C++中,堆記憶體的分配和回收都是顯式的,均由使用者負責,如果使用者申請了堆記憶體,而在使用後忘記釋放,則會產生“記憶體溢位”問題——可用記憶體存在,而其他使用者卻無法使用。
堆的優勢是在於動態地分配記憶體大小,可以“按需分配”,其生存期也不必事先告訴編譯器,在使用完畢後,Java的垃圾收集器會自動收走這些不再使用的記憶體塊。
其缺點為,由於要在執行時才動態分配記憶體,相比於棧記憶體,它的存取速度較慢。