1.對象的創建和銷毀
1.1 對象的創建
這里只介紹創建對象與構造方法的關系
(1).每實例化一個對象就會自動調用一次構造方法,實質上這個過程就是創建對象的過程,准確的說,在Java語言中使用new操作符調用構造方法創建對象。
(2).當創建對象時,自動調用構造方法,也就是說在Java語言中,初始化與創建對象是捆綁在一起的。
(3).每個對象都是相互獨立的,在內存中占獨立的內存地址,並且每個對象都有自己的生命周期,當一個對象的生命周期結束時,對象變成垃圾,由Java虛擬機回收處理。
1.2 對象的引用
引用只是存放一個對象的內存地址,並非存放一個對象,嚴格的說引用和對象是不同的,但可以忽略。
1.3 對象的比較
在Java語言中對象的比較有兩種方式,分別為 “==” 和 “equals()”,但這兩種方式有本質的區別:
例:
public class Compare{ public static void main(){ String c1 = new String("acb"); String c2 = new String("acb"); String c3 = c1; System.ou.println(c2==c3); System.ou.println(c2.equals(c3)); } }
運行結果:false
true
分析:equals()方法是String類中的方法,它可以用於比較對象引用所指的內容是否相等,而“==”運算符比較的是兩個對象引用的地址是否相等,由c1與c2是兩個不同地對象的引用,兩者內存位置不同,而String中c3=c1,語句將c1的引用賦給c3,所以c1與c3這兩個對象引用是相等的
1.4 對象的銷毀
有以下兩種情況會被Java虛擬機銷毀:
(1).對象引用超過其作用范圍
(2).將對象賦值為空
注:垃圾回收器只能回收那些由new操作符創建的對象,否則,不能回收,所以在Java中提供了一個finalize()方法,這個方法是Object類的方法,它被聲明為protected,用戶可以在自己的類中定義這個方法,在垃圾回收時首先會調用這個方法,在下一次垃圾回收動作發生時,才能真正回收對象占用的內存。但垃圾回收方法finaliza()不保證一定會發生。
2.訪問對象屬性和方法
舉個例子:
1 public class Test { 2 int i = 47; 3 public void call(){ 4 for (i = 0; i < 3; i++){ 5 System.out.print(i+" "); 6 if(i == 2){ 7 System.out.println("\n"); 8 } 9 } 10 } 11 public Test(){ 12 } 13 public static void main(String[] args) { 14 Test t1 = new Test(); 15 Test t2 = new Test(); 16 t2.i = 60; 17 System.out.println(+t1.i); 18 t1.call(); 19 System.out.println(+t2.i); 20 t2.call(); 21 } 22 23 }
運行結果:
47 0 1 2 60 0 1 2
分析:
在上述代碼的主方法中首先實例化一個對象,然后使用“.”操作符調用成員變量和成員方法,但是從運行結果可以看到,雖然使用兩個對象調用同一個成員變量,結果卻不同,因為在打印這個成員變量值之前將該值重新賦值為60,單在賦值使用時的是第二個對象t2對象調用成員變量,所以在第一個對象t1調用成員變量打印該值時仍然是成員變量的初始值。由此可見,兩個對象的產生是相互獨立的,改變了t2的值,不會影響到t1的 i 的值。在內存中如下圖所示:
如果希望成員變量不被其中任何一個對象改變,可以使用static關鍵字,代碼如下:
1 public class Test { 2 static int i = 47; 3 public void call(){ 4 for (i = 0; i < 3; i++){ 5 System.out.print(i+" "); 6 if(i == 2){ 7 System.out.println("\n"); 8 } 9 } 10 } 11 public Test(){ 12 } 13 public static void main(String[] args) { 14 Test t1 = new Test(); 15 Test t2 = new Test(); 16 t2.i = 60; 17 System.out.println(+t1.i); 18 t1.call(); 19 System.out.println(+t2.i); 20 t2.call(); 21 } 22 23 }
運行結果:
60 0 1 2 3 0 1 2
分析:
從上述運行結果中可以看到,由於使用t2.i=60語句改變了靜態成員變量的值,使用對象t1調用成員變量的值也是60,這正是i值被定義為靜態成員變量的效果,即使使用兩個對象對同一個靜態成員變量進行操作,依然可以改變靜態成員變量的值,因為在內存條中兩個對象指向同一塊內存區域,t1.i++語句執行后,i的值變為3,當再次調用call()方法時又被重新賦值為0,做循環打印操作。
3.final變量,方法和類
(1)filnal修飾的方法不能被重寫;
(2)final修飾在屬性上,屬性的值不能被改變。
(3)final修飾的類不能被繼承。
3.1 finla變量關鍵字
finla變量關鍵字可用於變量聲明,一旦該變量被設定,就不可以再改變該變量的值,通常,有final定義的變量為常量。
final關鍵字定義的變量必須在聲明時對其進行賦值定義,final除了可以修飾基本數據類型的常量,還可以修飾對象引用,由於數組也可以被看成一個對象的引用,所以final可以修飾數組,一旦一個對象引用被修飾成final后,它只能恆定指向一個對象,無法將其改變指定另一個對象,一個既是static又是final的字段只占據一段不能改變的存儲空間,以下面的例子深入了解final:
例:
1 import static java.lang.System.*; 2 import java.util.*; 3 class Test { 4 int i = 0; 5 } 6 7 public class FinalData { 8 static Random rand = new Random(); 9 private final int VALUE_1 = 9; // 聲明一個final常量 10 private static final int VALUE_2 = 10; // 聲明一個final、static常量 11 private final Test test = new Test(); // 聲明一個final引用 12 private Test test2 = new Test(); // 聲明一個不是final的引用 13 private final int[] a = { 1, 2, 3, 4, 5, 6 }; // 聲明一個定義為final的數組 14 private final int i4 = rand.nextInt(20); //隨機數 15 private static final int i5 = rand.nextInt(20); 16 17 public String toString() { 18 return i4 + " " + i5 + " "; 19 } 20 21 public static void main(String[] args) { 22 FinalData data = new FinalData(); 23 // data.test=new Test(); 24 //可以對指定為final的引用中的成員變量賦值 25 //但不能將定義為final的引用指向其他引用 26 // data.value2++; 27 //不能改變定義為final的常量值 28 data.test2 = new Test(); // 可以將沒有定義為final的引用指向其他引用 29 for (int i = 0; i < data.a.length; i++) { 30 // a[i]=9; 31 // //不能對定義為final的數組賦值 32 } 33 out.println(data); 34 out.println("data2"); 35 out.println(new FinalData()); 36 // out.println(data); 37 } 38 }
運行結果
8 3
data2
6 3
分析:
在本實例子中,被定義成final的常量定義時需要使用大寫字母命名,並且中間使用下划線進行連接,這是Java中的編碼規則,同時,定義為final的數據無論是常量,對象,還是數組,在主函數中都不可以被修改。一個被定義為final的對象引用只能指向唯一一個對象,不可以將它再指向其他對象,但是一個對象本身的值卻是可以改變的,那么為了使一個常量真正做到不可以更改,可以將常量聲明為staticfinal。為了驗證這個理論,看以下實例子:
1 import static java.lang.System.*; 2 3 import java.util.*; 4 5 public class FinalStaticData { 6 private static Random rand = new Random(); // 實例化一個Random類對象 7 // 隨機產生0~10之間的隨機數賦予定義為final的a1 8 private final int a1 = rand.nextInt(10); 9 // 隨機產生0~10之間的隨機數賦予定義為static final的a2 10 private static final int a2 = rand.nextInt(10); 11 12 public static void main(String[] args) { 13 FinalStaticData fdata = new FinalStaticData(); // 實例化一個對象 14 // 調用定義為final的a1 15 out.println("重新實例化對象調用a1的值:" + fdata.a1); 16 // 調用定義為static final的a2 17 out.println("重新實例化對象調用a1的值:" + fdata.a2); 18 // 實例化另外一個對象 19 FinalStaticData fdata2 = new FinalStaticData(); 20 out.println("重新實例化對象調用a1的值:" + fdata2.a1); 21 out.println("重新實例化對象調用a2的值:" + fdata2.a2); 22 } 23 }
運行結果:
重新實例化對象調用a1的值:4 重新實例化對象調用a1的值:1 重新實例化對象調用a1的值:5 重新實例化對象調用a2的值:1
從實例的結果可以看出,定義為final的常量是恆定不變的,將隨機數賦值定義為final的常量,可以做到每次運行程序時改變a1的值,但a1與a2不同,由於他被聲明為static final的形式,所以在內存中為a2開辟了一個恆定不變的區域,當再次實例化一個finalstaticdata對象時,仍然指向a2這塊內存區域,所以a2的值保持不變,a2是在裝載時被初始化,而不是每次創建新對象時度被初始化,而a1會在重新實例化對象時被更改。
3.2 final方法
首先說明一點,f定義為inal的的方法不能被重寫。
例:
1 class Parents { 2 private final void doit() { 3 System.out.println("父類.doit()"); 4 } 5 6 final void doit2() { 7 System.out.println("父類.doit2()"); 8 } 9 10 public void doit3() { 11 System.out.println("父類.doit3()"); 12 } 13 } 14 15 class Sub extends Parents { 16 public final void doit() { // 在子類中定義一個doit()方法 17 System.out.println("子類.doit()"); 18 } 19 // final void doit2(){ //final方法不能覆蓋 20 // System.out.println("子類.doit2()"); 21 // } 22 public void doit3() { 23 System.out.println("子類.doit3()"); 24 } 25 } 26 27 public class FinalMethod { 28 public static void main(String[] args) { 29 Sub s = new Sub(); // 實例化 30 s.doit(); // 調用doit()方法 31 Parents p = s; // 執行向上轉型操作 32 // p.doit(); //不能調用private方法 33 p.doit2(); 34 p.doit3(); 35 } 36 }
運行結果
子類.doit()
父類.doit2()
子類.doit3()
分析:
從上例子中可以看出,final方法不能被覆蓋。例如doit2()方法不能再子類中被重寫,但是在父類中定義了一個private final的doit()方法,同時在子類中也定義了一個doit()方法,從表面上看,子類中的doit()方法覆蓋了父類的doit()方法,但必須滿足一個對象向上轉型為它的基本類型並調用相同方法這樣一個條件,在例子中,對象p不能調用doit()方法,可見,子類中的doit()方法並不是正常覆蓋,而是生成一個新的方法。
3.3 final類
在這只說明一點:如果將某個類設置為final類,則類中的所有方法都被隱式設置為final形式,但是final類中的成員變量可以被定義為final或非final形式,並且,其值可以被改變。
4.靜態變量,常量和方法
(1)為什么要使用靜態變量,常量和方法
通常,在處理問題,會遇到多個不同的類要使用同一變量,常量或方法,然而,同一個常量在不同i的類中創建時系統都會為之分配內存,造成內存浪費,如果能將這些不同類中的變量共享到一個內存中,那就大大減少了內存的使用,而靜態變量(關鍵字 static)就是解決這個問題的。如下圖所示:
(2).被聲明的static的變量,常量和方法被稱為靜態成員,靜態成員屬於類所有,區別於個別對象,可以在本類或其他類使用類名“.”運算符調用靜態成員。如下代碼:
1 public class AnyThing { 2 static double PI = 3.1415; //在類中定義靜態常量 3 static int id; //在類中定義靜態變量 4 public static void method1(){ //在類中定義靜態方法 5 6 } 7 public void method2(){ 8 System.out.println(AnyThing.PI); //調用靜態常量 9 System.out.println(AnyThing.id); //調用靜態變量 10 AnyThing.method1(); //調用靜態方法 11 } 12 }
注意:
(1) 雖然靜態成員可以使用“對象.靜態成員”的形式進行調用,但通常不這么使用,這樣容易混淆靜態成員和非靜態成員。
(2) 在靜態方法中不可以使用this關鍵字。
(3) 在靜態方法中不可以直接調用非靜態方法。
(4) 在Java中規定不能將方法中的局部變量聲明為static的。
(5) 如果在執行類時,希望先執行類的初始化動作,可以使用static定義一個靜態區域,例如
1 public class example{ 2 static{ 3 ... 4 } 5 }
當這段代碼被執行時,首先執行static塊中的程序,並且只會執行一次,即在類加載的時候就被執行,之后執行main()方法。並且只是自上而下的順序執行。
(3)實例語句塊在構造方法調用之前調用,調用一次構造函數,就調用一次實例語句塊,執行順序自上而下。
(4)在java中,靜態方法和普通方法的區別:
a、在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的方式。而實例方法只有后面這種方式。也就是說,調用靜態方法可以無需創建對象。
b、靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變量和靜態方法),而不允許訪問實例成員變量和實例方法;實例方法則無此限制。
c、程序的static塊只能調用靜態方法,不能調用實例的方法。
如果要追溯其區別的根源,可以這樣理解:在編譯期間靜態方法的行為就已經確定,而實例方法只有在運行期間當實例確定之后才能確定。
5.this關鍵字
在下面代碼中。成員變量與setName()方法中的形式參數的名稱相同,都為name,那么該如何在類中區分使用的是哪一個比阿娘呢?在Java語言中規定使用this關鍵字來代表本類對象的引用,this關鍵字被隱士的用於引用對象的成員變量和方法,如在上述代碼中,this.name指的name是成員變量,第二個name是形參。
private void setName(String name){ this.name = name; }
那么,在java中this和對象都可以調用成員變量或成員方法,二者之間有什么區別呢?
區別:this引用的就是本類的一個對象,在局部變量或方法參數覆蓋了成員變量時,就要添加this關鍵字說明引用的是類成員還是局部變量或方法參數;如果this關鍵字直接寫成name = name;成員變量的值並沒有改變,因為參數name在方法的作用域中覆蓋了成員了變量name。
this關鍵字還可以調用類中的構造方法,如下代碼
1 public class AnyThing { 2 public AnyThing(){ 3 this(""); //使用this調用有參構造方法 4 System.out.println("無參構造方法"); 5 } 6 public AnyThing(String name){ 7 System.out.println("有參構造方法"); 8 } 9 10 }
在上例定義了兩個構造方法,在無參構造方法中使用this關鍵字調用有參的構造方法,但使用這種方法需要注意的是只可以在無參構造方法中的第一句使用。