Effective Java -- 避免創建不必要的對象

本文是 《Effective Java Second Edition》第5條的讀書筆記,文中如有錯誤或表述不當,非常歡迎能批評指正,本人不勝感激!


首先我們來看一下一個極端的反面例子

String str=new String("string");

這句話每次運行的時候都會創建一個String實例,由於傳遞給String構造函數的就是一個String實例,所以是完全沒有必要的。

比如,如果將同一個對象插入到HashMap中一千萬次,那麼無疑這種做法是非常耗時耗空間的。

我們推薦的做法如下

//在同一臺JVM中,只要包含相同字符串值,這個對象就會被重用
String str="string";

對於上面說的可能看着還是不直觀,我們給出下面的測試(近一分鐘的測試動圖)
直觀看出new String()的缺點

在執行一千萬次的put操作,使用推薦的方法只要10s,且佔用很少的空間,而使用new String()的這種方法,則耗費大量的時間空間(本機內存12G,佔用了98%)


在我們的項目之中,會有很多的靜態數據是不會被修改的,這時候我們就可以重用它們。

現在有一個需求,就是判斷一個人是不是90後(post-90s:1990-2000之間)。
我們需要一個Person類,在其中提供一個判斷方法isPost90s()

類的基本框架如下,我們將判斷方法isPost90s()單獨拿出來分析

package com.blog.effective5;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * Person類,判斷90後.
 *
 * @author 未緒~.
 * @date 2017/11/02-09:06.
 */
public class Person {
    private final Date birthDate;//Person類有一個出生日期的字段

    public Person(Date birthDate) {//Constructor
        this.birthDate = birthDate;
    }

    public boolean isPost90s() {//判斷是不是 90 後
        return null;
    }
}

首先最直接的實現判斷方法isPost90s()的代碼如下

    public boolean isPost90s() {

        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

        gmtCal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);

        Date post90sStart = gmtCal.getTime();//90後開始時間[包含]

        gmtCal.set(2000, Calendar.JANUARY, 1, 0, 0, 0);

        Date post90sEnd = gmtCal.getTime();//90後結束時間[不包含]
        //compareTo()函數來比較當前Person是不是90後
        return birthDate.compareTo(post90sStart) >= 0 &&
                birthDate.compareTo(post90sEnd) < 0;

    }

在對一個Person實例進行一百萬次的判斷其是不是90後,期相應結果如下

測試代碼

Date s=new Date();
        long time=10000000;
        Person p=new Person(new Date());
        for (long i = 0; i < time; i++) {
            p.isPost90sMethod1();
        }
        Date e=new Date();
        System.out.println((e.getTime() - s.getTime())/1000.0+"s");

上次運行花費時間分別爲:8.145s,8.254s,7.27s

我們觀察代碼就會發現問題
post90sStartpost90sEnd分別對應開始時間和結束時間是已知不會變的,不必要每次調用方法都去創建一個新的。
故可將其放在static域裏面,這樣可以在Person對象創建的時候將其實例化出來,以後直接重用就可以了。

Person類的定義如下代碼

public class Person {

    private final Date birthDate;

    //設置成靜態不可變的數據
    public static final Date BoomStart;
    public static final Date BoomEnd;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    static {//在類實例化的時候創建要被重用的對象
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

        gmtCal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);
        BoomStart = gmtCal.getTime();

        gmtCal.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
        BoomEnd = gmtCal.getTime();

    }

    public boolean isPost90s() {
        //使用compareTo函數進行比較
        return birthDate.compareTo(BoomStart) >= 0 &&
                birthDate.compareTo(BoomEnd) < 0;

    }

}

用同樣的代碼進行測試一百萬次,測試三次,花費時間分別爲:0.066s,0.074s,0.077s

對比以上對應兩段不同代碼的測試,相應的優勢已經非常明顯了(快了一百多倍)。


Java中我們可以將基本類型(如int)和裝箱基本類型(如Integer)進行混用,這操作叫做自動裝箱。

給出建議:我們要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱。

如下代碼測試代碼

    public static void main(String[] args) {

        Date s=new Date();
        long time=10000000;
//        long count=0L;      //運行三次花費時間:14ms 12ms 13ms
        Long count=0L;    //運行三次花費時間:114ms 83ms 79ms
        for (long i = 0; i < time; i++) {
            count++;
        }
        Date e=new Date();
        System.out.println((e.getTime() - s.getTime())+"ms");
    }

[注] 並不是說創建對象花費非常大,我們要儘可能避免創建對象。我們在該創建對象的時候就創建對象,通過創建額外的對象能提升程序的可讀性,簡潔性和功能性,這就是一件好事。


以上 2017-11-02 09:50 於福州大學.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章