Guava 是个风火轮之基础工具(4)
前言
Guava 是 Java 開發者的好朋友。雖然我在開發中使用 Guava 很長時間了,Guava API 的身影遍及我寫的生產代碼的每個角落,但是我用到的功能只是 Guava 的功能集中一個少的可憐的真子集,更別說我一直沒有時間認真的去挖掘 Guava 的功能,沒有時間去學習 Guava 的實現。直到最近,我開始閱讀Getting Started with Google Guava,感覺有必要將我學習和使用 Guava 的一些東西記錄下來。
Preconditions
Precondition 是先決條件的意思,也叫前置條件,可以人為是使函數正常執行的參數需要滿足的條件。在 Preconditions 這個靜態工廠中,Guava 為我們提供了一系列的靜態方法,用于幫助我們在函數執行的開始檢查參數,函數執行的過程中檢查狀態等等。
源碼分析
源碼來自 Guava 18.0。Preconditions 類代碼約 440 行,大部分是 JavaDoc 和函數重載,那些真正干活的代碼大部分也是先 if 然后 throw 的模式。
大約在 255 行處有一大段的注釋,講了一個有趣的事情。
大概從 2009 年開始,由于 Hotspot 虛擬機優化器的一個 bug,對于拋異常的代碼,直接在初始化異常時傳入字符串常量反而導致效率低下,效率遠遠不如在初始化前調用一個類型是 String 的函數來獲取字符串,而且這個性能差距不是 10% 或者 20%,而是可怕的 2 倍到 8 倍。于是我們看到的 JDK 類庫的拋異常代碼,就從
if (guardExpression) {throw new BadException(badMsg(...)); }
Objects
我們在定義一個類的時候,免不了會去覆蓋 toString 方法;如果要把這個類的對象放到 HashMap 中,還得去覆蓋 hashCode 方法;如果對象之間需要比較大小,那么還得實現 Comparable 接口的 compareTo 方法。
Guava 為我們提供了方便的實現這些方法的工具。雖然優秀的 IDE 比如 IntelliJ IDEA 能夠自動幫我們生成 toString 和 hashCode,但是依賴代碼生成器始終不是一個科學的開發方式。
需要說明的一點是,Objects 類中用于幫助實現 toString 方法的內部類 ToStringHelper,已經被標記為過時,在 Guava 18.0 中遷移到 MoreObjects 中了,而用于幫助實現 compareTo 的則是 ComparisonChain 類,稍后會解讀這個類的用法和代碼。
現在的 Objects 中碩果僅存的兩個函數,分別是 Objects#equal 和 Objects#hashCode,分別用于判斷兩個對象是否相等,和生成對象的 hashCode。
源碼分析
源碼來自 Guava 18.0。Objects 類代碼約 320 行,刨除過時代碼之后,也沒剩幾行了。
碩果僅存的兩個函數,實現比想象中還簡單。
public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result; }
MoreObjects
MoreObjects 是從 18.0 版本開始出現的一個新類,從 Objects 中分裂出來的,主要剝離了內部類 ToStringHelper 以及一系列的包裝函數。
至于那個順便一起遷移過來的 MoreObjects#firstNonNull 函數,功能和實現都過分簡單,這里就不展開了,有興趣的可以查看源碼。
下面是 ToStringHelper 的簡單用法,通過調用 ToStringHelper#omitNullValues 來配置 ToStringHelper 使得生成的字符串中不含 null 值。
源碼分析
源碼來自 Guava 18.0。MoreObjects 類代碼約 390 行,甚至比 Objects 還要多。其中 ToStringHelper 代碼約 240 行,這里我們主要看看 ToStringHelper 的實現。
從 ToStringHelper 的屬性可以看出,它內部維護著一個鏈表。
private ValueHolder addHolder() {ValueHolder valueHolder = new ValueHolder();holderTail = holderTail.next = valueHolder;return valueHolder; } private ToStringHelper addHolder(String name, @Nullable Object value) {ValueHolder valueHolder = addHolder();valueHolder.value = value;valueHolder.name = checkNotNull(name);return this; }最后的最后,ToStringHelper#toString 就是遍歷對象內部維護的鏈表,拼接字符串了。說道字符串拼接,之前在Guava 是個風火輪之基礎工具(1)中,我們看到 Joiner 使用 if 和 while 來實現了比較優雅的分隔符拼接,避免了在末尾插入分隔符的尷尬。在這里,Guava 的作者展示了另一個技巧,用更少的代碼實現同樣的效果。
@Override public String toString() {// create a copy to keep it consistent in case value changesboolean omitNullValuesSnapshot = omitNullValues;String nextSeparator = "";StringBuilder builder = new StringBuilder(32).append(className).append('{');for (ValueHolder valueHolder = holderHead.next; valueHolder != null;valueHolder = valueHolder.next) {if (!omitNullValuesSnapshot || valueHolder.value != null) {builder.append(nextSeparator);nextSeparator = ", ";if (valueHolder.name != null) {builder.append(valueHolder.name).append('=');}builder.append(valueHolder.value);}}return builder.append('}').toString(); }
一開始的時候,先把分隔符置為空字符串,完成分隔符拼接之后,將分隔符置為逗號,這樣就實現了從第二個元素開始,每個元素前面拼接分隔符的效果。這樣子就不用去判斷當前元素是不是第一個元素,代價僅僅是每次循環多出一次冗余的賦值,完全可以忽略不計。
ComparisonChain
ComparisonChain 可以幫助我們優雅地實現具有短回路功能鏈式比較,然后我們可以借助 ComparisonChain 來實現 compareTo 方法。先看看這個類的用法。
美中不足的是,比較鏈的參數,基本不能有空指針,不然當場就 NPE 了。雖然我們可以通過自定義比較器去兼容空指針,但是這樣一來代碼就變得一點都不優雅了。
源碼分析
帶著對 ComparisonChain 空指針處理不力的不滿,我們來看看它的實現,如果可能就動手實現我們需要的特性。
源碼來自 Guava 18.0。ComparisonChain 類代碼約 220 行,大部分是注釋和 ComparisonChain#compare 函數的各種重載。看到 ComparisonChain 是一個抽象類,各種 ComparisonChain#compare 都是虛函數,返回結果的 ComparisonChain#result 也是虛函數,我以為有希望繼承它然后做些改造。不過看到代碼里那個私有的構造函數之后,我打消了繼承它的念頭。
ComparisonChain 內部維護著 3 個 ComparisonChain 類型的變量,ACTIVE、LESS、GREATER,容易知道這代表著鏈式比較的狀態,ACTIVE 還需要繼續比較,其他兩個則是已經知道最終結果了。
LESS 和 GREATER 狀態其實是 InactiveComparisonChain 類的對象,這個類內部有一個屬性維護比較鏈的結果,然后各種 compare 函數都是直接返回 this 指針,著就是所謂的短回路了,能夠避免調用被比較對象的 compareTo 函數。
最后,我對 ComparisonChain 稍作改動,增強了它對空指針的容忍,可以通過 ComparisonChain#nullValueLess 來設置 null 字段在比較的時候小于非 null 字段,訪問?Gist查看代碼片段。
總結
以上是生活随笔為你收集整理的Guava 是个风火轮之基础工具(4)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 时序列数据库武斗大会之 OpenTSDB
- 下一篇: 多线程“基础篇”11之 生产消费者问题