Effective Java學習筆記(五) 避免建立沒必要要的對象

本篇筆記對應原書條目6,介紹幾個避免建立沒必要要對象的方法。java

1. 採用更合適的API或工具類減小對象的建立

若是一個方法中的一些對象,每次調用都起到相同的做用,那麼就能夠被重用。程序員

好比,咱們不該該用以下的方式來建立一個String對象:數據庫

String str = new String("aaa");

由於當咱們往構造方法裏傳入aaa的時候,其實這個aaa就是一個String實例了。咱們等因而建立了兩個String實例。數組

咱們應該直接這麼寫:安全

String str = "aaa";

根據jdk文檔,上述方式實際上等同於:app

char data[] = {'a', 'a', 'a'};
String str = new String(data);

傳入一個字符數組來建立String,避免了建立重複對象。工具

再舉一個常見的例子[2],咱們有時但願遍歷一個list,將其中的元素存到一個字符串裏,並用逗號分隔。咱們可能會用下面這種最low的辦法:性能

public static String listToString(List<String> list) {
    String str = "";
    for (int i = 0; i < list.size(); i++) {
        str += list.get(i);
        if (i < list.size() - 1) {
            str += ",";
        }
    }
    return str;
}

這樣其實在每次+=的時候都會從新建立String對象,極大地影響了性能。學習

咱們能夠修改一下,採用StringBuilder的方式來拼接list:優化

public static String listToString(List<String> list) {
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < list.size(); i++) {
        stringBuilder.append(list.get(i));
        if (i < list.size() - 1) {
            stringBuilder.append(",");
        }
    }
    return stringBuilder.toString();
}

這種方式每次只會生成兩個實例——StringBuilder和最後返回的String

那有沒有更好的方法呢?咱們能夠採用Google Guava的Joiner,這樣每次只用生成一個實例,以下所示:

public static String listToString(List<String> list) {
    return Joiner.on(",).join(list);
}

2. 重用相同功能的對象

有時候咱們提供的API中有一些每次調用都具有相同功能的對象,那麼就能夠把這些對象變成靜態的不可變對象,只需實例化一次便可。好比下面這個相似書中的例子[1]:

public static boolean isNumeral(String s) {
    return s.matches("^[0-9]*$");
}

這個例子用String.matches()方法來判斷字符串是否爲數字。每次調用matches()方法,裏面都會建立一個Pattern對象,這會對性能形成影響。

由於每次調用isNumeral實際上都會生成一個功能徹底相同的Pattern對象,因此咱們能夠把它抽出來,變成一個靜態不可變對象,以下所示:

public static final Pattern NUMBER = Pattern.compile("^[0-9]*$");

public static boolean isNumeral(String s) {
    return NUMBER.matcher(s).matches();
}

上面咱們談到了一個不可變對象的重用,接下來咱們再看看可變對象的重用。可變對象的重用能夠經過視圖(views)來實現。好比,Map的keySet()方法就會返回Map對象全部key的Set視圖。這個視圖是可變的,可是當Map對象不變時,在任何地方返回的任何一個keySet都是同樣的,當Map對象改變時,全部的keySet也會相應的發生改變。[1]

3. 當心自動裝箱(auto boxing)

自動裝箱容許程序員混用基本類型和包裝類型[1],在二者相計算時,程序會構造出基本類型的包裝類型實例。例如,咱們看書中的這個例子:

private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) 
        sum += i;
    return sum;    
}

這個例子裏的sum += i處用Long型和long型相加,這樣每次都會實例化一個值爲i的包裝類型,一共實例化了約231個沒必要要的實例,極大地下降了系統的性能。因此咱們在平常開發中,方法內儘可能用基本類型,只在入出參的地方用包裝類型。多留心,切忌無心識地使用到自動裝箱。[1]

其餘

若是涉及到對象池的應用,除非池中的對象很是重,相似數據庫鏈接,不然最好不要去本身維護一個對象池,由於這樣會很複雜。另外,有時考慮到系統的安全性,那麼咱們須要進行防護性複製,這個在後面會講到。此時,重複建立對象就是有意義的,由於比起隱含錯誤和安全漏洞,重複建立對象帶來的性能損失是能夠接受的。[1]

總結

這個條目要求咱們平時多審視咱們的代碼,在能不重複建立對象的地方,就不要重複建立。不要無腦寫代碼,而是要多留點心。這是一種很是好的編碼習慣,也能夠爲系統帶來性能上的優點。

聲明

本文僅用於學習交流,請勿用於商業用途。轉載請註明出處,若是涉及任何版權問題,請及時與我聯繫,謝謝!

參考資料

  1. 《Effective Java(第3版)》
  2. Java之避免建立沒必要要的對象 https://www.jianshu.com/p/420...
  3. java代碼優化——避免建立沒必要要的對象 https://www.jianshu.com/p/83a...
相關文章
相關標籤/搜索