【jdk源碼2】Objects源碼學習

  在學習上一個類TreeMap的時候,提到了這個類,這個類是jdk1.7新增的,裏面有不少實用的方法。就是一個工具類,熟悉之後,若是裏面有已經實現的方法,那麼就不要再去實現了,省時省力省測試。java

 

1、簡單理解

  這是一個工具類,介紹相對會簡單些,基本都是方法的介紹。編程

 1.1 類名的命名

  Objects是一個主要針對對象的工具類,因此它的命名只是在後面加上一個s,就像Arrays是操做數組的工具類同樣。這就涉及到一種設計理念,那就是工具方法應該放在哪:數組

  • 放在使用的類裏
  • 按操做屬性進行歸類,如StringUtils,FileUtils,MapUtils等等
  • 所有放到一個類裏,Utils

  第一種不太建議,既然都是一個工具類,那麼就應該拿出來給別人用,或者本身之後用。less

  第二種和第三種我比較不出來,我用的是第三種,創建一個大而全的Utils類,固然它們之間仍是能夠比較的:jvm

  • 多個工具類:使用的時候須要想在哪一個裏面,優勢就是能夠按照方法所操做進行分組,可是有時候有的工具類就是不知道怎麼分組
  • 一個工具類:一個缺點就是方法命名必定要好,畢竟方法多了很差找,優勢就是工具類好找,並且方法能夠重載,好比判斷是否爲空,就能夠重載Map、Collection、String等。

1.2 工具類的特性

  先看下面一部分代碼:ide

public final class Objects {
    private Objects() {
        throw new AssertionError("No java.util.Objects instances for you!");
    }
}

 

  由於是工具類加上final不讓別人繼承,還有就是構造方法加上私有並拋異常。工具

  說實話我以爲沒有必要這麼作,或許他們考慮的比較多,可是這麼作也沒什麼壞處,之後我寫工具類也這樣,顯得高大上一些。學習

1.3 對象equals比較

  Objects裏面提供兩種比較方式:測試

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
public static boolean deepEquals(Object a, Object b) {
    if (a == b)
        return true;
    else if (a == null || b == null)
        return false;
    else
        return Arrays.deepEquals0(a, b);
}

  其中咱們廣泛使用的就是equals,而後我看deepEquals的源碼的時候,發現了一個以前忽略的事而後我作了測試:ui

int[] a1=new int[]{1};
int[] a2=new int[]{1};
System.out.println(a1.equals(a2));

  結果是false,也就是說由於數組(以數字爲下標,不表明裏面的內容是數字)的equals都是false,除非它們是同一個對象。也就是說咱們之後若是要作兩個對象的相等判斷,若是比較的是兩個數組那麼記得使用Objects的deepEquals方法。

1.4 對null值的簡單包裝

  你們都知道在null值對象上調用任何方法都會報空指針異常,在一些須要判斷對象是不是null的狀況下,拋異常是有必要的,可是當能夠容許null值的時候,不少Object經常使用的方法就須要額外的處理,而Objects就幫咱們作了:

public static String toString(Object o) {
    return String.valueOf(o);
}
public static int hash(Object... values) {
    return Arrays.hashCode(values);
}
public static String toString(Object o, String nullDefault) {
    return (o != null) ? o.toString() : nullDefault;
}

  因此使用起來很方便,可是必定要注意使用場景,保證null值是有效值的時候使用。

1.5 null值檢查須要本身作嗎

  不少時候,咱們須要保證入參的正確性,最簡單的就是傳入的不能是null,若是說上層須要將錯誤緣由展現給用戶看,咱們就須要主動檢查是否爲null,而且主動返回null值所表明的信息(好比說用戶名不能爲空啦,郵箱不能爲空啦),而不是拋異常。

  可是當不須要給用戶看的時候,那就能夠直接將異常拋出來就能夠了,這就有兩種策略:

  • 主動檢查是否爲null,並拋異常
  • 被動檢查,等待程序調用null上面的方法時拋異常

  如今大多數人都不多主動去檢查null值,我以爲這是很差的編程習慣,我認爲應該在方法入口的時候主動檢查null值並拋異常,緣由以下:

  • 早點拋異常,在查看堆棧信息的時候,能更快速的定位異常緣由
  • 早點拋異常還可以,再也不執行額外的代碼,避免執行大部分代碼之後才拋出異常

  因此建議之後寫代碼的時候多多主動檢查,使用

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static <T> T requireNonNull(T obj, String message) {
    if (obj == null)
        throw new NullPointerException(message);
    return obj;
}

  上面說的好處你會慢慢體會到的。

1.6 延遲消息的好處

  如今先看下面兩個方法,一樣是判斷null值的:

public static <T> T requireNonNull(T obj, String message) {
    if (obj == null)
        throw new NullPointerException(message);
    return obj;
}
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
    if (obj == null)
        throw new NullPointerException(messageSupplier.get());
    return obj;
}

  傳入的message是在當參數是null值的時候傳入給異常使用的,可是爲何會有兩個方法呢,並且第二個看起來遠遠比第一個麻煩,咱們先看怎麼使用:

public void test1(String param){
  Objects.requireNonNull(param, "param can not be null");
  //do something about param
}

  上面是方法一調用的方式,很簡潔明瞭,下面是方法二的調用方式:

public void test2(String param){
  Objects.requireNonNull(param, new Supplier<String>() {
    @Override
    public String get() {
      return "param can not be null";
    }
  });
  //do something about param
}
public void test3(String param){
  Objects.requireNonNull(param, ()->"param can not be null");
  //do something about param
}

  有兩種方式,第二種是jdk8的lambada表達式,若是不瞭解的話,但願抽時間學習一下仍是有必要的。

  能夠看到延遲消息的調用要比普通消息複雜,可是爲何還要推薦這種方式呢?再舉一個例子,之前我看過以下記錄日誌的方式:

if(log.isDebugEnabled()){
  log.debug("read file sucess, read info is :"+properties);
}

  當時我對這種代碼嗤之以鼻,我以爲明明log.debug在裏面已經判斷日誌級別了,爲何在外面還要在判斷一次,如今想一想當時仍是太年輕。若是你仔細想一想或許能發現,若是咱們提早判斷日誌級別那麼,當不符合日誌級別的時候咱們就不須要拼接字符串,而若是咱們直接用:

log.debug("read file sucess, read info is :"+properties);

  用上面代碼的話,咱們就須要每次都要拼接完字符串後再進行判斷,而debug日誌基本上量都特別大,並且基本不會打印,這也就形成了字符串拼接過多,而字符串會放到常量池裏。

  因此簡單總結下延遲消息的好處,那就是在須要的時候才進行拼接,這個在log4j2中已經提供相應的支持,可是這個好處到底有多少,我如今還不清楚。

2、問題及總結

  這個類是一個工具類,不少方法用起來都很方便,若是用的好的話,應該能解決程序中出現的大部分NullPointerException,因此呢之後對經常使用的方法要作到封裝複用。

2.1 遺留問題

  大體看來遺留一個未深刻研究的問題

2.1.1 延遲消息能高效在哪

  上面1.6講解了延遲消息,我只知道延遲消息有好處,雖然jdk註釋中也明說了:

the costs of creating the message supplier are less than the cost of just creating the string message directly

  建立延遲消息要比直接建立字符串高效,可是能高效多少呢,這是一個疑問,固然這也牽扯到jvm對字符串的處理。我會抽時間作以下深刻測試和研究:

  • jvm中的常量池
  • jvm對常量池如何回收
  • 字符串是否會致使內存泄漏

  固然研究和測試只是爲了驗證論點,這不能成爲不用這個技術的理由,之後的使用場景我仍是會使用延遲消息的。

相關文章
相關標籤/搜索