文章目錄
- 1.[使用和避免使用null](https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained)
- 2.[前置條件](https://github.com/google/guava/wiki/PreconditionsExplained)
- 3.[通用對象方法](https://github.com/google/guava/wiki/CommonObjectUtilitiesExplained)
- 4.[排序](https://github.com/google/guava/wiki/OrderingExplained)
- 5.[異常](https://github.com/google/guava/wiki/ThrowablesExplained)
Guava項目包含一些我們在基於Java的項目中依賴的Google核心庫:集合[collections]、緩存[caching]、原生類型支持[primitives support]、併發庫[concurrency libraries]、通用註解[common annotations]、字符串處理[string processing]、I/O等。這些工具中的每一種確實每天都會被Google員工用於生產服務中。
但是,通過Javadoc並不總是最有效的去學習如何充分使用庫。在這裏,我們嘗試提供一些Guava最流行和最強大的功能的可讀且令人愉快的解釋說明。
該Wiki正在開發中,部分內容可能仍在建設中。
基本工具:讓使用Java語言更加愉快。
- 使用和避免使用null:
null
可能會造成歧義,導致混亂的錯誤,有時甚至是令人不快的。許多Guava實用工具拒絕null並快速失敗,而不是盲目地接受它們。 - 前置條件:更輕鬆地測試方法的前置條件。
- 通用對象方法:簡化實現
Object
的方法,例如hashCode()
和toString()
。 - 排序:Guava功能強大的"流利
Comparator
"類。Guava’s powerful "fluentComparator
" 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個變體:
- 沒有額外參數。拋出異常而不會出現錯誤消息。
- 一個額外的
Object
參數。拋出異常使用object.toString()
作爲錯誤消息。 - 一個額外的
String
參數,帶有任意數量的Object
對象參數。行爲類似於printf,但是爲了GWT的兼容性和效率,它僅允許%s
指示符。- 注意:
checkNotNull
,checkArgument
和checkState
具有大量重載,這些重載採用原始參數和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塊。對於RuntimeException
或Error
實例,通常是這種情況,它們不需要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) |
如果它是RuntimeException 或Error ,則按原樣拋出,或者將其包裝在RuntimeException 中拋出。返回類型是RuntimeException ,因此可以如上所述編寫throw Throwables.propagate(t) ,Java將意識到該行肯定會拋出異常。 |
void propagateIfInstanceOf(Throwable, Class) throws X |
僅當它是X 的實例時,才按原樣拋出該throwable 。 |
void propagateIfPossible(Throwable) |
僅當它是RuntimeException 或Error 時纔可以拋出throwable 。 |
void propagateIfPossible(Throwable, Class) throws X |
僅當它是RuntimeException ,Error 或X 時,纔可以按原樣拋出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
編寫代碼很容易。特別要注意的是,上述模式僅適用於RuntimeException
和Error
。如果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
可能會引起混淆,因爲它不僅傳播給定類型的參數,而且傳播Errors
和RuntimeExceptions
。)
這種模式(或類似的變體,例如引發throw new RuntimeException(t)
)在Google的代碼庫中顯示了約30次。(搜索'propagateIfPossible[^;]* Exception.class[)];'
)它們中的絕大多數採用顯式throw new RuntimeException(t)
方法。對於Throwable
到Exception
的轉換,我們可能需要一個throwWrappingWeirdThrowable
方法,但是考慮到兩行替代,除非我們也要棄用propagateIfPossible
,否則可能不需要太多。
5.2.3有爭議的Throwables.propagate
用途
5.2.3.1有爭議的:將檢查的異常轉換爲未檢查的異常
原則上,未檢查的異常表示錯誤,而檢查的異常表示無法控制的問題。實際上,即使JDK有時也會出錯Object、Integer、URI(或者至少對於某些方法,沒有答案適合每個人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());
}
這裏有很多事情要考慮:
-
可能是經過檢查的異常導致。請參閱上面的“將檢查的異常轉換爲未檢查的異常”。但是,如果已知該任務不拋出已檢查異常,該怎麼辦?(也許這是
Runnable
的結果)如上所述,可以捕獲異常並拋出AssertionError
;propagate
沒什麼好提供的。特別是對於Future
,還要考慮Futures.get
。 -
可能是非
Exception
,非Error
的Throwable
導致。(嗯,實際上不可能是一個,但是如果嘗試直接將其重新拋出,編譯器將迫使您考慮這種可能性。)請參見上面的"從throwable Throwable
轉換爲throws Exception
"。 -
可能是未經檢查的異常或錯誤導致。如果是這樣,它將直接重新拋出。不幸的是,它的堆棧跟蹤將反映最初在其中創建異常的線程,而不是當前正在其中傳播的線程。通常最好在異常鏈中包含兩個線程的堆棧跟蹤,如
get
拋出的ExecutionException
一樣。(此問題實際上與propagate
無關;它與在不同線程中重新拋出異常的任何代碼有關。)
5.3因果鏈
Guava使研究異常的因果鏈更爲簡單,它提供了三種有用的方法,其方法簽名是不言自明的:
Throwable getRootCause(Throwable)
List getCausalChain(Throwable)
String getStackTraceAsString(Throwable)
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