1. 程式人生 > 其它 >如何正確的建立和銷燬Java物件

如何正確的建立和銷燬Java物件

一、介紹

Java由Sun Microsystems發明並在1995年釋出,是世界上使用最廣泛的程式語言之一。Java是一個通用程式語言。由於它擁有功能強大的庫、執行時、簡單的語法、平臺無關(Write Once, Run Anywhere - WORA)以及令人敬畏的社群從而吸引了很多的開發者。

本系列文章我們我們將會覆蓋一些高階的Java概念,我們假設你對Java語言已經有一些基礎知識。本系列文章並不是一個完整的參考,而是一個將您的Java技能提升到下一個級別的詳細指南。

本系列文章中將會看到一些程式碼片段,在這些程式碼片段裡面將會使用java 7的語法以及java 8的語法。

二、例項構造(Instance Construction)

Java是面向物件的程式語言,所以新例項(objects)的建立可能是它最重要的概念之一。在新的類例項中構造器(Constructors)扮演了非常核心的角色,Java對於構造器(Constructors)的定義提供了很多方案。

2.1 隱式(implicitly)構造器

Java允許定義無任何構造器的類,但是這並不意味著此類沒有構造器。比如說,讓我們看一下下面這個類。

packagecom.javacodegeeks.advanced.construction;
publicclassNoConstructor{
}

此類沒有構造器,但是Java編譯器會隱式地(implicitly)生成一個構造器並且在使用new關鍵字建立新的類例項時會被呼叫。

finalNoConstructornoConstructorInstance=newNoConstructor();

2.2 無參構造器(Constructors without Arguments)

無參構造器是顯式執行Java編譯器工作的最簡單的方法。

packagecom.javacodegeeks.advanced.construction;
publicclassNoArgConstructor{
publicNoArgConstructor(){
//Constructorbodyhere
}
}

在使用new關鍵字建立此類的新例項時會此構造器將會被呼叫。

finalNoArgConstructornoArgConstructor=newNoArgConstructor();

2.3 有參構造器(Constructors with Arguments)

有參構造器是引數化建立類例項的一個非常有意思和有用的方法。下面這個類定義了一個具有兩個引數的構造器。

packagecom.javacodegeeks.advanced.construction;
publicclassConstructorWithArguments{
publicConstructorWithArguments(finalStringarg1,finalStringarg2){
//Constructorbodyhere
}
}

在這種情況下,當使用new關鍵字建立類例項時,兩個構造引數都必須提供。

finalConstructorWithArgumentsconstructorWithArguments=newConstructorWithArguments("arg1","arg2");

非常有意思的是,使用this關鍵字,構造器之間可以相互呼叫。這種連線建構函式的方式在作為減少程式碼重複方面是一個非常好的實踐,並且從跟本上說這樣做可以讓一個類只有一個初始化入口點。接上例,我們新增一個只有一個引數的構造器。

publicConstructorWithArguments(finalStringarg1){
this(arg1,null);
}

2.4 初始化塊(Initialization Blocks)

Java也提供了另外一種使用初始化塊的方式實現初始化邏輯。這個特性很少使用但是非常有必要了解一下它的存在。

packagecom.javacodegeeks.advanced.construction;
publicclassInitializationBlock{
{
//initializationcodehere
}
}

在某些情況下,初始化塊可以彌補匿名無參構造器的缺陷。有一些特殊的類可能會有很多個初始化塊並且他們會依次按照他們在程式碼中定義的順序被呼叫,比如:

packagecom.javacodegeeks.advanced.construction;
publicclassInitializationBlocks{
{
//initializationcodehere
}{
//initializationcodehere
}
}

初始化塊並不是替代構造器並且他們可以獨立於構造器而存在。但是富貴需要提及的最重要的一點就是初始化塊會在任何構造器被呼叫之前被執行。

packagecom.javacodegeeks.advanced.construction;
publicclassInitializationBlockAndConstructor{
{
//initializationcodehere
}
publicInitializationBlockAndConstructor(){
}
}

2.5 構造保障(Construction guarantee)

Java提供了一些開發者所依賴的初始化保障,未初始化的例項和類引數會自動初始化為它們的預設值。

讓我們使用下面的例子來確認一下這些預設值。

packagecom.javacodegeeks.advanced.construction;
publicclassInitializationWithDefaults{
privatebooleanbooleanMember;
privatebytebyteMember;
privateshortshortMember;
privateintintMember;
privatelonglongMember;
privatecharcharMember;
privatefloatfloatMember;
privatedoubledoubleMember;
privateObjectreferenceMember;

publicInitializationWithDefaults(){
System.out.println("booleanMember="+booleanMember);
System.out.println("byteMember="+byteMember);
System.out.println("shortMember="+shortMember);
System.out.println("intMember="+intMember);
System.out.println("longMember="+longMember);
System.out.println("charMember="+
Character.codePointAt(newchar[]{charMember},));
System.out.println("floatMember="+floatMember);
System.out.println("doubleMember="+doubleMember);
System.out.println("referenceMember="+referenceMember);
}
}

一旦使用new關鍵字例項化:

inalInitializationWithDefaultsinitializationWithDefaults=newInitializationWithDefaults();

將會在控制檯輸出如下結果:

booleanMember=false
byteMember=
shortMember=
intMember=
longMember=
charMember=
floatMember=0.0
doubleMember=0.0
referenceMember=null

2.6 可見性(Visibility)

構造器受Java可見性規則約束並且可以擁有訪問控制修飾符來決定是否其他類可以呼叫特定的建構函式。

2.7 垃圾回收(Garbage collection)

Java(特別是JVM)使用自動垃圾回收機制。簡而言之,當新物件被建立,JVM就會自動為這些新建立的物件分配記憶體。於是,當這些物件沒有任何引用的時候,他們就會被銷燬並且他們所佔用的記憶體就會被回收。

Java垃圾回收是分代的,基於這種假設(分代假設)大多數的物件在很年輕的時候就已經不可達(在他們被建立之後的很短的時間內就沒有任何引用並且被安全銷燬)。大多數開發者曾經相信在Java中建立物件是很慢的並且應該儘可能地避免新物件的例項化。

實際上,這並不成立:在Java中建立物件的開銷非常的小並且很快。雖然如此,但是沒有必要建立生命週期比較長的物件,因為建立過多的長壽命物件最終可能會填滿老年代空間從而引發stop-the-world的垃圾回收,這樣的話開銷就會比較大。

2.8 終結器(Finalizers)

到目前為止,我們已經談到了建構函式和物件初始化,但實際上並沒有提到任何關於物件銷燬的內容。這是因為Java使用垃圾收集器去管理物件的生命週期,並且垃圾收集器的責任就是去銷燬無用物件並回收這些物件佔用的記憶體。

然而,在Java中有一個被稱為終結器(Finalizers)的特殊特性,它有點類似於解構函式,但是在執行資源清理時它所解決的是不同的意圖。終結器(Finalizers)是被考慮用來解決一些危險的特徵(比如會導致無數的副作用和效能問題的問題)。

一般來說,他們是沒有必要的,應該避免(除了非常罕見的情況下,主要是有關本地物件)。Java 7語言引入了一種名為try-with-resources的更好的替代方法和AutoCloseable介面,它允許像如下的方式這樣乾淨的寫程式碼:

try(finalInputStreamin=Files.newInputStream(path)){
//codehere
}

3、靜態初始化(Static initialization)

到目前為止,,我們已經談到了建構函式和物件初始化。但是Java也支援類級別的初始化構造,我們稱之為靜態初始化(Static initialization)。

靜態初始化(Static initialization)有點類似於初始化塊,除了需要新增static關鍵字之外。注意靜態初始化在每次類載入的時候它只執行一次。比如:

packagecom.javacodegeeks.advanced.construction;
publicclassStaticInitializationBlock{
static{
//staticinitializationcodehere
}
}

和初始化塊類似,在類定義時你可以包含任意數量的初始化塊,它們會根據在類程式碼中出現的順序依次執行,比如:

packagecom.javacodegeeks.advanced.construction;
publicclassStaticInitializationBlocks{
static{
//staticinitializationcodehere
}
static{
//staticinitializationcodehere
}
}

因為靜態初始化(Static initialization)塊可以從多個並行執行緒中觸發(第一次類載入發生),Java執行時保證線上程安全的前提下僅僅被執行一次。

4、構造模式(Construction Patterns)

過去這幾年很多易於理解和廣泛應用的構造模式在Java社群出現。我們將會介紹幾個比較常用的:單例模式(singleton)、幫助器(helpers)、工廠模式(factory)、依賴注入(dependency injection )——大家熟知的控制反轉(inversion of control)。

4.1 單例模式(Singleton)

單例模式是軟體開發者社群中最老也是最具爭議性的模式之一。基本來說,它的主要思想就是確保在任何時候類僅僅只有一個例項被建立。思想就是如此簡單,然而單例模式引發了很多關於如何使之正確的討論,特別是執行緒安全的討論。下面是單例模式原生版本的例子:

packagecom.javacodegeeks.advanced.construction.patterns;
publicclassNaiveSingleton{
privatestaticNaiveSingletoninstance;

privateNaiveSingleton(){
}

publicstaticNaiveSingletongetInstance(){
if(instance==null){
instance=newNaiveSingleton();
}
returninstance;
}
}

這段程式碼至少有一個問題就是如果多個執行緒同時呼叫,那麼此類就能夠建立多個例項。設計合適的單例模式的方法之一是使用類的 static final屬性。

finalpropertyoftheclass.
packagecom.javacodegeeks.advanced.construction.patterns;
publicclassEagerSingleton{
privatestaticfinalEagerSingletoninstance=newEagerSingleton();

privateEagerSingleton(){
}

publicstaticEagerSingletongetInstance(){
returninstance;
}
}

如果你不想浪費資源並且希望在單例物件真正需要的時候才被延遲建立的話,這就要求顯示同步了(explicit synchronization),這就有可能導致多執行緒環境中的併發性降低(關於併發的詳細內容我們將會在後續的文章中討論)。

packagecom.javacodegeeks.advanced.construction.patterns;
publicclassLazySingleton{
privatestaticLazySingletoninstance;
privateLazySingleton(){
}

publicstaticsynchronizedLazySingletongetInstance(){
if(instance==null){
instance=newLazySingleton();
}
returninstance;
}
}

如今,在大多數的案例中單例模式並不被考慮作為一個很好的選擇,主要是因為單例模式將會導致程式碼很難測試。依賴注入模式讓單例模式變得沒有必要。

4.2 Utility/Helper類

utility或者helper類是被許多開發者所使用的相當流行的一種模式。基本來說,它所代表的是無例項( non-instantiable)類(構造器被定義成private),僅僅可以選擇將方法定義成final(後續會介紹如何定義類)或者static。比如;

packagecom.javacodegeeks.advanced.construction.patterns;
publicfinalclassHelperClass{
privateHelperClass(){
}

publicstaticvoidhelperMethod1(){
//Methodbodyhere
}

publicstaticvoidhelperMethod2(){
//Methodbodyhere
}
}

站在開發者的角度,helpers類經常所扮演的是一個容器的角色,這個容器中放了很多在其他地方找不到但是其他類需要相互共享和使用的互相不相關的方法。這種設計決定了在很多情況下要避免使用:總能找到另一種重用所需功能的方式,保持程式碼的簡潔和清晰。

4.3 工廠模式(Factory)

工廠模式被證明是軟體開發人員手中非常有用的技術。因此,Java有幾種風格工廠模式,從工廠方法到抽象工廠。工廠模式最簡單的例子是返回特定類的新例項的靜態方法(工廠方法)。例如:

packagecom.javacodegeeks.advanced.construction.patterns;
publicclassBook{
privateBook(finalStringtitle){
}
publicstaticBooknewBook(finalStringtitle){
returnnewBook(title);
}
}

有人可能會爭辯說,介紹newBook工廠方法並沒有什麼意義,但是使用這種模式通常會使程式碼更具可讀性。工廠模式的另一個變化涉及介面或抽象類(抽象工廠)。例如,讓我們定義一個工廠介面:

publicinterfaceBookFactory{
BooknewBook();
}

依賴庫型別,完成幾種不同的實現:

publicclassLibraryimplementsBookFactory{
@Override
publicBooknewBook(){
returnnewPaperBook();
}
}

publicclassKindleLibraryimplementsBookFactory{
@Override
publicBooknewBook(){
returnnewKindleBook();
}
}

現在,Book的特定類被隱藏在BookFactory介面實現之後,BookFactory仍然提供建立book的通用方式。

4.4 依賴注入(Dependency Injection)

依賴注入(一說控制反轉)被類設計者認為是一個很好的做法:如果某些類的例項依賴其他類的例項,被依賴的例項應該通過構造(比如通過設定器——setters,或者策略——strategies模式等)的思想提供給依賴的例項,而不是依賴的例項自行建立。看一下下面這種情況:

packagecom.javacodegeeks.advanced.construction.patterns;
importjava.text.DateFormat;
importjava.util.Date;
publicclassDependant{
privatefinalDateFormatformat=DateFormat.getDateInstance();

publicStringformat(finalDatedate){
returnformat.format(date);
}
}

類Dependant需要一個DateFormat的例項,並且它僅僅只是在構造時通過呼叫DateFormat.getDateInstance() 建立。最好的設計方案應該是通過構造器引數的形式去完成相同的事情。

packagecom.javacodegeeks.advanced.construction.patterns;
importjava.text.DateFormat;
importjava.util.Date;
publicclassDependant{
privatefinalDateFormatformat;

publicDependant(finalDateFormatformat){
this.format=format;
}


publicStringformat(finalDatedate){
returnformat.format(date);
}
}

按這種方案實現的話,類的所有依賴都是通過外部提供,這樣就很容易的修改date format和為類寫測試用例。

在本系列文章的這一部分中,我們一直在研究類和類的例項構造以及初始化技術,涵蓋了幾種廣泛使用的模式。在下一部分中,我們將分析Object類以及其熟知方法的用法:equals,hashCode,toString和clone。