随着計算機革命的發展,“不安全”的程式設計方式已逐漸成為程式設計代價高昂的主因之一。
初始化和清理正是涉及安全的兩個問題。C++引入了構造器的概念,在建立對象時被自動調用的特殊方法。Java也采用了構造器,并額外提供了“垃圾回收器”。對于不再使用的記憶體資源,垃圾回收器能自動将其釋放。
1、用構造器確定初始化。建立對象時,如果其類具有構造器,Java就會在使用者有能力操作對象之前自動調用相應的構造器,進而保證了初始化的進行。不接受任何參數的構造器叫做預設構造器,Java文檔中通常使用術語無參構造器。
假設類Tree 有一個構造器,它接受一個整型變量來表示樹的高度,那它就能這樣建立一個Tree對象。
Tree t = new Tree(12);
如果Tree(int)是Tree類中唯一的構造器,那麼編譯器将不會允許你以其他任何方式建立Tree對象。
構造器有助于減少錯誤,并使代碼更易于閱讀。從概念上講,“初始化”與“建立”是彼此獨立的,然而在上面的代碼中,你找不到對initialize()方法的調用。在Java中,“初始化”和“建立”捆綁在一起,兩者不能分離。
構造器是一種特殊類型的方法,因為它沒有傳回值,這與傳回值為空明顯不同。對于空傳回值,盡管方法本身不會自動傳回什麼,但仍選擇讓它傳回别的東西。構造器則不會傳回任何東西你别無選擇(new表達式确實傳回了對建立對象的引用,但構造器本身并沒有任何傳回值)。
2、方法重載。當建立一個對象時,也就給此對象配置設定到的存儲空間取了一個名字。所謂方法則是給某個動作取得名字。通過使用名字,你可以引用所有的對象和方法。區分重載方法。即每個重載的方法必須有一個獨一無二的參數類型清單。
涉及基本類型的重載。基本類型能從一個“較小”的類型自動提升至一個“較大”的類型。比如傳入參數為常數5,某一個重載方法接受int型參數,它就會被調用。如果傳入的資料類型(實際參數類型)小于方法中聲明的形式參數類型,實際資料類型就會被提升。char型略有不同,如果無法找到恰好接受char參數的方法,就會把char直接提升至int型。 方法接受較小的基本類型作為參數,如果傳入的實際參數比較大,則得聽過類型轉換來執行窄化轉換(顯式進行)。否則編譯器則會報錯。
以傳回值區分重載方法是不可行的。比如有時候我們隻是調用方法。比如f();并不需要任何傳回值,這種情況下,編譯器無法靠傳回值來分辨我所使用的的具體是那一個方法。
3、預設構造器。如果寫的類中沒有構造器,則編譯器會自動幫你建立一個預設的構造器。如果已經定義了一個構造器(無論是否有參數),編譯器就不會自動幫你建立預設構造器。
4、this關鍵字。this關鍵詞隻能在方法内部使用,表示對“調用方法的那個對象”的引用。this的用法和其他對象引用并無不同。如果在方法内部調用同一個類的另一個方法,就不必使用this,直接調用即可。
public class User{
void pick(){};
void pit(){pick();}
}
隻有當需要明确指出對目前對象的引用時,才需要使用this關鍵字。傳遞目前對象給其他方法時,this關鍵字也很有用。
class Person {
public void eat(Apple apple){
Apple peeled = apple.getPeeled();
System.out.println("Yummy");
}
}
class Peeler{
static Apple peel(Apple apple){
return apple;
}
}
class Apple {
Apple getPeeled(){
return Peeler.peel(this);
}
}
public class PassingThis{
public static void main(String[] args){
new Person().eat(new Apple());
}
}
5、在構造器中調用構造器。有時候為了一個類寫了多個構造器。有時候可能想在一個構造器中調用另一個構造器,以避免重複代碼。可用this關鍵字做到這一點。
public class Flower{
Flower(int petals){}
Flower(String ss){}
Flower(String s,int petals){
this(petals);
// this(s); 盡管可以用this調用一個構造器,但卻不能調用兩個。此外,必須将構造器調用最起始處,否則編譯器會報錯
}
}
有時候,傳入的形參的名稱和類的資料成員的名稱相同時,就可以使用this來解決這個歧義的問題。
以及,除了構造器以外,編譯器禁止在其他地方調用構造器。
6、static的含義。static就是沒有this的方法。在static方法的内部不能調用非靜态方法。而且可以在沒有建立任何對象的前提下,僅僅通過類本身來調用static方法。static方法具有全局函數的語義。使用static方法時,由于不存在this,是以不是通過“面向對象發送消息”的方式來完成的。如果在代碼中出現了大量的static方法,就該重新考慮自己的設計了。
7、清理:終結處理和垃圾回收。假定你的對象(并非使用new)獲得了一塊“特殊”的記憶體區域。由于垃圾回收器隻知道釋放那些經由new配置設定的記憶體,是以它不知道該如何釋放該對象的這塊“特殊”記憶體。為了應對這種情況,Java允許在類中定義一個名為finalize()的方法。它的工作原理“假定”是這樣的:一旦垃圾回收器準備釋放好對象占用的存儲空間,将先調用其finalize()方法,并且在下一次垃圾回收動作發生時,才會真正回收對象占用的記憶體。是以你要是打算使用finalize(),就能在垃圾回收時刻做一些重要的清理工作。
這裡存在一個程式設計陷阱:finalize()并不是C++中的析構函數。在C++中,對象一定會被銷毀(如果程式沒有缺陷)。但是Java中的對象卻并非總是被垃圾回收。也就是說:在Java中,1、對象可能不被垃圾回收、2、垃圾回收不等于“析構”。隻要程式沒有瀕臨存儲空間用完的那一刻,對象占用的空間就總也得不到釋放。如果程式執行結束,并且垃圾回收器一直都沒有釋放你建立的任何對象的存儲空間,則随着程式的退出,那些資源也将全部交還給作業系統。因為垃圾回收本身也有開銷,要是不使用它,就不必支付這部分開銷了。
垃圾回收隻與記憶體有關。使用垃圾回收器的唯一原因是為了回收程式不再使用的記憶體。本地方法可以讓java調用非java代碼。本地方法現在隻支援C和C++。但是C和C++又可以調用其它語言寫的代碼。是以實際上可以調用任何代碼。在非Java代碼中,也許會調用C的malloc()函數系列來配置設定存儲空間,而且除非使用free()函數,否則存儲空間得不到釋放,進而造成記憶體洩漏。是以要在finalize()中用本地方法調用它。
Java虛拟機并未面臨記憶體耗盡的情形,它是不會浪費時間去執行垃圾回收以恢複記憶體的。如果你想看一下垃圾回收時的情形,那可以使用System.gc(),用于強制進行終結動作。
8、垃圾回收器是如何工作的。一般來說,在堆上配置設定對象的代價是十分高昂的。然而,垃圾回收器對于提高對象的建立速度,卻有明顯的效果。垃圾回收器的介入,當他工作時,将一面回收空間,一面使堆中的對象緊湊排列,這樣“堆指針”就可以很容易移動到更靠近傳送帶(某些Java虛拟機中,堆的實作很像傳送帶,每配置設定一個新對象,就往前移動一格)的開始處。
如何判定對象已經死了呢?垃圾回收器是這樣判斷的:對任何“活”的對象,一定能最終追溯到其存活在堆棧或靜态存儲區之中的引用。這個引用鍊條可能會穿過數個對象層次。由此,如果從堆棧和靜态存儲區開始,周遊所有的引用,就能找到所有“活”的對象。對于發現的每個引用,必須追蹤它所引用的對象,然後是此對象包含的所有引用。 如此反複進行,知道“根源于堆棧和靜态存儲區的引用”所形成的網絡全部被通路為止。
如何處理找到的存活對象,取決于不同的Java虛拟機實作。有一種名為 停止--複制。先暫停程式的運作,然後将所有存活的對象從目前堆複制到另一個堆。沒有被複制的全部都是垃圾。當對象被複制到新堆時,它們是一個挨着一個的,以新堆保持緊湊排列。但有時候這個方法并不好用,首先它就必須要有兩個堆,這樣就浪費了不少空間。其次是在程式穩定之後,隻會産生少量垃圾,但是複制還是得将所有記憶體從一處複制到另一處。為了避免這種情況,要是沒有新垃圾産生,Java虛拟機就會轉換到另一種模式,這種模式稱之為 标記--清掃。它的思路同樣是從堆棧和靜态存儲區出發,周遊所有的引用,進而找出所有存活的對象。每當它找到一個存活對象,就會給對象設一個标記。這個過程中不會回收任何對象。隻有全部标記工作完成的時候,清理動作才會開始。在清理過程中,沒有标記的對象将被釋放,不會發生任何複制工作。是以剩下的堆空間是不連續的。垃圾回收器要是希望能到連續空間的話,就得重新整理一遍。嚴格來說,停止--複制 要求在釋放舊對象前,必須先把所有存活對象從舊堆複制到新堆。但有些對象占用記憶體比較大,于是就會占用單獨的塊。有了塊之後,垃圾回收器在回收的時候就可以往廢棄的塊裡拷貝對象了。每個塊都用相應的代數來記錄它是否還存活。在清理過程中,大型對象并不會被複制(隻是其代數會增加),内含小型對象的那些塊則被複制并整理。
Java虛拟機中還有許多附加技術用以提升速度。尤其是與加載器操作有關的。被稱為“即時”編譯器的技術。這種技術可以把程式全部或部分翻譯成本地機器碼,程式運作速度是以可以提升。當需要裝載某個類時,編譯器會先找到其.class檔案。然後将該類的位元組碼裝入記憶體。此時,有兩種方案可供選擇,一種是就讓即時編譯器編譯所有代碼,但是這種做法有兩個缺陷,這種加載動作散落在這個程式生命周期内,累加起來要花更多時間,并且會增加可執行代碼的長度,這将導緻頁面排程,進而降低程式速度。另一種做法稱為惰性評估,意思是即時編譯器隻在必要的時候才編譯代碼。這樣,從不會被執行的代碼也許壓根就不會被“即時”編譯器所編譯。新版JDK中的Java HotSpot技術就采用了類似的方法,代碼每次被執行的時候都會做一些優化,是以執行的次數越多,它的速度就越快。
9、成員初始化。初始化的順序是,先靜态對象,而後是“非靜态對象”。
構造器初始化。可以使用構造器進行初始化,但是自動初始化将會在構造器被調用之前發生。
靜态資料的初始化。無論建立多少個對象,靜态資料都隻占用一份存儲區域。static關鍵字不能應用于局部變量,是以它隻能作用于域。如果一個域是靜态的基本類型域,且也沒有對它進行初始化。那麼它會獲得基本類型的标準初值。如果它是一個對象引用,那麼它的預設初始值就是null。靜态初始化動作隻進行一次:當首次生成這個類的一個對象時,或者首次通路屬于這個類的靜态成員時(即便從未生成過那個類的對象)。
數組初始化。數組隻是相同類型的、用一個辨別符名稱封裝到一起的一個對象序列或基本類型資料序列。擁有的隻是對數組的一個引用。數組的建立是在運作時刻進行的。可變參數清單。提供了一種友善的文法來建立對象并調用方法。由于是以的類都直接或間接繼承于Object類,是以可以建立以Object數組為參數的方法。
枚舉類型。當建立枚舉時,編譯器會自動添加一些有用的特性,比如toString()方法,顯示某個enum執行個體的名字。ordinal()方法,用來表示某個特定enum常量的聲明順序。static values(),用來按照enum常量的聲明順序,産生由這些常量值構成的數組。
轉載于:https://www.cnblogs.com/zhazhaR/p/8023747.html