Java進階1 —— 對象的建立與銷燬

原文連接:http://www.javacodegeeks.com/2015/09/how-to-create-and-destroy-objects.htmlphp

本文是Java進階課程的第一部分。html

本課程的目標是幫你更有效的使用Java。其中討論了一些高級主題,包括對象的建立、併發、序列化、反射以及其餘高級特性。本課程將爲你的精通Java的旅程提供指導。java

內容提綱

  1. 引言程序員

  2. 實例構造
    2.1 隱式(產生的)構造方法
    2.2 無參構造方法
    2.3 有參構造方法
    2.4 初始化代碼塊
    2.5 保證構造默認值
    2.6 可見性
    2.7 垃圾回收
    2.8 對象終結器編程

  3. 靜態初始化設計模式

  4. 構造器模式
    4.1 單例模式
    4.2 工具類/輔助類
    4.3 工廠模式
    4.4 依賴注入安全

  5. 源碼下載多線程

  6. 下章概要併發

1. 引言

TIOBE 編程語言排名中,Sun 公司於1995年開發的Java語言是世界上使用最普遍的編程語言之一。做爲一種通用編程語言,由於強大的工具包和運行時環境、簡單的語法、豐富的平臺支持(一次編寫,處處運行)以及的異常活躍的社區支持,Java語言對軟件開發工程師極具吸引力。編程語言

在這一系列的文章中,涵蓋了Java相關的高級內容,所以假設讀者已具備基本語言知識。這並非一個完整的參考手冊,而是讓你的技能更上一層樓的詳盡指南。

本課程中包含了大量的代碼片斷,在有些對方爲了作對比,會同時提供Java 7和Java 8的示例。

2. 實例構造

做爲一種面嚮對象語言,對象的建立也許就是Java語言中最重要的概念之一。構造方法是在對象實例初始化過程當中具備舉足輕重的地位,而且Java提供了多種方式來定義構造方法。

2.1 隱式(產生的)構造方法

Java容許在定義類時不聲明任何的構造方法,並這並不表明類沒有構造方法。咱們看下面類的定義:

package com.javacodegeeks.advanced.construction;

public class NoConstructor {
}

這個類未定義構造方法,可是Java編譯器會爲其隱式生成一個,從而使咱們可使用new關鍵字來建立新的對象實例。

final NoConstructor noConstructorInstance = new NoConstructor();

2.2 無參構造方法

無參構造方法是最簡單的經過顯式聲明來替代Java編譯生成構造方法的方式。

package com.javacodegeeks.advanced.construction;

public class NoArgConstructor {
    public NoArgConstructor() {
        // Constructor body here
    }
}

在使用new關鍵字建立新的對象實例時,上面的構造方法就會被調用。

2.3 有參構造方法

有參構造方法最有意思而且普遍使用,經過指定參數來定製新實例的建立。下面的例子中定義了一個有兩個參數的構造方法。

package com.javacodegeeks.advanced.construction;

public class ConstructorWithArguments {
    public ConstructorWithArguments(final String arg1,final String arg2) {
        // Constructor body here
    }
}

這種場景中,當使用new關鍵字來建立實例時,須要同時提供構造方法上定義的兩個參數。

final ConstructorWithArguments constructorWithArguments = 
    new ConstructorWithArguments( "arg1", "arg2" );

有趣的是構造方法之間能夠經過this關鍵字互相調用。在實踐中,推薦經過使用this把多個構造方法鏈起來以減小代碼重複,並從基礎上使對象具備單一的初始化入口。做爲示例,下面的代碼中定義了只有一個參數的構造方法。

public ConstructorWithArguments(final String arg1) {
    this(arg1, null);
}

2.4 初始化代碼塊

除了構造方法,Java還提供了經過初始化代碼塊進行初始化的邏輯。這種用法雖然少見,但多瞭解一些也沒害處。

package com.javacodegeeks.advanced.construction;

public class InitializationBlock {
    {
        // initialization code here
    }
}

另外一方面,初始化代碼塊也可被看做是無參的隱式構造方法。在一個具體的類中能夠定義多個初始化代碼塊,在執行的時候按照他們在代碼中的位置順序被調用,以下面的代碼所示:

package com.javacodegeeks.advanced.construction;

public class InitializationBlocks {
    {
        // initialization code here
    }

    {
        // initialization code here
    }

}

實始化代碼塊並非爲了取代構造方法,相反它們能夠同時出現。可是要記住,初始化代碼快會在構造方法調用以前被執行。

package com.javacodegeeks.advanced.construction;

public class InitializationBlockAndConstructor {
    {
        // initialization code here
    }
    
    public InitializationBlockAndConstructor() {
    }
}

2.5 保證構造默認值

Java提供了肯定的初始化保證,程序員能夠直接使用初始化結果。未初始化的實例以及類變量(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關鍵字實例化對象以後:

final InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults(),

可從控制檯中看到輸出結果以下:

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

2.6 可見性

構造方法聽從Java的可見性規則,而且能夠經過訪問控制修飾符決定在其餘類中是否能調用該構造方法。

修飾符 包可見性 子類可見性 公開可見性
public 可見 可見 可見
protected 可見 可見 不可見
<無修飾符> 可見 不可見 不可見
private 不可見 不可見 不可見

表2

2.7 垃圾回收

Java(準確的說是JVM)擁有自動的垃圾回收機制。簡單來說,當有新對象建立時,會自動爲其分配內在;而後當對象再也不被引用後,他們會被自動銷燬,相應的內存也會被回收。

Java垃圾回收採用分代回收的機制,並基於"大多數對象生命短暫"的假設(即在對象建立以後很快就不會被再引用,因此能夠被安全的銷燬)。大多程序員習慣性的認爲Java中對象建立的效率很低因此要儘量避免新對象的建立。事實上,這種認識是不對的。在Java中建立對象的開銷是至關低的,而且速度很快。真正代來巨大開銷的是沒必要要的長期存活的對象,所以他們最終會被遷移到老年代,並致使stop-the-world發生。

2.8 對象終結器(Finalizers)

前面咱們講述的都是構造方法和對象初始化相關的主題,但還未說起他們的反面:對象銷燬。主要是由於Java使用垃圾回收機制來管理對象的生命週期,因此銷燬沒必要要的對象並釋放所需內存就成了垃圾回收的職責了。

不過,Java仍是提供了另一種相似於析構函數的終結器(finalizer)的特性,擔任多種資源清理的責任。Finalizer通常被看做是危險的事情(由於它會帶來多種反作用和性能問題)。一般並不須要finalizer所以要儘可能避免使用它(除了極少見的包含大量本地對象(native objects)的場景)。Java 7中引入的try-with-resources語法和AutoCloseable接口可看成finalizer的替代選擇,並可寫出以下簡潔的代碼:

try ( final InputStream in = Files.newInputStream( path ) ) {
    // code here
}

3. 靜態初始化

上面咱們學習了類實例的構造與初始化,除此以外,Java還支持類級別的初始化構造,稱做靜態初始化。靜態初始化與上面介紹的初始化代碼塊相似,只是多了額外的static關鍵字修飾。須要注意的是靜態初始化只會在類加載時執行一次。示例以下:

package com.javacodegeeks.advanced.construction;

public class StaticInitializationBlock {
    static {
        // static initialization code here
    }
}

與初始化代碼塊相似,能夠在類中定義多個靜態初始化塊,它們在類中的位置決定在初始化時執行的順序。示例以下;

package com.javacodegeeks.advanced.construction;

public class StaticInitializationBlocks {
    static {
        // static initialization code here
    }

    static {
        // static initialization code here
    }
}

由於靜態初始化塊能夠被多個並行執行的線程觸發(當類被初始加載時),JVM運行時保證初始化的代碼以線程安全的方式只被執行一次。

4. 構造器模式

這些年多種容易理解的構造器(建立者)模式被引入到Java社區。下面咱們會學習其中比較流行的幾個:單例模式、輔助類模式、工廠模式以及依賴注入(也稱爲控制反轉)。

4.1 單例模式

單例是一種歷史悠久卻在軟件開發社區中飽受爭議的模式。單例模式的核心理念是保證在任什麼時候候給定的類只有一個對象被建立。雖然聽起來很簡單,但人們對如何以正確且線程安全的方式建立對象進行了大量的討論。下面的代碼中展現了簡單版本的單例模式實現:

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;
    }
}

上面的代碼至少有一個問題:在多線程併發場景中可能會建立出多個對象。一種合理的實現方式(但不能延遲加載)是使用類的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;
    }
}

若是你不想浪費寶貴的資源,但願單例對象只在真正須要的時候才被建立,那麼就要使用顯式的同步方式,不可這種方法可能會下降多線程環境下的併發性(更多關於Java併發的細節將會在Java進階9-併發最佳實踐中詳細介紹)。

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;
    }
}

如今,在不少場景下單例模式再也不被認爲是一種好的選擇,由於他們會使代碼不易於測試。另外依賴注入模式的產生也使單例模式變得再也不必要。

4.2 工具類/輔助類

工具類/輔助類模式在Java開發者當中至關流行。它的核心理念就是使用不可實例化的類(經過聲明private構造方法)、可選的final(更多關於聲明final類的細節將會在Java進階3-類和接口的設計中詳細介紹)關鍵字以及靜態方法。示例以下:

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
    }
}

不少經驗豐富的開發者認爲這種模式會讓工具類成爲各類不相關方法的容器。由於有些方法沒有合適的放置位置卻須要被其餘類使用,就會被誤放入工具類中。在大多數場景中也應該避免這種設計:總會有更好的功能複用的方式,保持代碼清晰簡潔。

4.3 工廠模式

工廠模式被證實是開發者的極其強大的利器,在Java中有多種實現方式:工廠方法抽象工廠。最簡單的例子就是使用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 );
    }
}

雖然使用這種方法能提升代碼的可讀性,但常常爭議的一點是難以給newBook工廠方法賦予更豐富的場景。另一種實現工廠模式的方法是採用接口或抽象類(抽象工廠)。以下,咱們定義一個工廠接口

public interface BookFactory {
    Book newBook();
}

根據圖片館的不一樣,咱們能夠有多種不一樣的newBook實現:

public class Library implements BookFactory {
    @Override
    public Book newBook() {
        return new PaperBook();
    }
}

public class KindleLibrary implements BookFactory {
    @Override
    public Book newBook() {
        return new KindleBook();
    }
}

如今,BookFactory的不一樣實現屏蔽掉了具體Book的不一樣,卻提供了通用的newBook的方法。

4.4 依賴注入

依賴注入(也稱爲控制反轉)被類設計者認爲是一種良好的設計實踐:若是一些類實例依賴其餘類的實例,那些被依賴的實例應該經過構造方法(或者setter方法、策略等方式)提供(注入),而不該該是由實例本身去建立。先看一下下面的代碼:

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()的方式得到。更好的方式應該經過構造方法的參數來完成一樣的事情:

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 );
    }
}

在上面的例子中,類實例的全部依賴都由外部提供,這樣就很容易調整DateFormat,並易於編寫測試代碼。

5. 源碼下載

能夠從這裏下載本文中的源碼:com.javacodegeeks.advanced.java

6. 下章概要

在本章中咱們學習了類及類實例的構造和初始化技術,以及相關的幾種設計模式。下一章中將對Object類作深刻分析,並介紹其中的幾個重要方法的用法:equals, hashcCode, toStringclone

相關文章
相關標籤/搜索