建議:避免建立不必要的物件。

孤芳不自賞發表於2017-08-17

        一般來說,最好能重用物件而不是在每次需要的時候就建立一個相同功能的新物件。重用方式既快速,又流行。如果物件時不可變的(immutable),它就始終可以被重用。

        作為一個極端的反面例子,考慮下面的語句:

        String s = new String("stringette"); // DON'T DO THIS!

        該語句每次被執行的時候都建立一個新的String例項,但是這些建立物件的動作全都是不必要的。傳遞給String構造器的引數("stringete")本身就是一個String例項,功能方面等同於構造器建立的所有物件。如果這種用法是在一個迴圈中,或者是在一個被頻繁呼叫的方法中,就會建立出成千上萬不必要的String例項。

        改進後的版本如下所示:

        String s = "stringette";

        這個版本只用了一個String例項,而不是每次執行的時候都建立一個新的例項。而且,它可以保證,對於所有在同一臺虛擬機器中執行的程式碼,只要他們包含相同的字串字面常量,該物件就會被重用。

        對於同時提供了靜態工廠方法和構造器的不可變類,通常可以使用靜態工廠方法而不是構造器,以避免建立不必要的物件。例如,靜態工廠方法Boolean.valueOf(String)幾乎總是優先於構造器Boolean(String)。構造器在每次被呼叫的時候都會建立一個新的物件,而靜態工廠方法則從來不要求這樣做,實際上也不會這樣做。

        除了重用不可變的物件外,也可以重用哪些已知不會被修改的可變物件。

        注意,在提倡使用保護性拷貝的時候,因重用物件而付出的代價要遠遠大於因建立重複物件而付出的代價。必要時如果沒能實施保護性拷貝,將會導致潛在的錯誤和安全漏洞,而不必要的建立物件則只會影響程式的風格和效能。

示例程式碼:

反例

public class Person {

  private final Date birthDate;

  // Other fields,methods,and constructor omitted

  // DON't DO THIS!

  public boolean isBabyBoomer() {
    // Unnecessary allocation of expensive object

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

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

     Date boomStart = gmtCal.getTime();

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

     Date boomEnd = gmtCal.getTime();

     return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;

  }

  isBabyBoomer每次被呼叫的時候,都會新建立一個Calendar、一個TimeZone和兩個Date例項,這是不必要的。下面的版本用一個靜態的初始化器(initializer),避免了這種效率低下的情況:

class Person {

  private final Date birthDate;

  // Other fields,methods,and constructor comitted

  / **

    * The starting and ending dates of the baby boom.

    */

  private static final Date BOOM_START;

  private static final Date BOOM_END;

 

  static {

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

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

    BOOM_START = gmtCal.getTime();

    gtmCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);

     BOOM_END = gmtCal.getTime();

  }


   public boolean isBabyBoomer() {

      return birthDate.compareTo(BOOM_START) >= 0 && birthDate.comparte(Boom_END) < 0;

    }

}

    改進後的Person類只在初始化建立的時候建立Calendar、TimeZone和Date例項一次,而不是在每次呼叫isBabyBoomer的時候都建立這些例項。如果isBabyBoomer方法被頻繁的呼叫,這種方法將會顯著提高效能。在我的機器上,每呼叫一千萬次,原來的版本需要32 000ms,而改進後的版本只需要130ms,大約快了250倍。除了提高效能外,程式碼的含義也更加清晰了。把boomStart和boomEnd從區域性變數改為final靜態域,這些日期顯然是被作為常量對待,從而使得程式碼更易於理解。但是,這種優化帶來的效果並不總是那麼明顯,因為Calendar例項的建立代價特別昂貴。

相關文章