Google Guava與基本工具操作相關的類

Guava項目包含一些我們在基於Java的項目中依賴的Google核心庫:集合[collections]、緩存[caching]、原生類型支持[primitives support]、併發庫[concurrency libraries]、通用註解[common annotations]、字符串處理[string processing]、I/O等。這些工具中的每一種確實每天都會被Google員工用於生產服務中。

但是,通過Javadoc並不總是最有效的去學習如何充分使用庫。在這裏,我們嘗試提供一些Guava最流行和最強大的功能的可讀且令人愉快的解釋說明。

該Wiki正在開發中,部分內容可能仍在建設中。

基本工具:讓使用Java語言更加愉快。

  • 使用和避免使用nullnull可能會造成歧義,導致混亂的錯誤,有時甚至是令人不快的。許多Guava實用工具拒絕null並快速失敗,而不是盲目地接受它們。
  • 前置條件:更輕鬆地測試方法的前置條件。
  • 通用對象方法:簡化實現Object的方法,例如hashCode()toString()
  • 排序:Guava功能強大的"流利Comparator"類。Guava’s powerful "fluent Comparator" class.
  • 異常:簡化了異常和錯誤的傳播與檢查。

1.使用和避免使用null

1.1Optional

程序員使用null的許多情況是表示某種缺席:也許在可能有值、無值或找不到值的地方。例如,當找不到鍵值時,Map.get返回null

Optional<T>是用非空值替換可空的T引用的一種方法。Optional可以包含非空T引用(在這種情況下,我們稱該引用爲“存在”),也可以不包含任何內容(在這種情況下,我們稱該引用爲“不存在”)。它從來不說“包含null”。

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

Optional打算直接模擬其它編程環境中的任何現有“option”或“maybe”構造,儘管它可能有一些相似之處。

我們在這裏列出了一些最常見的Optional操作。

1.1.1創建Optional

這些都是Optional上的靜態方法。

方法 描述
Optional.of(T) 使Optional包含給定的非null值,或者在null上快速失敗。
Optional.absent() 返回某種引用缺失的Optional。
Optional.fromNullable(T) 將給定的可能爲null的引用轉換爲Optional,將non-null視爲存在,將null視爲不存在。

1.1.2查詢方法

這些都是基於特定Optional<T>值的非靜態方法。

方法 描述
boolean isPresent() 如果此Optional包含非空實例,則返回true。
T get() 返回包含的T實例,該實例必須存在;否則,拋出IllegalStateException。
T or(T) 返回此Optional中的當前值,如果沒有,則返回指定的默認值。
T orNull() 返回此Optional中的當前值,如果沒有,則返回null。fromNullable的逆運算。
Set asSet() 如果存在實例,則返回Optional所包含此實例的不可變單例Set,否則返回一個空的不可變Set。

Optional提供了除這些方法之外的更多便捷實用方法。有關詳細信息,請查閱Javadoc。

1.1.3重點是什麼?

除了爲null命名而提高了可讀性之外,Optional的最大優點是它的防白癡。如果你要完全編譯程序,它會迫使你積極考慮不存在的情況,因爲你必須主動打開Optional並解決該情況。令人不安的是,Null很容易讓人忘記一些事情,儘管FindBugs有所幫助,但我們認爲它沒有很好的解決問題。

當你返回的值可能存在或不存在時,這一點尤其重要。你(和其他人)更可能忘記在實現other.method時,other.method(a, b)可能返回null值,而你可能忘記了a可能爲null。返回Optional使調用者無法忘記這種情況,因爲調用者必須自己打開對象去編譯代碼。

1.2便利方法

每當你希望將null值替換爲某些默認值時,請使用MoreObjects.firstNonNull(T, T)。就像方法名稱所暗示的那樣,如果兩個輸入都爲null,則它會由於NullPointerException而快速失敗。如果你使用的是Optional,則有更好的選擇——例如,first.or(second)

字符串中提供了一些處理可能爲空的字符串值的方法。具體來說,我們提供恰當的名稱:

我們想強調的是,這些方法主要用於與使null字符串和空字符串等同看待的令人不愉快的API聯合使用。每次你編寫將null字符串和空字符串合在一起的代碼時,Guava團隊都會哭泣。(如果null字符串和空字符串表示的是完全不同的東西,那會更好,但是將它們視爲同一種東西是一種令人不安的常見代碼壞味道)

1.3使用示例

import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import junit.framework.TestCase;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;

public final class OptionalTest extends TestCase {
    public void testToJavaUtil_static() {
        assertNull(Optional.toJavaUtil(null));
        assertEquals(java.util.Optional.empty(), Optional.toJavaUtil(Optional.absent()));
        assertEquals(java.util.Optional.of("abc"), Optional.toJavaUtil(Optional.of("abc")));
    }

    public void testToJavaUtil_instance() {
        assertEquals(java.util.Optional.empty(), Optional.absent().toJavaUtil());
        assertEquals(java.util.Optional.of("abc"), Optional.of("abc").toJavaUtil());
    }

    public void testFromJavaUtil() {
        assertNull(Optional.fromJavaUtil(null));
        assertEquals(Optional.absent(), Optional.fromJavaUtil(java.util.Optional.empty()));
        assertEquals(Optional.of("abc"), Optional.fromJavaUtil(java.util.Optional.of("abc")));
    }

    public void testAbsent() {
        Optional<String> optionalName = Optional.absent();
        assertFalse(optionalName.isPresent());
    }

    public void testOf() {
        assertEquals("training", Optional.of("training").get());
    }

    public void testOf_null() {
        try {
            Optional.of(null);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    public void testFromNullable() {
        Optional<String> optionalName = Optional.fromNullable("bob");
        assertEquals("bob", optionalName.get());
    }

    public void testFromNullable_null() {
        // not promised by spec, but easier to test
        assertSame(Optional.absent(), Optional.fromNullable(null));
    }

    public void testIsPresent_no() {
        assertFalse(Optional.absent().isPresent());
    }

    public void testIsPresent_yes() {
        assertTrue(Optional.of("training").isPresent());
    }

    public void testGet_absent() {
        Optional<String> optional = Optional.absent();
        try {
            optional.get();
            fail();
        } catch (IllegalStateException expected) {
        }
    }

    public void testGet_present() {
        assertEquals("training", Optional.of("training").get());
    }

    public void testOr_T_present() {
        assertEquals("a", Optional.of("a").or("default"));
    }

    public void testOr_T_absent() {
        assertEquals("default", Optional.absent().or("default"));
    }

    public void testOr_supplier_present() {
        assertEquals("a", Optional.of("a").or(Suppliers.ofInstance("fallback")));
    }

    public void testOr_supplier_absent() {
        assertEquals("fallback", Optional.absent().or(Suppliers.ofInstance("fallback")));
    }

    public void testOr_nullSupplier_absent() {
        Supplier<Object> nullSupplier = Suppliers.ofInstance(null);
        Optional<Object> absentOptional = Optional.absent();
        try {
            absentOptional.or(nullSupplier);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    public void testOr_nullSupplier_present() {
        Supplier<String> nullSupplier = Suppliers.ofInstance(null);
        assertEquals("a", Optional.of("a").or(nullSupplier));
    }

    public void testOr_Optional_present() {
        assertEquals(Optional.of("a"), Optional.of("a").or(Optional.of("fallback")));
    }

    public void testOr_Optional_absent() {
        assertEquals(Optional.of("fallback"), Optional.absent().or(Optional.of("fallback")));
    }

    public void testOrNull_present() {
        assertEquals("a", Optional.of("a").orNull());
    }

    public void testOrNull_absent() {
        assertNull(Optional.absent().orNull());
    }

    public void testAsSet_present() {
        Set<String> expected = Collections.singleton("a");
        assertEquals(expected, Optional.of("a").asSet());
    }

    public void testAsSet_absent() {
        assertTrue("Returned set should be empty", Optional.absent().asSet().isEmpty());
    }

    public void testAsSet_presentIsImmutable() {
        Set<String> presentAsSet = Optional.of("a").asSet();
        try {
            presentAsSet.add("b");
            fail();
        } catch (UnsupportedOperationException expected) {
        }
    }

    public void testAsSet_absentIsImmutable() {
        Set<Object> absentAsSet = Optional.absent().asSet();
        try {
            absentAsSet.add("foo");
            fail();
        } catch (UnsupportedOperationException expected) {
        }
    }

    public void testTransform_absent() {
        assertEquals(Optional.absent(), Optional.absent().transform(Functions.identity()));
        assertEquals(Optional.absent(), Optional.absent().transform(Functions.toStringFunction()));
    }

    public void testTransform_presentIdentity() {
        assertEquals(Optional.of("a"), Optional.of("a").transform(Functions.identity()));
    }

    public void testTransform_presentToString() {
        assertEquals(Optional.of("42"), Optional.of(42).transform(Functions.toStringFunction()));
    }

    public void testPresentInstances_allPresent() {
        List<Optional<String>> optionals =
                ImmutableList.of(Optional.of("a"), Optional.of("b"), Optional.of("c"));
        assertThat(Optional.presentInstances(optionals)).containsExactly("a", "b", "c").inOrder();
    }

    public void testPresentInstances_allAbsent() {
        List<Optional<Object>> optionals = ImmutableList.of(Optional.absent(), Optional.absent());
        assertThat(Optional.presentInstances(optionals)).isEmpty();
    }

    public void testPresentInstances_somePresent() {
        List<Optional<String>> optionals =
                ImmutableList.of(Optional.of("a"), Optional.<String>absent(), Optional.of("c"));
        assertThat(Optional.presentInstances(optionals)).containsExactly("a", "c").inOrder();
    }

    public void testPresentInstances_callingIteratorTwice() {
        List<Optional<String>> optionals =
                ImmutableList.of(Optional.of("a"), Optional.<String>absent(), Optional.of("c"));
        Iterable<String> onlyPresent = Optional.presentInstances(optionals);
        assertThat(onlyPresent).containsExactly("a", "c").inOrder();
        assertThat(onlyPresent).containsExactly("a", "c").inOrder();
    }

    public void testPresentInstances_wildcards() {
        List<Optional<? extends Number>> optionals =
                ImmutableList.<Optional<? extends Number>>of(Optional.<Double>absent(), Optional.of(2));
        Iterable<Number> onlyPresent = Optional.presentInstances(optionals);
        assertThat(onlyPresent).containsExactly(2);
    }

}

2.前置條件

2.1介紹

Guava提供了許多前置條件檢查工具。每個方法有3個變體:

  1. 沒有額外參數。拋出異常而不會出現錯誤消息。
  2. 一個額外的Object參數。拋出異常使用object.toString()作爲錯誤消息。
  3. 一個額外的String參數,帶有任意數量的Object對象參數。行爲類似於printf,但是爲了GWT的兼容性和效率,它僅允許%s指示符。
    • 注意:checkNotNullcheckArgumentcheckState具有大量重載,這些重載採用原始參數和Object參數而不是可變數組的組合——這樣,在大多數情況下,上述調用都可以避免原始裝箱和可變數組分配

第三種變體的示例:

checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
checkArgument(i < j, "Expected i < j, but %s >= %s", i, j);
方法聲明(不包括額外參數) 描述 失敗時拋出異常
checkArgument(boolean) 檢查boolean是否爲true。用於驗證方法的參數。 IllegalArgumentException
checkNotNull(T) 檢查值是否不爲null。直接返回值,因此可以內聯使用checkNotNull(value) NullPointerException
checkState(boolean) 檢查對象的某些狀態,而不依賴於方法參數。例如,一個Iterator可能會使用它來檢查在調用任何remove之前是否已調用next IllegalStateException
checkElementIndex(int index, int size) 檢查index是否爲具有指定大小的列表、字符串或數組的有效元素索引。元素索引的範圍可以從0(含0)到size(不含)。不直接通過列表、字符串或數組;而是隻要通過它的size。返回index IndexOutOfBoundsException
checkPositionIndex(int index, int size) 檢查index是否爲具有指定大小的列表、字符串或數組的有效位置索引。位置索引的範圍可以是0(含0)到size(含)。不直接通過列表、字符串或數組;而是隻要通過它的size。返回index IndexOutOfBoundsException
checkPositionIndexes(int start, int end, int size) 檢查[start, end)是否爲具有指定大小的列表、字符串或數組的有效子範圍。帶有自己的錯誤消息。 IndexOutOfBoundsException

出於幾個原因,我們傾向於使用我們自己的先決條件檢查,而不是Apache Commons中的類似工具。簡要地:

  • 靜態導入後,Guava方法清晰明瞭。checkNotNull可以清楚地說明正在執行的操作以及將拋出的異常。
  • checkNotNull在驗證後返回其參數,從而允許在構造函數中使用簡單的一列式:this.field = checkNotNull(field);
  • 簡單的可變參數"printf樣式"異常消息。(這也是我們建議繼續對Objects.requireNonNull使用checkNotNull的原因)

我們建議你將前置條件分成不同的行,這可以幫助你確定調試時哪個前置條件失敗。此外,你應該提供有用的錯誤消息,當每項檢查都在自己的行上時,會更容易。

2.2使用示例

import com.google.common.base.Preconditions;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

import static com.google.common.truth.Truth.assertThat;

public class PreconditionsTest extends TestCase {
    public void testCheckArgument_simple_success() {
        Preconditions.checkArgument(true);
    }

    public void testCheckArgument_simple_failure() {
        try {
            Preconditions.checkArgument(false);
            fail("no exception thrown");
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testCheckArgument_simpleMessage_success() {
        Preconditions.checkArgument(true, IGNORE_ME);
    }

    public void testCheckArgument_simpleMessage_failure() {
        try {
            Preconditions.checkArgument(false, new Message());
            fail("no exception thrown");
        } catch (IllegalArgumentException expected) {
            verifySimpleMessage(expected);
        }
    }

    public void testCheckArgument_nullMessage_failure() {
        try {
            Preconditions.checkArgument(false, null);
            fail("no exception thrown");
        } catch (IllegalArgumentException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("null");
        }
    }

    public void testCheckArgument_nullMessageWithArgs_failure() {
        try {
            Preconditions.checkArgument(false, null, "b", "d");
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("null [b, d]");
        }
    }

    public void testCheckArgument_nullArgs_failure() {
        try {
            Preconditions.checkArgument(false, "A %s C %s E", null, null);
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("A null C null E");
        }
    }

    public void testCheckArgument_notEnoughArgs_failure() {
        try {
            Preconditions.checkArgument(false, "A %s C %s E", "b");
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("A b C %s E");
        }
    }

    public void testCheckArgument_tooManyArgs_failure() {
        try {
            Preconditions.checkArgument(false, "A %s C %s E", "b", "d", "f");
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("A b C d E [f]");
        }
    }

    public void testCheckArgument_singleNullArg_failure() {
        try {
            Preconditions.checkArgument(false, "A %s C", (Object) null);
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("A null C");
        }
    }

    public void testCheckArgument_singleNullArray_failure() {
        try {
            Preconditions.checkArgument(false, "A %s C", (Object[]) null);
            fail("no exception thrown");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat().isEqualTo("A (Object[])null C");
        }
    }

    public void testCheckArgument_complexMessage_success() {
        Preconditions.checkArgument(true, "%s", IGNORE_ME);
    }

    public void testCheckArgument_complexMessage_failure() {
        try {
            Preconditions.checkArgument(false, FORMAT, 5);
            fail("no exception thrown");
        } catch (IllegalArgumentException expected) {
            verifyComplexMessage(expected);
        }
    }

    public void testCheckState_simple_success() {
        Preconditions.checkState(true);
    }

    public void testCheckState_simple_failure() {
        try {
            Preconditions.checkState(false);
            fail("no exception thrown");
        } catch (IllegalStateException expected) {
        }
    }

    public void testCheckState_simpleMessage_success() {
        Preconditions.checkState(true, IGNORE_ME);
    }

    public void testCheckState_simpleMessage_failure() {
        try {
            Preconditions.checkState(false, new Message());
            fail("no exception thrown");
        } catch (IllegalStateException expected) {
            verifySimpleMessage(expected);
        }
    }

    public void testCheckState_nullMessage_failure() {
        try {
            Preconditions.checkState(false, null);
            fail("no exception thrown");
        } catch (IllegalStateException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("null");
        }
    }

    public void testCheckState_complexMessage_success() {
        Preconditions.checkState(true, "%s", IGNORE_ME);
    }

    public void testCheckState_complexMessage_failure() {
        try {
            Preconditions.checkState(false, FORMAT, 5);
            fail("no exception thrown");
        } catch (IllegalStateException expected) {
            verifyComplexMessage(expected);
        }
    }

    private static final String NON_NULL_STRING = "foo";

    public void testCheckNotNull_simple_success() {
        String result = Preconditions.checkNotNull(NON_NULL_STRING);
        assertSame(NON_NULL_STRING, result);
    }

    public void testCheckNotNull_simple_failure() {
        try {
            Preconditions.checkNotNull(null);
            fail("no exception thrown");
        } catch (NullPointerException expected) {
        }
    }

    public void testCheckNotNull_simpleMessage_success() {
        String result = Preconditions.checkNotNull(NON_NULL_STRING, IGNORE_ME);
        assertSame(NON_NULL_STRING, result);
    }

    public void testCheckNotNull_simpleMessage_failure() {
        try {
            Preconditions.checkNotNull(null, new Message());
            fail("no exception thrown");
        } catch (NullPointerException expected) {
            verifySimpleMessage(expected);
        }
    }

    public void testCheckNotNull_complexMessage_success() {
        String result = Preconditions.checkNotNull(NON_NULL_STRING, "%s", IGNORE_ME);
        assertSame(NON_NULL_STRING, result);
    }

    public void testCheckNotNull_complexMessage_failure() {
        try {
            Preconditions.checkNotNull(null, FORMAT, 5);
            fail("no exception thrown");
        } catch (NullPointerException expected) {
            verifyComplexMessage(expected);
        }
    }

    public void testCheckElementIndex_ok() {
        assertEquals(0, Preconditions.checkElementIndex(0, 1));
        assertEquals(0, Preconditions.checkElementIndex(0, 2));
        assertEquals(1, Preconditions.checkElementIndex(1, 2));
    }

    public void testCheckElementIndex_badSize() {
        try {
            Preconditions.checkElementIndex(1, -1);
            fail();
        } catch (IllegalArgumentException expected) {
            // don't care what the message text is, as this is an invalid usage of
            // the Preconditions class, unlike all the other exceptions it throws
        }
    }

    public void testCheckElementIndex_negative() {
        try {
            Preconditions.checkElementIndex(-1, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative");
        }
    }

    public void testCheckElementIndex_tooHigh() {
        try {
            Preconditions.checkElementIndex(1, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("index (1) must be less than size (1)");
        }
    }

    public void testCheckElementIndex_withDesc_negative() {
        try {
            Preconditions.checkElementIndex(-1, 1, "foo");
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative");
        }
    }

    public void testCheckElementIndex_withDesc_tooHigh() {
        try {
            Preconditions.checkElementIndex(1, 1, "foo");
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("foo (1) must be less than size (1)");
        }
    }

    public void testCheckPositionIndex_ok() {
        assertEquals(0, Preconditions.checkPositionIndex(0, 0));
        assertEquals(0, Preconditions.checkPositionIndex(0, 1));
        assertEquals(1, Preconditions.checkPositionIndex(1, 1));
    }

    public void testCheckPositionIndex_badSize() {
        try {
            Preconditions.checkPositionIndex(1, -1);
            fail();
        } catch (IllegalArgumentException expected) {
            // don't care what the message text is, as this is an invalid usage of
            // the Preconditions class, unlike all the other exceptions it throws
        }
    }

    public void testCheckPositionIndex_negative() {
        try {
            Preconditions.checkPositionIndex(-1, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative");
        }
    }

    public void testCheckPositionIndex_tooHigh() {
        try {
            Preconditions.checkPositionIndex(2, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected)
                    .hasMessageThat()
                    .isEqualTo("index (2) must not be greater than size (1)");
        }
    }

    public void testCheckPositionIndex_withDesc_negative() {
        try {
            Preconditions.checkPositionIndex(-1, 1, "foo");
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative");
        }
    }

    public void testCheckPositionIndex_withDesc_tooHigh() {
        try {
            Preconditions.checkPositionIndex(2, 1, "foo");
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("foo (2) must not be greater than size (1)");
        }
    }

    public void testCheckPositionIndexes_ok() {
        Preconditions.checkPositionIndexes(0, 0, 0);
        Preconditions.checkPositionIndexes(0, 0, 1);
        Preconditions.checkPositionIndexes(0, 1, 1);
        Preconditions.checkPositionIndexes(1, 1, 1);
    }

    public void testCheckPositionIndexes_badSize() {
        try {
            Preconditions.checkPositionIndexes(1, 1, -1);
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testCheckPositionIndex_startNegative() {
        try {
            Preconditions.checkPositionIndexes(-1, 1, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected).hasMessageThat().isEqualTo("start index (-1) must not be negative");
        }
    }

    public void testCheckPositionIndexes_endTooHigh() {
        try {
            Preconditions.checkPositionIndexes(0, 2, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected)
                    .hasMessageThat()
                    .isEqualTo("end index (2) must not be greater than size (1)");
        }
    }

    public void testCheckPositionIndexes_reversed() {
        try {
            Preconditions.checkPositionIndexes(1, 0, 1);
            fail();
        } catch (IndexOutOfBoundsException expected) {
            assertThat(expected)
                    .hasMessageThat()
                    .isEqualTo("end index (0) must not be less than start index (1)");
        }
    }

    private static final Object IGNORE_ME =
            new Object() {
                @Override
                public String toString() {
                    throw new AssertionFailedError();
                }
            };

    private static class Message {
        boolean invoked;

        @Override
        public String toString() {
            assertFalse(invoked);
            invoked = true;
            return "A message";
        }
    }

    private static final String FORMAT = "I ate %s pies.";

    private static void verifySimpleMessage(Exception e) {
        assertThat(e).hasMessageThat().isEqualTo("A message");
    }

    private static void verifyComplexMessage(Exception e) {
        assertThat(e).hasMessageThat().isEqualTo("I ate 5 pies.");
    }
}

3.通用對象方法

3.1equals

當對象字段可以爲null時,實現Object.equals會很麻煩,因爲必須分別檢查null。使用Objects.equal可以以null敏感的方式執行equals檢查,沒有NullPointerException的風險。

Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true

注意:JDK 7中新引入的Objects類提供了等效的Objects.equals方法。

3.2hashCode

Object對象的所有字段作散列(hash)應該更簡單。Guava的Objects.hashCode(Object ...)爲指定的字段序列創建一個合理的,順序敏感的哈希。使用Objects.hashCode(field1, field2, ..., fieldn)代替手動構建哈希。

注意:JDK 7中新引入的Objects類提供了等效的Objects.hash(Object ...)

3.3toString

好的toString方法在調試中可能是無價的,但是編寫起來很麻煩。使用MoreObjects.toStringHelper()輕鬆創建有用的toString

示例:

// Returns "ClassName{x=1}"
MoreObjects.toStringHelper(this)
    .add("x", 1)
    .toString();

// Returns "MyObject{x=1}"
MoreObjects.toStringHelper("MyObject")
    .add("x", 1)
    .toString();

3.4compare/compareTo

實現Comparator比較器或直接實現Comparable接口可能很麻煩。

考慮一下這種情況:

class Person implements Comparable<Person> {
  private String lastName;
  private String firstName;
  private int zipCode;

  public int compareTo(Person other) {
    int cmp = lastName.compareTo(other.lastName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = firstName.compareTo(other.firstName);
    if (cmp != 0) {
      return cmp;
    }
    return Integer.compare(zipCode, other.zipCode);
  }
}

這段代碼很容易弄亂,難以掃描錯誤,而且冗長。應該把這種代碼變得更優雅,爲此,Guava提供了ComparisonChain

ComparisonChain執行“惰性”比較:它僅執行比較,直到找到非零結果爲止,此後它將忽略進一步的輸入。

public int compareTo(Foo that) {
    return ComparisonChain.start()
        .compare(this.aString, that.aString)
        .compare(this.anInt, that.anInt)
        .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
        .result();
}

這種流利的習慣用法更具可讀性,不容易出現偶然的錯誤,並且足夠聰明,不用去做必須的更多的工作。其它比較實用工具可以在Guava的“流利比較器”類Ordering中找到,在此進行解釋。

3.5使用示例

import com.google.common.base.Objects;
import junit.framework.TestCase;

public class ObjectsTest extends TestCase {

    public void testEqual() {
        assertTrue(Objects.equal(1, 1));
        assertTrue(Objects.equal(null, null));

        // test distinct string objects
        String s1 = "foobar";
        String s2 = new String(s1);
        assertTrue(Objects.equal(s1, s2));

        assertFalse(Objects.equal(s1, null));
        assertFalse(Objects.equal(null, s1));
        assertFalse(Objects.equal("foo", "bar"));
        assertFalse(Objects.equal("1", 1));
    }

    public void testHashCode() {
        int h1 = Objects.hashCode(1, "two", 3.0);
        int h2 = Objects.hashCode(new Integer(1), new String("two"), new Double(3.0));
        // repeatable
        assertEquals(h1, h2);

        // These don't strictly need to be true, but they're nice properties.
        assertTrue(Objects.hashCode(1, 2, null) != Objects.hashCode(1, 2));
        assertTrue(Objects.hashCode(1, 2, null) != Objects.hashCode(1, null, 2));
        assertTrue(Objects.hashCode(1, null, 2) != Objects.hashCode(1, 2));
        assertTrue(Objects.hashCode(1, 2, 3) != Objects.hashCode(3, 2, 1));
        assertTrue(Objects.hashCode(1, 2, 3) != Objects.hashCode(2, 3, 1));
    }

}

4.排序

4.1示例

assertTrue(byLengthOrdering.reverse().isOrdered(list));

4.2概述

Ordering排序是Guava的“流利”比較器Comparator類,可用於構建複雜的比較器並將其應用於對象集合。

從本質上講,Ordering實例不過是一個特殊的Comparator實例。Ordering排序僅採用依賴於Comparator的方法(例如Collections.max),並使它們作爲實例方法可用。爲了增加功能,Ordering類提供了鏈式調用方法來調整和增強現有的比較器。

4.3創建

靜態方法提供了常見的排序方式:

方法 描述
natural() 對可比較類型使用自然排序。
usingToString() 按對象的字符串表示形式按字典順序對對象進行比較,該對象由toString()返回。

使一個預先存在的Comparator比較器成爲Ordering就像使用Ordering.from(Comparator)一樣簡單。

但是創建自定義Ordering的更常見方法是完全跳過Comparator,而直接擴展Ordering抽象類:

Ordering<String> byLengthOrdering = new Ordering<String>() {
  public int compare(String left, String right) {
    return Ints.compare(left.length(), right.length());
  }
};

4.4鏈式調用

可以包裝給定的Ordering以獲得派生的排序器。一些最常用的變體包括:

方法 描述
reverse() 返回相反的排序器
nullsFirst() 返回一個Ordering在非null元素之前對null進行排序,否則其行爲與原始Ordering相同。參見nullsLast()
compound(Comparator) 合成另一個比較器,以處理當前排序器中的相等情況。
lexicographical() 返回一個Ordering,根據元素按字典順序對可迭代對象進行排序。
onResultOf(Function) 返回一個Ordering通過函數返回結果,然後使用原始Ordering比較對返回值進行排序。

例如,假設想要該類的比較器…

class Foo {
  @Nullable String sortedBy;
  int notSortedBy;
}

…可以處理sortedBy的空值。這是建立在鏈式方法之上的解決方案:

Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(new Function<Foo, String>() {
  public String apply(Foo foo) {
    return foo.sortedBy;
  }
});

當閱讀Ordering的鏈式調用方法,應從右到左向後閱讀。上面的示例通過查找Foo實例的sortedBy字段值對它們進行排序,首先將所有空的sortedBy值移到頂部,然後通過自然字符串排序對其餘值進行排序。之所以會出現這種向後順序,是因爲每個鏈接調用都將先前的Ordering順序“包裝”到一個新的順序中。

“向後”規則的例外:對於compound的調用鏈,請從左至右讀取。爲避免混淆,請避免將compound調用與其它鏈式調用混在一起。

鏈式調用超過一定長度可能很難理解。如上例所示,建議將鏈式調用限制爲大約三個。此外,也可以通過分離中間對象(例如Function實例)來簡化代碼:

Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(sortKeyFunction);

4.5應用

Guava提供了多種方法使用排序來操縱或檢查值或集合。在這裏列出了一些最常用的:

方法 描述 另請參見
greatestOf(Iterable iterable, int k) 從最大到最小的順序,根據此排序,返回指定可迭代的k個最大元素。不一定穩定。 leastOf
isOrdered(Iterable) 測試指定的Iterable是否按照此順序爲非遞減順序。 isStrictlyOrdered
sortedCopy(Iterable) List列表的形式返回指定元素的排序副本。 immutableSortedCopy
min(E, E) 根據此順序返回其兩個參數中的最小值。如果值比較相等,則返回第一個參數。 max(E, E)
min(E, E, E, E...) 根據此順序返回其參數的最小值。如果存在多個最小值,則返回第一個。 max(E, E, E, E...)
min(Iterable) 返回指定Iterable的最小元素。如果Iterable爲空,則拋出NoSuchElementException max(Iterable), min(Iterator), max(Iterator)

4.6使用示例

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import junit.framework.TestCase;
import java.util.*;
import static com.google.common.testing.SerializableTester.reserialize;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;

public class OrderingTest extends TestCase {

    private final Ordering<Number> numberOrdering = new NumberOrdering();

    public void testAllEqual() {
        Ordering<Object> comparator = Ordering.allEqual();
        assertSame(comparator, comparator.reverse());

        assertEquals(0, comparator.compare(null, null));
        assertEquals(0, comparator.compare(new Object(), new Object()));
        assertEquals(0, comparator.compare("apples", "oranges"));
        assertSame(comparator, reserialize(comparator));
        assertEquals("Ordering.allEqual()", comparator.toString());

        List<String> strings = ImmutableList.of("b", "a", "d", "c");
        assertEquals(strings, comparator.sortedCopy(strings));
        assertEquals(strings, comparator.immutableSortedCopy(strings));
    }

    public void testFrom() {
        Ordering<String> caseInsensitiveOrdering = Ordering.from(String.CASE_INSENSITIVE_ORDER);
        assertEquals(0, caseInsensitiveOrdering.compare("A", "a"));
        assertTrue(caseInsensitiveOrdering.compare("a", "B") < 0);
        assertTrue(caseInsensitiveOrdering.compare("B", "a") > 0);
    }

    public void testExplicit_two() {
        Comparator<Integer> c = Ordering.explicit(42, 5);
        System.out.println(c.compare(43, 6));
        assertEquals(0, c.compare(5, 5));
        assertTrue(c.compare(5, 42) > 0);
        assertTrue(c.compare(42, 5) < 0);
    }

    public void testExplicit_sortingExample() {
        Comparator<Integer> c = Ordering.explicit(2, 8, 6, 1, 7, 5, 3, 4, 0, 9);
        List<Integer> list = Arrays.asList(0, 3, 5, 6, 7, 8, 9);
        Collections.sort(list, c);
        assertThat(list).containsExactly(8, 6, 7, 5, 3, 0, 9).inOrder();
    }

    public void testReverseOfReverseSameAsForward() {
        // Not guaranteed by spec, but it works, and saves us from testing
        // exhaustively
        assertSame(numberOrdering, numberOrdering.reverse().reverse());
    }

    public void testBinarySearch() {
        List<Integer> ints = Lists.newArrayList(0, 2, 3, 5, 7, 9);
        assertEquals(4, numberOrdering.binarySearch(ints, 7));
    }

    public void testSortedCopy() {
        List<Integer> unsortedInts = Collections.unmodifiableList(Arrays.asList(5, 0, 3, null, 0, 9));
        List<Integer> sortedInts = numberOrdering.nullsLast().sortedCopy(unsortedInts);
        assertEquals(Arrays.asList(0, 0, 3, 5, 9, null), sortedInts);

        assertEquals(
                Collections.emptyList(), numberOrdering.sortedCopy(Collections.<Integer>emptyList()));
    }

    public void testImmutableSortedCopy() {
        ImmutableList<Integer> unsortedInts = ImmutableList.of(5, 3, 0, 9, 3);
        ImmutableList<Integer> sortedInts = numberOrdering.immutableSortedCopy(unsortedInts);
        assertEquals(Arrays.asList(0, 3, 3, 5, 9), sortedInts);

        assertEquals(
                Collections.<Integer>emptyList(),
                numberOrdering.immutableSortedCopy(Collections.<Integer>emptyList()));

        List<Integer> listWithNull = Arrays.asList(5, 3, null, 9);
        try {
            Ordering.natural().nullsFirst().immutableSortedCopy(listWithNull);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    public void testIsOrdered() {
        assertFalse(numberOrdering.isOrdered(asList(5, 3, 0, 9)));
        assertFalse(numberOrdering.isOrdered(asList(0, 5, 3, 9)));
        assertTrue(numberOrdering.isOrdered(asList(0, 3, 5, 9)));
        assertTrue(numberOrdering.isOrdered(asList(0, 0, 3, 3)));
        assertTrue(numberOrdering.isOrdered(asList(0, 3)));
        assertTrue(numberOrdering.isOrdered(Collections.singleton(1)));
        assertTrue(numberOrdering.isOrdered(Collections.<Integer>emptyList()));
    }

    public void testIsStrictlyOrdered() {
        assertFalse(numberOrdering.isStrictlyOrdered(asList(5, 3, 0, 9)));
        assertFalse(numberOrdering.isStrictlyOrdered(asList(0, 5, 3, 9)));
        assertTrue(numberOrdering.isStrictlyOrdered(asList(0, 3, 5, 9)));
        assertFalse(numberOrdering.isStrictlyOrdered(asList(0, 0, 3, 3)));
        assertTrue(numberOrdering.isStrictlyOrdered(asList(0, 3)));
        assertTrue(numberOrdering.isStrictlyOrdered(Collections.singleton(1)));
        assertTrue(numberOrdering.isStrictlyOrdered(Collections.<Integer>emptyList()));
    }

    public void testLeastOfIterableLargeK() {
        List<Integer> list = Arrays.asList(4, 2, 3, 5, 1);
        assertEquals(Arrays.asList(1, 2, 3, 4, 5), Ordering.natural().leastOf(list, Integer.MAX_VALUE));
    }

    public void testLeastOfIteratorLargeK() {
        List<Integer> list = Arrays.asList(4, 2, 3, 5, 1);
        assertEquals(
                Arrays.asList(1, 2, 3, 4, 5),
                Ordering.natural().leastOf(list.iterator(), Integer.MAX_VALUE));
    }

    public void testGreatestOfIterable_simple() {
        /*
         * If greatestOf() promised to be implemented as reverse().leastOf(), this
         * test would be enough. It doesn't... but we'll cheat and act like it does
         * anyway. There's a comment there to remind us to fix this if we change it.
         */
        List<Integer> list = Arrays.asList(3, 1, 3, 2, 4, 2, 4, 3);
        assertEquals(Arrays.asList(4, 4, 3, 3), numberOrdering.greatestOf(list, 4));
    }

    public void testGreatestOfIterator_simple() {
        /*
         * If greatestOf() promised to be implemented as reverse().leastOf(), this
         * test would be enough. It doesn't... but we'll cheat and act like it does
         * anyway. There's a comment there to remind us to fix this if we change it.
         */
        List<Integer> list = Arrays.asList(3, 1, 3, 2, 4, 2, 4, 3);
        assertEquals(Arrays.asList(4, 4, 3, 3), numberOrdering.greatestOf(list.iterator(), 4));
    }

    public void testIteratorMinAndMax() {
        List<Integer> ints = Lists.newArrayList(5, 3, 0, 9);
        assertEquals(9, (int) numberOrdering.max(ints.iterator()));
        assertEquals(0, (int) numberOrdering.min(ints.iterator()));

        // when the values are the same, the first argument should be returned
        Integer a = new Integer(4);
        Integer b = new Integer(4);
        ints = Lists.newArrayList(a, b, b);
        assertSame(a, numberOrdering.max(ints.iterator()));
        assertSame(a, numberOrdering.min(ints.iterator()));
    }

    public void testIteratorMinExhaustsIterator() {
        List<Integer> ints = Lists.newArrayList(9, 0, 3, 5);
        Iterator<Integer> iterator = ints.iterator();
        assertEquals(0, (int) numberOrdering.min(iterator));
        assertFalse(iterator.hasNext());
    }

    public void testIteratorMaxExhaustsIterator() {
        List<Integer> ints = Lists.newArrayList(9, 0, 3, 5);
        Iterator<Integer> iterator = ints.iterator();
        assertEquals(9, (int) numberOrdering.max(iterator));
        assertFalse(iterator.hasNext());
    }

    public void testIterableMinAndMax() {
        List<Integer> ints = Lists.newArrayList(5, 3, 0, 9);
        assertEquals(9, (int) numberOrdering.max(ints));
        assertEquals(0, (int) numberOrdering.min(ints));

        // when the values are the same, the first argument should be returned
        Integer a = new Integer(4);
        Integer b = new Integer(4);
        ints = Lists.newArrayList(a, b, b);
        assertSame(a, numberOrdering.max(ints));
        assertSame(a, numberOrdering.min(ints));
    }

    public void testVarargsMinAndMax() {
        // try the min and max values in all positions, since some values are proper
        // parameters and others are from the varargs array
        assertEquals(9, (int) numberOrdering.max(9, 3, 0, 5, 8));
        assertEquals(9, (int) numberOrdering.max(5, 9, 0, 3, 8));
        assertEquals(9, (int) numberOrdering.max(5, 3, 9, 0, 8));
        assertEquals(9, (int) numberOrdering.max(5, 3, 0, 9, 8));
        assertEquals(9, (int) numberOrdering.max(5, 3, 0, 8, 9));
        assertEquals(0, (int) numberOrdering.min(0, 3, 5, 9, 8));
        assertEquals(0, (int) numberOrdering.min(5, 0, 3, 9, 8));
        assertEquals(0, (int) numberOrdering.min(5, 3, 0, 9, 8));
        assertEquals(0, (int) numberOrdering.min(5, 3, 9, 0, 8));
        assertEquals(0, (int) numberOrdering.min(5, 3, 0, 9, 0));

        // when the values are the same, the first argument should be returned
        Integer a = new Integer(4);
        Integer b = new Integer(4);
        assertSame(a, numberOrdering.max(a, b, b));
        assertSame(a, numberOrdering.min(a, b, b));
    }

    public void testParameterMinAndMax() {
        assertEquals(5, (int) numberOrdering.max(3, 5));
        assertEquals(5, (int) numberOrdering.max(5, 3));
        assertEquals(3, (int) numberOrdering.min(3, 5));
        assertEquals(3, (int) numberOrdering.min(5, 3));

        // when the values are the same, the first argument should be returned
        Integer a = new Integer(4);
        Integer b = new Integer(4);
        assertSame(a, numberOrdering.max(a, b));
        assertSame(a, numberOrdering.min(a, b));
    }

    private static class NumberOrdering extends Ordering<Number> {
        @Override
        public int compare(Number a, Number b) {
            return ((Double) a.doubleValue()).compareTo(b.doubleValue());
        }

        @Override
        public int hashCode() {
            return NumberOrdering.class.hashCode();
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof NumberOrdering;
        }

        private static final long serialVersionUID = 0;
    }
  
}

5.異常

Guava的Throwables 實用工具可以簡化異常處理。

5.1傳播

有時,當捕獲異常時,想將其拋出返回到下一個try/catch塊。對於RuntimeExceptionError實例,通常是這種情況,它們不需要try/catch塊,但是當它們不希望被拋出時可以用try/catch塊捕獲。

Guava提供了幾種實用程序來簡化傳播異常。例如:

try {
  someMethodThatCouldThrowAnything();
} catch (IKnowWhatToDoWithThisException e) {
  handle(e);
} catch (Throwable t) {
  Throwables.propagateIfInstanceOf(t, IOException.class);
  Throwables.propagateIfInstanceOf(t, SQLException.class);
  throw Throwables.propagate(t);
}

這些方法各自都會拋出異常,但是也能拋出方法返回的結果——例如,throw Throwables.propagate(t)——有助於向編譯器證明將拋出異常。

以下是Guava提供的傳播方法的快速摘要:

方法聲明 說明
RuntimeException propagate(Throwable) 如果它是RuntimeExceptionError,則按原樣拋出,或者將其包裝在RuntimeException中拋出。返回類型是RuntimeException,因此可以如上所述編寫throw Throwables.propagate(t),Java將意識到該行肯定會拋出異常。
void propagateIfInstanceOf(Throwable, Class) throws X 僅當它是X的實例時,才按原樣拋出該throwable
void propagateIfPossible(Throwable) 僅當它是RuntimeExceptionError時纔可以拋出throwable
void propagateIfPossible(Throwable, Class) throws X 僅當它是RuntimeExceptionErrorX時,纔可以按原樣拋出throwable

5.2Throwables.propagate用法

5.2.1模擬Java 7多重捕獲並重新拋出

通常,如果想讓異常在堆棧中傳播,則根本不需要catch塊。由於你不會從異常中恢復,因此你可能不應該記錄該異常或採取其他措施。你可能需要執行一些清除操作,但是通常無論操作是否成功,都需要進行清除操作,因此該操作最終會在finally塊中結束。但是,重新拋出的catch塊有時很有用:也許你必須在傳播異常之前更新失敗計數,或者可能只想有條件地傳播異常。

僅處理一種異常時,捕獲和重新拋出異常就很簡單。當處理多種異常時,它變得凌亂:

@Override public void run() {
  try {
    delegate.run();
  } catch (RuntimeException e) {
    failures.increment();
    throw e;
  } catch (Error e) {
    failures.increment();
    throw e;
  }
}

Java 7通過multicatch解決了這個問題:

} catch (RuntimeException | Error e) {
  failures.increment();
  throw e;
}

非Java 7用戶被卡住了。他們想編寫如下代碼,但是編譯器不允許他們拋出Throwable類型的變量:

(這種寫法把原本是Error或RuntimeException類型的異常修改成了Throwable,因此調用者不得不修改方法簽名)

} catch (Throwable t) {
  failures.increment();
  throw t;
}

解決方案是用throw Throwables.propagate(t)替換throw t。在這種有限的情況下,Throwables.propagate的行爲與原始代碼相同。但是,使用具有其他隱藏行爲的Throwables.propagate編寫代碼很容易。特別要注意的是,上述模式僅適用於RuntimeExceptionError。如果catch塊可能捕獲了受檢查的異常,則還需要調用propertyIfInstanceOf以保持行爲,因爲Throwables.propagate無法直接傳播受檢查的異常。

總體而言,對傳播的這種使用很一般。在Java 7中是不必要的。在其他版本下,它可以節省少量重複,但是可以簡化簡單的“提取方法”重構。此外,使用propagate容易意外包裝檢查的異常

5.2.2不必要:從"throws Throwable"轉換爲"throws Exception"

少數API,特別是Java反射API和(以此爲基礎的)JUnit,聲明瞭拋出Throwable的方法。與這些API交互可能會很痛苦,因爲即使是最通用的API通常也只能聲明throws Exception。一些知道其具有非EXception異常,非Error錯誤的調用者使用Throwables.propagate轉化Throwable。這是一個聲明執行JUnit測試的Callable的示例:

public Void call() throws Exception {
  try {
    FooTest.super.runTest();
  } catch (Throwable t) {
    Throwables.propagateIfPossible(t, Exception.class);
    Throwables.propagate(t);
  }

  return null;
}

此處無需進行propagate(),因爲第二行等效於throw new RuntimeException(t)。(題外話:這個示例還提醒我們,propagateIfPossible可能會引起混淆,因爲它不僅傳播給定類型的參數,而且傳播ErrorsRuntimeExceptions。)

這種模式(或類似的變體,例如引發throw new RuntimeException(t))在Google的代碼庫中顯示了約30次。(搜索'propagateIfPossible[^;]* Exception.class[)];')它們中的絕大多數採用顯式throw new RuntimeException(t)方法。對於ThrowableException的轉換,我們可能需要一個throwWrappingWeirdThrowable方法,但是考慮到兩行替代,除非我們也要棄用propagateIfPossible,否則可能不需要太多。

5.2.3有爭議的Throwables.propagate用途

5.2.3.1有爭議的:將檢查的異常轉換爲未檢查的異常

原則上,未檢查的異常表示錯誤,而檢查的異常表示無法控制的問題。實際上,即使JDK有時也會出錯ObjectIntegerURI(或者至少對於某些方法,沒有答案適合每個人URI)。

結果,調用者有時不得不在異常類型之間進行轉換:

try {
  return Integer.parseInt(userInput);
} catch (NumberFormatException e) {
  throw new InvalidInputException(e);
}
try {
  return publicInterfaceMethod.invoke();
} catch (IllegalAccessException e) {
  throw new AssertionError(e);
}

有時,這些調用者使用Throwables.propagate。缺點是什麼?

最主要的是,代碼的含義不太明顯。Throwables.propagate(ioException)會做什麼?throw new RuntimeException(ioException)有什麼作用?兩者的作用相同,但後者更直接。前者提出了一個問題:“這是做什麼的?它不僅包裝在RuntimeException中,是嗎?如果是,爲什麼他們要編寫一個方法包裝器?”

誠然,這裏的部分問題是“propagate”是一個模糊的名稱。(這是拋出未聲明異常的一種方法嗎?)也許“ wrapIfChecked”會更好。即使調用了該方法,在已知的已檢查異常上調用它沒有任何好處。甚至還有其它缺點:也許有比普通的RuntimeException更合適的類型供拋出-例如IllegalArgumentException

有時,當僅異常可能是已檢查的異常時,有時還會使用propagate。結果比替代方法小一些,直接性也小一些:

} catch (RuntimeException e) {
  throw e;
} catch (Exception e) {
  throw new RuntimeException(e);
}
} catch (Exception e) {
  throw Throwables.propagate(e);
}

然而,這裏是將已檢查的異常轉換爲未檢查的異常的一般做法。在某些情況下,無疑這是正確的做法,但更經常地,它是用來避免處理合法的已檢查異常。這使我們開始辯論有關檢查異常是否總體上是一個壞主意。我不想在這裏討論所有內容。可以說,Throwables.propagate不存在爲了鼓勵Java用戶忽略IOException等的目的。

5.2.3.2有爭議的:異常隧道

但是,當要實現不允許拋出異常的方法時,該怎麼辦?有時,需要將異常包裝在未經檢查的異常中。很好,但同樣,對於簡單包裝而言,不需要propagate。實際上,手動包裝可能是更好的選擇:如果包裝每個異常(而不是僅檢查的異常),則可以在另一端解包每個異常,從而減少特殊情況。此外,可能希望對包裝使用自定義異常類型。

5.2.3.3有爭議的:從其它線程重新拋出異常

try {
  return future.get();
} catch (ExecutionException e) {
  throw Throwables.propagate(e.getCause());
}

這裏有很多事情要考慮:

  1. 可能是經過檢查的異常導致。請參閱上面的“將檢查的異常轉換爲未檢查的異常”。但是,如果已知該任務不拋出已檢查異常,該怎麼辦?(也許這是Runnable的結果)如上所述,可以捕獲異常並拋出AssertionError;propagate沒什麼好提供的。特別是對於Future,還要考慮Futures.get

  2. 可能是非Exception,非ErrorThrowable導致。(嗯,實際上不可能是一個,但是如果嘗試直接將其重新拋出,編譯器將迫使您考慮這種可能性。)請參見上面的"從throwable Throwable轉換爲throws Exception"。

  3. 可能是未經檢查的異常或錯誤導致。如果是這樣,它將直接重新拋出。不幸的是,它的堆棧跟蹤將反映最初在其中創建異常的線程,而不是當前正在其中傳播的線程。通常最好在異常鏈中包含兩個線程的堆棧跟蹤,如get拋出的ExecutionException一樣。(此問題實際上與propagate無關;它與在不同線程中重新拋出異常的任何代碼有關。)

5.3因果鏈

Guava使研究異常的因果鏈更爲簡單,它提供了三種有用的方法,其方法簽名是不言自明的:

5.4使用示例

import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
import com.google.common.testing.NullPointerTester;
import junit.framework.TestCase;
import java.util.List;
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
import static com.google.common.base.Throwables.*;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;
import static java.util.regex.Pattern.quote;

public class ThrowablesTest extends TestCase {

    public void testThrowIfUnchecked_Unchecked() {
        try {
            throwIfUnchecked(new SomeUncheckedException());
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    public void testThrowIfUnchecked_Error() {
        try {
            throwIfUnchecked(new SomeError());
            fail();
        } catch (SomeError expected) {
        }
    }

    @SuppressWarnings("ThrowIfUncheckedKnownChecked")
    public void testThrowIfUnchecked_Checked() {
        throwIfUnchecked(new SomeCheckedException());
    }

    @GwtIncompatible // propagateIfPossible
    public void testPropagateIfPossible_NoneDeclared_NoneThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatDoesntThrowAnything();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect no exception to be thrown
        sample.noneDeclared();
    }

    @GwtIncompatible // propagateIfPossible
    public void testPropagateIfPossible_NoneDeclared_UncheckedThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatThrowsUnchecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the unchecked exception to propagate as-is
        try {
            sample.noneDeclared();
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible
    public void testPropagateIfPossible_NoneDeclared_UndeclaredThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatThrowsUndeclaredChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the undeclared exception to have been chained inside another
        try {
            sample.noneDeclared();
            fail();
        } catch (SomeChainingException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class)
    public void testPropagateIfPossible_OneDeclared_NoneThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatDoesntThrowAnything();
                        } catch (Throwable t) {
                            // yes, this block is never reached, but for purposes of illustration
                            // we're keeping it the same in each test
                            Throwables.propagateIfPossible(t, SomeCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect no exception to be thrown
        sample.oneDeclared();
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class)
    public void testPropagateIfPossible_OneDeclared_UncheckedThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsUnchecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t, SomeCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the unchecked exception to propagate as-is
        try {
            sample.oneDeclared();
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class)
    public void testPropagateIfPossible_OneDeclared_CheckedThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t, SomeCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the checked exception to propagate as-is
        try {
            sample.oneDeclared();
            fail();
        } catch (SomeCheckedException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class)
    public void testPropagateIfPossible_OneDeclared_UndeclaredThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsUndeclaredChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(t, SomeCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the undeclared exception to have been chained inside another
        try {
            sample.oneDeclared();
            fail();
        } catch (SomeChainingException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class, Class)
    public void testPropagateIfPossible_TwoDeclared_NoneThrown()
            throws SomeCheckedException, SomeOtherCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
                        try {
                            methodThatDoesntThrowAnything();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(
                                    t, SomeCheckedException.class, SomeOtherCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect no exception to be thrown
        sample.twoDeclared();
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class, Class)
    public void testPropagateIfPossible_TwoDeclared_UncheckedThrown()
            throws SomeCheckedException, SomeOtherCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
                        try {
                            methodThatThrowsUnchecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(
                                    t, SomeCheckedException.class, SomeOtherCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the unchecked exception to propagate as-is
        try {
            sample.twoDeclared();
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class, Class)
    public void testPropagateIfPossible_TwoDeclared_CheckedThrown() throws SomeOtherCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
                        try {
                            methodThatThrowsChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(
                                    t, SomeCheckedException.class, SomeOtherCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the checked exception to propagate as-is
        try {
            sample.twoDeclared();
            fail();
        } catch (SomeCheckedException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class, Class)
    public void testPropagateIfPossible_TwoDeclared_OtherCheckedThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
                        try {
                            methodThatThrowsOtherChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfPossible(
                                    t, SomeCheckedException.class, SomeOtherCheckedException.class);
                            throw new SomeChainingException(t);
                        }
                    }
                };

        // Expect the checked exception to propagate as-is
        try {
            sample.twoDeclared();
            fail();
        } catch (SomeOtherCheckedException expected) {
        }
    }

    public void testThrowIfUnchecked_null() throws SomeCheckedException {
        try {
            throwIfUnchecked(null);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    @GwtIncompatible // propagateIfPossible
    public void testPropageIfPossible_null() throws SomeCheckedException {
        Throwables.propagateIfPossible(null);
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class)
    public void testPropageIfPossible_OneDeclared_null() throws SomeCheckedException {
        Throwables.propagateIfPossible(null, SomeCheckedException.class);
    }

    @GwtIncompatible // propagateIfPossible(Throwable, Class, Class)
    public void testPropageIfPossible_TwoDeclared_null() throws SomeCheckedException {
        Throwables.propagateIfPossible(null, SomeCheckedException.class, SomeUncheckedException.class);
    }

    @GwtIncompatible // propagate
    public void testPropagate_NoneDeclared_NoneThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatDoesntThrowAnything();
                        } catch (Throwable t) {
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect no exception to be thrown
        sample.noneDeclared();
    }

    @GwtIncompatible // propagate
    public void testPropagate_NoneDeclared_UncheckedThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatThrowsUnchecked();
                        } catch (Throwable t) {
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect the unchecked exception to propagate as-is
        try {
            sample.noneDeclared();
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    @GwtIncompatible // propagate
    public void testPropagate_NoneDeclared_ErrorThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatThrowsError();
                        } catch (Throwable t) {
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect the error to propagate as-is
        try {
            sample.noneDeclared();
            fail();
        } catch (SomeError expected) {
        }
    }

    @GwtIncompatible // propagate
    public void testPropagate_NoneDeclared_CheckedThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void noneDeclared() {
                        try {
                            methodThatThrowsChecked();
                        } catch (Throwable t) {
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect the undeclared exception to have been chained inside another
        try {
            sample.noneDeclared();
            fail();
        } catch (RuntimeException expected) {
            assertThat(expected).hasCauseThat().isInstanceOf(SomeCheckedException.class);
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testThrowIfInstanceOf_Unchecked() throws SomeCheckedException {
        throwIfInstanceOf(new SomeUncheckedException(), SomeCheckedException.class);
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testThrowIfInstanceOf_CheckedDifferent() throws SomeCheckedException {
        throwIfInstanceOf(new SomeOtherCheckedException(), SomeCheckedException.class);
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testThrowIfInstanceOf_CheckedSame() {
        try {
            throwIfInstanceOf(new SomeCheckedException(), SomeCheckedException.class);
            fail();
        } catch (SomeCheckedException expected) {
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testThrowIfInstanceOf_CheckedSubclass() {
        try {
            throwIfInstanceOf(new SomeCheckedException() {
            }, SomeCheckedException.class);
            fail();
        } catch (SomeCheckedException expected) {
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testPropagateIfInstanceOf_NoneThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatDoesntThrowAnything();
                        } catch (Throwable t) {
                            Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect no exception to be thrown
        sample.oneDeclared();
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testPropagateIfInstanceOf_DeclaredThrown() {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect declared exception to be thrown as-is
        try {
            sample.oneDeclared();
            fail();
        } catch (SomeCheckedException expected) {
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testPropagateIfInstanceOf_UncheckedThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsUnchecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect unchecked exception to be thrown as-is
        try {
            sample.oneDeclared();
            fail();
        } catch (SomeUncheckedException expected) {
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testPropagateIfInstanceOf_UndeclaredThrown() throws SomeCheckedException {
        Sample sample =
                new Sample() {
                    @Override
                    public void oneDeclared() throws SomeCheckedException {
                        try {
                            methodThatThrowsOtherChecked();
                        } catch (Throwable t) {
                            Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
                            throw Throwables.propagate(t);
                        }
                    }
                };

        // Expect undeclared exception wrapped by RuntimeException to be thrown
        try {
            sample.oneDeclared();
            fail();
        } catch (RuntimeException expected) {
            assertThat(expected).hasCauseThat().isInstanceOf(SomeOtherCheckedException.class);
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testThrowIfInstanceOf_null() throws SomeCheckedException {
        try {
            throwIfInstanceOf(null, SomeCheckedException.class);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    @GwtIncompatible // throwIfInstanceOf
    public void testPropageIfInstanceOf_null() throws SomeCheckedException {
        Throwables.propagateIfInstanceOf(null, SomeCheckedException.class);
    }

    public void testGetRootCause_NoCause() {
        SomeCheckedException exception = new SomeCheckedException();
        assertSame(exception, Throwables.getRootCause(exception));
    }

    public void testGetRootCause_SingleWrapped() {
        SomeCheckedException cause = new SomeCheckedException();
        SomeChainingException exception = new SomeChainingException(cause);
        assertSame(cause, Throwables.getRootCause(exception));
    }

    public void testGetRootCause_DoubleWrapped() {
        SomeCheckedException cause = new SomeCheckedException();
        SomeChainingException exception = new SomeChainingException(new SomeChainingException(cause));
        assertSame(cause, Throwables.getRootCause(exception));
    }

    public void testGetRootCause_Loop() {
        Exception cause = new Exception();
        Exception exception = new Exception(cause);
        cause.initCause(exception);
        try {
            Throwables.getRootCause(cause);
            fail("Should have throw IAE");
        } catch (IllegalArgumentException expected) {
            assertThat(expected).hasCauseThat().isSameInstanceAs(cause);
        }
    }

    private static class SomeError extends Error {
    }

    private static class SomeCheckedException extends Exception {
    }

    private static class SomeOtherCheckedException extends Exception {
    }

    private static class SomeUncheckedException extends RuntimeException {
    }

    private static class SomeUndeclaredCheckedException extends Exception {
    }

    private static class SomeChainingException extends RuntimeException {
        public SomeChainingException(Throwable cause) {
            super(cause);
        }
    }

    static class Sample {
        void noneDeclared() {
        }

        void oneDeclared() throws SomeCheckedException {
        }

        void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
        }
    }

    static void methodThatDoesntThrowAnything() {
    }

    static void methodThatThrowsError() {
        throw new SomeError();
    }

    static void methodThatThrowsUnchecked() {
        throw new SomeUncheckedException();
    }

    static void methodThatThrowsChecked() throws SomeCheckedException {
        throw new SomeCheckedException();
    }

    static void methodThatThrowsOtherChecked() throws SomeOtherCheckedException {
        throw new SomeOtherCheckedException();
    }

    static void methodThatThrowsUndeclaredChecked() throws SomeUndeclaredCheckedException {
        throw new SomeUndeclaredCheckedException();
    }

    @GwtIncompatible // getStackTraceAsString(Throwable)
    public void testGetStackTraceAsString() {
        class StackTraceException extends Exception {
            StackTraceException(String message) {
                super(message);
            }
        }

        StackTraceException e = new StackTraceException("my message");

        String firstLine = quote(e.getClass().getName() + ": " + e.getMessage());
        String secondLine = "\\s*at " + ThrowablesTest.class.getName() + "\\..*";
        String moreLines = "(?:.*\n?)*";
        String expected = firstLine + "\n" + secondLine + "\n" + moreLines;
        assertThat(getStackTraceAsString(e)).matches(expected);
    }

    public void testGetCausalChain() {
        SomeUncheckedException sue = new SomeUncheckedException();
        IllegalArgumentException iae = new IllegalArgumentException(sue);
        RuntimeException re = new RuntimeException(iae);
        IllegalStateException ex = new IllegalStateException(re);

        assertEquals(asList(ex, re, iae, sue), Throwables.getCausalChain(ex));
        assertSame(sue, Iterables.getOnlyElement(Throwables.getCausalChain(sue)));

        List<Throwable> causes = Throwables.getCausalChain(ex);
        try {
            causes.add(new RuntimeException());
            fail("List should be unmodifiable");
        } catch (UnsupportedOperationException expected) {
        }
    }

    public void testGetCasualChainNull() {
        try {
            Throwables.getCausalChain(null);
            fail("Should have throw NPE");
        } catch (NullPointerException expected) {
        }
    }

    public void testGetCasualChainLoop() {
        Exception cause = new Exception();
        Exception exception = new Exception(cause);
        cause.initCause(exception);
        try {
            Throwables.getCausalChain(cause);
            fail("Should have throw IAE");
        } catch (IllegalArgumentException expected) {
            assertThat(expected).hasCauseThat().isSameInstanceAs(cause);
        }
    }

    @GwtIncompatible // Throwables.getCauseAs(Throwable, Class)
    public void testGetCauseAs() {
        SomeCheckedException cause = new SomeCheckedException();
        SomeChainingException thrown = new SomeChainingException(cause);

        assertThat(thrown).hasCauseThat().isSameInstanceAs(cause);
        assertThat(Throwables.getCauseAs(thrown, SomeCheckedException.class)).isSameInstanceAs(cause);
        assertThat(Throwables.getCauseAs(thrown, Exception.class)).isSameInstanceAs(cause);

        try {
            Throwables.getCauseAs(thrown, IllegalStateException.class);
            fail("Should have thrown CCE");
        } catch (ClassCastException expected) {
            assertThat(expected).hasCauseThat().isSameInstanceAs(thrown);
        }
    }

    @GwtIncompatible // lazyStackTraceIsLazy()
    public void testLazyStackTraceWorksInProd() {
        // TODO(b/64442212): Remove this guard once lazyStackTrace() works in Java 9+.
        Integer javaVersion = Ints.tryParse(JAVA_SPECIFICATION_VERSION.value());
        if (javaVersion != null && javaVersion >= 9) {
            return;
        }
        // Obviously this isn't guaranteed in every environment, but it works well enough for now:
        assertTrue(lazyStackTraceIsLazy());
    }

    @GwtIncompatible // lazyStackTrace(Throwable)
    public void testLazyStackTrace() {
        Exception e = new Exception();
        StackTraceElement[] originalStackTrace = e.getStackTrace();

        assertThat(lazyStackTrace(e)).containsExactly((Object[]) originalStackTrace).inOrder();

        try {
            lazyStackTrace(e).set(0, null);
            fail();
        } catch (UnsupportedOperationException expected) {
        }

        // Now we test a property that holds only for the lazy implementation.

        if (!lazyStackTraceIsLazy()) {
            return;
        }

        e.setStackTrace(new StackTraceElement[0]);
        assertThat(lazyStackTrace(e)).containsExactly((Object[]) originalStackTrace).inOrder();
    }

    @GwtIncompatible // lazyStackTrace
    private void doTestLazyStackTraceFallback() {
        assertFalse(lazyStackTraceIsLazy());

        Exception e = new Exception();

        assertThat(lazyStackTrace(e)).containsExactly((Object[]) e.getStackTrace()).inOrder();

        try {
            lazyStackTrace(e).set(0, null);
            fail();
        } catch (UnsupportedOperationException expected) {
        }

        e.setStackTrace(new StackTraceElement[0]);
        assertThat(lazyStackTrace(e)).isEmpty();
    }

    @GwtIncompatible // NullPointerTester
    public void testNullPointers() {
        new NullPointerTester().testAllPublicStaticMethods(Throwables.class);
    }
}

本文參考:
Google Guava wiki
Using and avoiding null
Preconditions
Common object methods
Ordering
Throwables
guava-tests-base
guava-tests-collect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章