本文是 《Effective Java Second Edition》第5條的讀書筆記,文中如有錯誤或表述不當,非常歡迎能批評指正,本人不勝感激!
首先我們來看一下一個極端的反面例子
String str=new String("string");
這句話每次運行的時候都會創建一個String
實例,由於傳遞給String
構造函數的就是一個String
實例,所以是完全沒有必要的。
比如,如果將同一個對象插入到HashMap
中一千萬次,那麼無疑這種做法是非常耗時耗空間的。
我們推薦的做法如下
//在同一臺JVM中,只要包含相同字符串值,這個對象就會被重用
String str="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
我們觀察代碼就會發現問題
post90sStart
和post90sEnd
分別對應開始時間和結束時間是已知不會變的,不必要每次調用方法都去創建一個新的。
故可將其放在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 於福州大學.