Java 程式設計思想第五章
第五章初始化與清理
初始化與清理是設計安全的兩個問題,C語言中如果使用者不知如何初始化庫的構建,另外資源的佔用,忘記了釋放導致記憶體用盡也是一個問題。c++中引入了構造器的概念,這是一個在建立物件時被自動呼叫的方法。Java 進行採用並提供了垃圾回收器。 5.1用構造器確保初始化 通過提供構造器,確保每個物件都會得到初始化。建立物件時,會為物件分配儲存空間並且呼叫相應的構造器,確保了在操作物件之前,已經恰當地進行了初始化。構造器是一個特殊的方法,特殊在沒有返回型別(也沒有void void 也是一種返回型別),並且命名方式很不同,他與類名完全一樣。 前面的 print()和 print(o) 都不接受返回型別為 void 的方法,現在我想試一下沒有返回型別的,發現這樣的提示:找不到方法 XXX。 感覺自己撿了西瓜丟了芝麻,前面的東西都忘得差不多了,像是關鍵字和識別符號這些。 構造器的作用:減少錯誤,增加可讀性。Java 中將初始化和建立結合在了一起。就像是建立一個變數,有時候會忘記了將自己的變數初始化,但在這裡不可能發生。 new 表示式返回了對新建物件的引用,但是這是一個引用型別(諸如 print 之類接受的方法引數都是有返回型別,void 除外。。print 還可接受的是如變數引用和物件引用,後者打印出來是引用指向的資料在記憶體中的地址)
5.2 方法過載
這句話有點意思:將人類語言中存在細微差別的概念“對映”道程式設計語言中時,問題隨之產生。生活中相同的詞可以表達多種不同的含義。大多人類的語言具有強大的“冗餘性”,所以不需要對每個概念使用不同的詞彙。(放在計算機中就是不需要對那種“細微差別”的操作使用不同的命名來區分) 但是我們如果放在計算機裡面就要加以區分了,同樣是清洗東西,有些時候我是清理房屋,有些時候我是清理衣物,計算機需要識別我們執行的具體方案。(用某種東西加以區分) Java 和 c++中構造器是方法過載的一個原因,為了以不同的形式建立物件,方法過載使得形式引數不同的構造器同時存在。程式要求為每一個函式設定一個獨一無二的識別符號。
5.2.1 區分過載方法 形式引數不同指的是方法的引數在型別,多少以及順序上的不同(不要順序,很不好閱讀)。
5.2.2 設計基本型別的過載 這個程式碼我一開始沒有明白。 char byte 和 short 比較難以理解
1、 基本型別:int 二進位制位數:32 包裝類:java.lang.Integer 最小值:Integer.MIN_VALUE= -2147483648 (-2的31次方) 最大值:Integer.MAX_VALUE= 2147483647 (2的31次方-1) 2、 基本型別:short 二進位制位數:16 包裝類:java.lang.Short 最小值:Short.MIN_VALUE=-32768 (-2的15此方) 最大值:Short.MAX_VALUE=32767 (2的15次方-1) 3、 基本型別:long 二進位制位數:64 包裝類:java.lang.Long 最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方) 最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1) 4、 基本型別:float 二進位制位數:32 包裝類:java.lang.Float 最小值:Float.MIN_VALUE=1.4E-45 (2的-149次方) 最大值:Float.MAX_VALUE=3.4028235E38 (2的128次方-1) 5、 基本型別:double 二進位制位數:64 包裝類:java.lang.Double 最小值:Double.MIN_VALUE=4.9E-324 (2的-1074次方) 最大值:Double.MAX_VALUE=1.7976931348623157E308 (2的1024次方-1)
還有的是 byte 和 char 的取值範圍,前者是-128 到 127 後者是0 到 2^16-1(65535)
特點總結 1.常數值總是被當做 int 值處理 2.傳入的資料型別小於方法中宣告中的形式引數型別,會進行提升 3.char 型別比較特殊,如果沒有能恰好接收 char 型別的引數,會把 char 直接提升為 int
5.2.3以返回值區分過載方法
5.3 預設構造器 是一個無參構造器,編譯器自動建立的。但是如果你自己定義了一個構造器,編譯器就不會幫你建立了。
5.4 this 關鍵字 這裡就像是 static 一樣我一直都不是很明白。面向物件的語法----傳送訊息給物件,編譯器案子做了一份工作,把所操作物件的引用作為第一個引數傳給了 Peel()方法 。 this 關鍵字只能在方法內部使用,表示對呼叫方法的那個物件的引用。。。如果方法內部呼叫同一個類的另一個方法,不用 this 直接呼叫即可(因為編譯器自動添加了)。記住這個 this 就是呼叫這個方法的引用,所以可以用 this 呼叫其他方法
5.4.1 在構造器中呼叫構造器
可以使用 this 來呼叫不同的構造器(他們之間僅僅是引數不同),this 還有一種用法就是在形參與資料成員的名稱相同時,可以利用 this.x=x用 this.x來表示資料成員。除了構造器之外,任何方法都不能呼叫構造器。。。。
5.4.2 static 的含義 static 就是沒有 this 的方法???非靜態方法呼叫靜態方法,不同類中,可以不用建立物件,使用類本身來呼叫 static 方法。Java 中禁止全域性方法,但是類中有 static 方法就可以訪問其他static 方法和 static 域。 不是很明白這裡說的 this 和 static 面向物件的含義,前者是想物件傳送訊息後者是擁有全域性函式的含義。
5.5 清理:終結處理和垃圾回收
課堂上老師講解 Java 的時候,沒有說到這一部分,平時也沒有訓練過,所以這裡很不明瞭。 Java 中有一個 finalize()方法:一旦垃圾回收器準備好釋放物件佔用的記憶體,首先呼叫方法,並且在下次垃圾回收動作發生時,才會真正回收物件佔用的記憶體。(潛在的程式設計陷阱,c++程式設計師可能吧 finalize 方法當做解構函式,c++ 中銷燬物件必須用到這個函式。)區別在於:c++中物件一定會被銷燬,Java中物件並非總是被垃圾回收。
5.6 成員初始化
Java 盡力保證:所有變數在使用前都能得到適當的初始化。對於區域性變數不初始化,編譯器報錯,(不修改直接執行會賦值一個預設值)對於基本型別資料成員保證有一個初始值。。。。。
Data type Initial value boolean false char [ ] byte 0 short 0 int 0 long 0 float 0.0 double 0.0 reference null
類中定義個物件引用,如果不將其初始化,飲用會獲得一個特殊的 null(null 與 void 有什麼區別)
5.6.1 指定初始化 定義類成員變數的地方賦值(c++儘量不要這麼做)。 對基本型別的初始化賦值,可以使用到常數 變數以及表示式甚至是方法(方法可以帶上引數,但是要那種經過了初始化的);像是對類物件進行的初始化,不像是基本型別的(有些時候你根本想不到他是跟給基本型別賦值的同一種東西),用的是 new XXX(); 類的每個物件都有相同的初始值
5.7 構造器初始化 int i ;然後構造器中將 i 賦值為7;i 的值首先會賦值為0然後是7。這說明了編譯器執行的順序( 初始化得到了保證)
5.7.1 初始化順序
變數定義初始化(包括了常見的基本資料型別定義以及我不怎麼使用的類物件定義的初始化)
5.7.2 靜態資料的初始化 無論建立多少個物件,靜態資料只佔用一份儲存區域。static 不能應用於區域性變數,只能作用於域。(域是一個靜態的基本型別域)。靜態初始化只有在第一次訪問的時候建立 靜態變數(包括了引用物件的初始化)--》靜態方法--》非靜態變數--》非靜態方法??? 我看這個程式碼有很多的陷阱,比如說我想當然的認為,main方法先進行,但是靜態資料成員變數的初始化(這個例子中是靜態物件的初始化)要更早,我還以為看到了 new XXX()先於 print 進行,但是這個print我忘記他是一個靜態的方法了。難道靜態的方法與靜態物件(靜態的方法)是優先順序是一樣的嗎??沒錯
例項域:故名意義需要例項如類後方可使用裡面的屬性、方法;靜態域:則不需要可以直接使後,更重的是靜態域可以作為系統快取在不同類中使用,也可以理解全域性的概念 例項域就是java中定義類的時候定義的屬性 靜態域就是在定義類的熟悉的時候加上了static
這就是初始化物件的過程
建立物件時,構造器或者靜態方法或者靜態域出發了 Java 直譯器去查詢路徑,然後載入如 Dog。class ,建立了一個類物件。同時在堆上為物件分配儲存空間。。。。。
同樣是建立靜態的物件,因為這裡是用的顯式,所以優先順序更高。隱式的靜態物件建立會導致兩者優先順序相同 ,我想這樣就解釋了有 static 修飾的靜態物件建立早於靜態方法 main,同時解釋了靜態方法中的靜態方法 print 和 new Cupboard 優先順序一樣的情況
5.7.3 顯式的靜態初始化 Java 與許多個靜態初始化動作組織成一個特殊的靜態子句。
static Cup cup1; static Cup cup2; static { cup1 = new Cup(1); cup2 = new Cup(2); }
這個特殊的子句在什麼時候執行,常見的是首次建立一個物件之時,但是還有一個是首次訪問屬於哪個類的靜態資料成員之時
5.7.4非靜態例項初始化 類似與前面的那個靜態初始化子句,但是沒有 static,訪問的時候不像是 static 的靜態初始化旨在首次的時候執行。而是每次訪問都會執行。這個也與前面的那個 一樣可以通過訪問類的資料成員 使得其子句執行。 這是我自己寫的測試
Mug m=new Mugs().mug1; m.f(0); print("測試訪問非靜態的例項初始化的某個資料成員會導致例項初始化的子句進行嗎? \n 答案是會的");
5.8陣列初始化 陣列是什麼?相同型別用一個識別符號名稱封裝到時期的一個物件序列或者基本型別資料序列,通過方括號下標操作符定義和使用。 int [ ] a1; 編譯器不允許指定陣列的大小。現在只是一個引用(為了引用分配了足夠的空間),沒有給陣列物件本身分配空間。如果要為了陣列建立空間,必須有初始化動作。---》int [ ] a1={1,2,3,4,5};
注意 :Java中可以把一個數組賦值給另一個數組,a1=a2;實際上是引用的複製,a1原先的引用被覆蓋了。這就像是物件的賦值時候需要注意的東西,他們不像是基本資料型別的賦值(基本資料型別之間賦值是獨立的)。這與前面所說的別名現象一樣的,a1 a2 是相同的引用,只不過是不同的名字。。
int[] a1 = { 1, 2, 3, 4, 5 }; int[] a2; a2 = a1; for(int i = 0; i < a2.length; i++) a2[i] = a2[i] + 1; for(int i = 0; i < a1.length; i++) print("a1[" + i + "] = " + a1[i]);
所有的陣列(無論元素是基本型別還是物件)都有一個固有成員,可以獲得組內包含多少元素。(成員是 length,從0開始,超過邊界 C語言和 c++默默接受,並允許訪問所有記憶體)。Java 一旦訪問下表過節,就會出現執行時錯誤(異常) int[] a; Random rand = new Random(47);//作者經常使用這個的原因很簡單,這個可以保證每次測試出的結果一樣 a = new int[rand.nextInt(20)];// 傳入的引數是 bound必須是一個positive 正數,同時返回值是【0,bound) print("length of a = " + a.length); print(Arrays.toString(a));//一直以來列印陣列其實有更簡單的方法,我卻棄之不用,java.util.Arrays.*;
這裡呼叫的方法傳入的引數是 bound必須是一個positive 正數,同時返回值是【0,bound)建立的陣列有多大,是由 隨機方法產生決定的。輸出表明瞭,陣列元素中基本資料型別會自動初始化為空值(對於數字和字元,是0.對於布林值是 false)
非基本型別陣列(引用陣列),整形的包裝器類,Integer 是一個類而不是一個基本型別。??? Random rand = new Random(47); Integer[] a = new Integer[rand.nextInt(20)]; print("length of a = " + a.length); // for(int i = 0; i < a.length; i++) // a[i] = rand.nextInt(500); // Autoboxing print(Arrays.toString(a));
run: length of a = 18 [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null] 成功構建 (總時間: 0 秒) 結果充分說明了,編譯器預設的引用初始化值是 null
花括號列表初始化物件陣列,這與基本資料型別相似,3使用自動包裝機制。 Integer[] a = { new Integer(1), new Integer(2), 3, // Autoboxing 逗號可以選擇使用,更好的編碼風格 };
我不是很明白書上的三種形式。。。跳過
5.8.1 可變化的引數列表
第二種形式是指的 new Object[ ]{new Integer(1),new Float(3.14)}這種的
class A {}
public class VarArgs { static void printArray(Object[] args) {//這裡是可變引數的前身 for(Object obj : args) System.out.print(obj + " "); System.out.println(); } public static void main(String[] args) { printArray(new Object[]{ new Integer(47), new Float(3.14), new Double(11.11) });//這就是第二種陣列建立形式嗎??感覺不怎麼樣啊 printArray(new Object[]{"one", "two", "three" }); printArray(new Object[]{new A(), new A(), new A()}); } }
JavaSE5支援的新特性。相較於以前的那種更好,因為 Object[] args接收只是陣列,現在的這個什麼都能接受。
static void printArray(Object... args) { for(Object obj : args) System.out.print(obj + " "); System.out.println(); } 而且還可以組合,有了可變引數,可選的尾隨引數很方便。這裡的可變引數都是 String 的物件,可以這麼說當呼叫方法的時候 只有一個 int 型別引數那麼,後面的可變引數個數==0個。呼叫方法,必須有的是能傳入被 int 型別接收的型別(如 char byte short int) static void f(int required, String... trailing) { System.out.print("required: " + required + " "); for(String s : trailing) System.out.print(s + " "); System.out.println(); }
這些東西以前都沒有接觸過感覺還是比較難的!! 其他的可變引數型別,Object 能夠接收 所有的,但是如果是一種特定的可變引數型別嘞??比如說 f(String... args) 傳入String 型別,利用 Object 方法呼叫 getClass()以及 length 方法。檢視實參傳入形參,到底變成了什麼。
static void f(Character... args) { //這是包裝器類 System.out.print(args.getClass()); System.out.println(" length " + args.length); } static void g(int... args) { //這是基本型別 System.out.print(args.getClass()); System.out.println(" length " + args.length); } public static void main(String[] args) { f('a'); f(); g(1); g(); System.out.println("int[]: " + new int[0].getClass()); //最終 int 型別元素不是安裝包裝器型別 }
run: class [Ljava.lang.Character; length 1 class [Ljava.lang.Character; length 0 class [I length 1 class [I length 0 int[]: class [I
最後一個驗證了可變引數列表不依賴與自動包裝機制,實際使用的是基本型別。 注意有可能出現模糊的情況比如說定義了a(String args) a(int args...),結果呼叫的是a( );
這個程式原來也是模糊的ambiguous public class OverloadingVarargs2 { static void f(String i, Character... args) { //這位作者在寫書的時候應該是幾年前了,float i 都可以接受字元型別了??? //我把 float I 改成了 String System.out.println("first"); } static void f(Character... args) { System.out.print("second"); } public static void main(String[] args) { f("1", 'a'); // 把 f(1,'a') 變成了 String 和 char 組合 f('b'); //把f(‘a’,'b') 變成了單個的字元 } } ///:~
作者的修改方式,是在第二個方法前面增加一個非可變引數,那麼兩個都有了非可變的引數區分。
5.9列舉型別 JavaSE 5的新特性:關鍵字 enum。可以將之看作是一個類,具有自己的方法,還有自己定義的整形常量組合,按照命名規則全部都是大寫的。
列舉還可以與 switch 配合使用,因為控制流程語句中的條件判斷case 旁邊的吹能使常量和字元(帶有單引號的)
19章會更深入的討論它。