java教學筆記之對象的創(chuàng)建與銷毀
本課程的目標是幫你更有效的使用Java。其中討論了一些高級主題,包括對象的創(chuàng)建、并發(fā)、序列化、反射以及其他高級特性。本課程將為你的精通Java的旅程提供指導。
1. 引言
在TIOBE 編程語言排名中,Sun 公司于1995年開發(fā)的Java語言是世界上使用最廣泛的編程語言之一。作為一種通用編程語言,因為強大的工具包和運行時環(huán)境、簡單的語法、豐富的平臺支持(一次編寫,到處運行)以及的異?;钴S的社區(qū)支持,Java語言對軟件開發(fā)工程師極具吸引力。
在這一系列的文章中,涵蓋了Java相關(guān)的高級內(nèi)容,因此假設(shè)讀者已具有基本語言知識。這并不是一個完整的參考手冊,而是讓你的技能更上一層樓的詳盡指南。
本課程中包含了大量的代碼片段,在有些對方為了做對比,會同時提供Java 7和Java 8的示例。
2. 實例構(gòu)造
作為一種面向?qū)ο笳Z言,對象的創(chuàng)建也許就是Java語言中最重要的概念之一。構(gòu)造方法是在對象實例初始化過程中具有舉足輕重的地位,并且Java提供了多種方式來定義構(gòu)造方法。
2.1 隱式(產(chǎn)生的)構(gòu)造方法
Java允許在定義類時不聲明任何的構(gòu)造方法,并這并不代表類沒有構(gòu)造方法。我們看下面類的定義:
package com.javacodegeeks.advanced.construction;
public class NoConstructor {
}
這個類未定義構(gòu)造方法,但是Java編譯器會為其隱式生成一個,從而使我們可以使用new關(guān)鍵字來創(chuàng)建新的對象實例。
final NoConstructor noConstructorInstance = new NoConstructor();
2.2 無參構(gòu)造方法
無參構(gòu)造方法是最簡單的通過顯式聲明來替代Java編譯生成構(gòu)造方法的方式。
package com.javacodegeeks.advanced.construction;
public class NoArgConstructor {
public NoArgConstructor() {
// Constructor body here
}
}
在使用new關(guān)鍵字創(chuàng)建新的對象實例時,上面的構(gòu)造方法就會被調(diào)用。
2.3 有參構(gòu)造方法
有參構(gòu)造方法最有意思并且廣泛使用,通過指定參數(shù)來定制新實例的創(chuàng)建。下面的例子中定義了一個有兩個參數(shù)的構(gòu)造方法。
package com.javacodegeeks.advanced.construction;
public class ConstructorWithArguments {
public ConstructorWithArguments(final String arg1,final String arg2) {
// Constructor body here
}
}
這種場景中,當使用new關(guān)鍵字來創(chuàng)建實例時,需要同時提供構(gòu)造方法上定義的兩個參數(shù)。
final ConstructorWithArguments constructorWithArguments = new ConstructorWithArguments( "arg1", "arg2" );
有趣的是構(gòu)造方法之間可以通過this關(guān)鍵字互相調(diào)用。在實踐中,推薦通過使用this把多個構(gòu)造方法鏈起來以減少代碼重復,并從基礎(chǔ)上使對象具有單一的初始化入口。作為示例,下面的代碼中定義了只有一個參數(shù)的構(gòu)造方法。
public ConstructorWithArguments(final String arg1) {
this(arg1, null);
}
2.4 初始化代碼塊
除了構(gòu)造方法,Java還提供了通過初始化代碼塊進行初始化的邏輯。這種用法雖然少見,但多了解一些也沒害處。
package com.javacodegeeks.advanced.construction;
public class InitializationBlock {
{
// initialization code here
}
}
另一方面,初始化代碼塊也可被看作是無參的隱式構(gòu)造方法。在一個具體的類中可以定義多個初始化代碼塊,在執(zhí)行的時候按照他們在代碼中的位置順序被調(diào)用,如下面的代碼所示:
package com.javacodegeeks.advanced.construction;
public class InitializationBlocks {
{
// initialization code here
}
{
// initialization code here
}
}
實始化代碼塊并不是為了取代構(gòu)造方法,相反它們可以同時出現(xiàn)。但是要記住,初始化代碼快會在構(gòu)造方法調(diào)用之前被執(zhí)行。
package com.javacodegeeks.advanced.construction;
public class InitializationBlockAndConstructor {
{
// initialization code here
}
public InitializationBlockAndConstructor() {
}
}
2.5 保證構(gòu)造默認值
Java提供了確定的初始化保證,程序員可以直接使用初始化結(jié)果。未初始化的實例以及類變量(static)會自動初始化為相應的默認值。
類型 默認值
boolean False
byte 0
short 0
int 0
long 0L
char \u0000
float 0.0f
double 0.0d
對象引用 null
表 1
我們通過下面的例子來驗證上表中的默認值:
package com.javacodegeeks.advanced.construction;
public class InitializationWithDefaults {
private boolean booleanMember;
private byte byteMember;
private short shortMember;
private int intMember;
private long longMember;
private char charMember;
private float floatMember;
private double doubleMember;
private Object referenceMember;
public InitializationWithDefaults() {
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( new char[] { charMember }, 0 ) );
System.out.println( "floatMember = " + floatMember );
System.out.println( "doubleMember = " + doubleMember );
System.out.println( "referenceMember = " + referenceMember );
}
}
當使用new關(guān)鍵字實例化對象之后:
final InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults(),
可從控制臺中看到輸出結(jié)果如下:
booleanMember = false byteMember = 0 shortMember = 0 intMember = 0 longMember = 0 charMember = 0 floatMember = 0.0 doubleMember = 0.0 referenceMember = null
2.6 可見性
構(gòu)造方法遵從Java的可見性規(guī)則,并且可以通過訪問控制修飾符決定在其他類中是否能調(diào)用該構(gòu)造方法。
修飾符 包可見性 子類可見性 公開可見性
public 可見 可見 可見
protected 可見 可見 不可見
<無修飾符> 可見 不可見 不可見
private 不可見 不可見 不可見
表2
2.7 垃圾回收
Java(準確的說是JVM)擁有自動的垃圾回收機制。簡單來講,當有新對象創(chuàng)建時,會自動為其分配內(nèi)在;然后當對象不再被引用后,他們會被自動銷毀,相應的內(nèi)存也會被回收。
Java垃圾回收采用分代回收的機制,并基于"大多數(shù)對象生命短暫"的假設(shè)(即在對象創(chuàng)建之后很快就不會被再引用,所以可以被安全的銷毀)。大多程序員習慣性的認為Java中對象創(chuàng)建的效率很低所以要盡可能避免新對象的創(chuàng)建。事實上,這種認識是不對的。在Java中創(chuàng)建對象的開銷是相當?shù)偷模⑶宜俣群芸?。真正代來巨大開銷的是不必要的長期存活的對象,因此他們最終會被遷移到老年代,并導致stop-the-world發(fā)生。
2.8 對象終結(jié)器(Finalizers)
前面我們講述的都是構(gòu)造方法和對象初始化相關(guān)的主題,但還未提及他們的反面:對象銷毀。主要是因為Java使用垃圾回收機制來管理對象的生命周期,所以銷毀不必要的對象并釋放所需內(nèi)存就成了垃圾回收的職責了。
不過,Java還是提供了另外一種類似于析構(gòu)函數(shù)的終結(jié)器(finalizer)的特性,擔任多種資源清理的責任。Finalizer一般被看作是危險的事情(因為它會帶來多種副作用和性能問題)。通常并不需要finalizer因此要盡量避免使用它(除了極少見的包含大量本地對象(native objects)的場景)。Java 7中引入的try-with-resources語法和AutoCloseable接口可當作finalizer的替代選擇,并可寫出如下簡潔的代碼:
try ( final InputStream in = Files.newInputStream( path ) ) {
// code here
}
3. 靜態(tài)初始化
上面我們學習了類實例的構(gòu)造與初始化,除此之外,Java還支持類級別的初始化構(gòu)造,稱作靜態(tài)初始化。靜態(tài)初始化與上面介紹的初始化代碼塊類似,只是多了額外的static關(guān)鍵字修飾。需要注意的是靜態(tài)初始化只會在類加載時執(zhí)行一次。示例如下:
與初始化代碼塊類似,可以在類中定義多個靜態(tài)初始化塊,它們在類中的位置決定在初始化時執(zhí)行的順序。示例如下;
package com.javacodegeeks.advanced.construction;
public class StaticInitializationBlocks {
static {
// static initialization code here
}
static {
// static initialization code here
}
}
因為靜態(tài)初始化塊可以被多個并行執(zhí)行的線程觸發(fā)(當類被初始加載時),JVM運行時保證初始化的代碼以線程安全的方式只被執(zhí)行一次。
4. 構(gòu)造器模式
這些年多種容易理解的構(gòu)造器(創(chuàng)建者)模式被引入到Java社區(qū)。下面我們會學習其中比較流行的幾個:單例模式、輔助類模式、工廠模式以及依賴注入(也稱為控制反轉(zhuǎn))。
4.1 單例模式
單例是一種歷史悠久卻在軟件開發(fā)社區(qū)中飽受爭議的模式。單例模式的核心理念是保證在任何時候給定的類只有一個對象被創(chuàng)建。雖然聽起來很簡單,但人們對如何以正確且線程安全的方式創(chuàng)建對象進行了大量的討論。下面的代碼中展示了簡單版本的單例模式實現(xiàn):
package com.javacodegeeks.advanced.construction.patterns;
public class NaiveSingleton {
private static NaiveSingleton instance;
private NaiveSingleton() {
}
public static NaiveSingleton getInstance() {
if( instance == null ) {
instance = new NaiveSingleton();
}
return instance;
}
}
上面的代碼至少有一個問題:在多線程并發(fā)場景中可能會創(chuàng)建出多個對象。一種合理的實現(xiàn)方式(但不能延遲加載)是使用類的static`final`屬性。如下:
final property of the class.
package com.javacodegeeks.advanced.construction.patterns;
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
如果你不想浪費寶貴的資源,希望單例對象只在真正需要的時候才被創(chuàng)建,那么就要使用顯式的同步方式,不可這種方法可能會降低多線程環(huán)境下的并發(fā)性(更多關(guān)于Java并發(fā)的細節(jié)將會在Java進階9-并發(fā)最佳實踐中詳細介紹)。
package com.javacodegeeks.advanced.construction.patterns;
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if( instance == null ) {
instance = new LazySingleton();
}
return instance;
}
}
現(xiàn)在,在很多場景下單例模式不再被認為是一種好的選擇,因為他們會使代碼不易于測試。另外依賴注入模式的產(chǎn)生也使單例模式變得不再必要。
4.2 工具類/輔助類
工具類/輔助類模式在Java開發(fā)者當中相當流行。它的核心理念就是使用不可實例化的類(通過聲明private構(gòu)造方法)、可選的final(更多關(guān)于聲明final類的細節(jié)將會在Java進階3-類和接口的設(shè)計中詳細介紹)關(guān)鍵字以及靜態(tài)方法。示例如下:
package com.javacodegeeks.advanced.construction.patterns;
public final class HelperClass {
private HelperClass() {
}
public static void helperMethod1() {
// Method body here
}
public static void helperMethod2() {
// Method body here
}
}
很多經(jīng)驗豐富的開發(fā)者認為這種模式會讓工具類成為各種不相關(guān)方法的容器。因為有些方法沒有合適的放置位置卻需要被其他類使用,就會被誤放入工具類中。在大多數(shù)場景中也應該避免這種設(shè)計:總會有更好的功能復用的方式,保持代碼清晰簡潔。
4.3 工廠模式
工廠模式被證明是開發(fā)者的極其強大的利器,在Java中有多種實現(xiàn)方式:工廠方法和抽象工廠。最簡單的例子就是使用static方法返回特定類的實例(工廠方法),如下:
package com.javacodegeeks.advanced.construction.patterns;
public class Book {
private Book( final String title) {
}
public static Book newBook( final String title ) {
return new Book( title );
}
}
雖然使用這種方法能提高代碼的可讀性,但經(jīng)常爭議的一點是難以給newBook工廠方法賦予更豐富的場景。另外一種實現(xiàn)工廠模式的方法是采用接口或抽象類(抽象工廠)。如下,我們定義一個工廠接口:
public interface BookFactory {
Book newBook();
}
根據(jù)圖片館的不同,我們可以有多種不同的newBook實現(xiàn):
public class Library implements BookFactory {
@Override
public Book newBook() {
return new PaperBook();
}
}
public class KindleLibrary implements BookFactory {
@Override
public Book newBook() {
return new KindleBook();
}
}
現(xiàn)在,BookFactory的不同實現(xiàn)屏蔽掉了具體Book的不同,卻提供了通用的newBook的方法。
4.4 依賴注入
依賴注入(也稱為控制反轉(zhuǎn))被類設(shè)計者認為是一種良好的設(shè)計實踐:如果一些類實例依賴其他類的實例,那些被依賴的實例應該通過構(gòu)造方法(或者setter方法、策略等方式)提供(注入),而不應該是由實例自己去創(chuàng)建。先看一下下面的代碼:
package com.javacodegeeks.advanced.construction.patterns;
import java.text.DateFormat;
import java.util.Date;
public class Dependant {
private final DateFormat format = DateFormat.getDateInstance();
public String format( final Date date ) {
return format.format( date );
}
}
Dependant類需要一個DateFormat類的實例并通過在實例化對象時通過DateFormat.getDateInstance()的方式獲得。更好的方式應該通過構(gòu)造方法的參數(shù)來完成同樣的事情:
package com.javacodegeeks.advanced.construction.patterns;
import java.text.DateFormat;
import java.util.Date;
public class Dependant {
private final DateFormat format;
public Dependant( final DateFormat format ) {
this.format = format;
}
public String format( final Date date ) {
return format.format( date );
}
}
在上面的例子中,類實例的所有依賴都由外部提供,這樣就很容易調(diào)整DateFormat,并易于編寫測試代碼。
相關(guān)文章
利用Spring Cloud Zuul實現(xiàn)動態(tài)路由示例代碼
Spring Cloud Zuul路由是微服務架構(gòu)的不可或缺的一部分,提供動態(tài)路由,監(jiān)控,彈性,安全等的邊緣服務。下面這篇文章主要給大家介紹了關(guān)于利用Spring Cloud Zuul實現(xiàn)動態(tài)路由的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09
Java泛型模擬scala實現(xiàn)自定義ArrayList方式
這篇文章主要介紹了Java泛型模擬scala實現(xiàn)自定義ArrayList方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10

