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>
值的非静态方法。
方法 | 描述 |
---|---|
booleanisPresent() | 如果此Optional包含非空实例,则返回true。 |
Tget() | 返回包含的T实例,该实例必须存在;否则,抛出IllegalStateException。 |
Tor(T) | 返回此Optional中的当前值,如果没有,则返回指定的默认值。 |
TorNull() | 返回此Optional中的当前值,如果没有,则返回null。fromNullable的逆运算。 |
SetasSet() | 如果存在实例,则返回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(intindex,intsize) | 检查index是否为具有指定大小的列表、字符串或数组的有效元素索引。元素索引的范围可以从0(含0)到size (不含) 。不直接通过列表、字符串或数组;而是只要通过它的size。返回index。 | IndexOutOfBoundsException |
checkPositionIndex(intindex,intsize) | 检查index是否为具有指定大小的列表、字符串或数组的有效位置索引。位置索引的范围可以是0(含0)到size (含) 。不直接通过列表、字符串或数组;而是只要通过它的size。返回index。 | IndexOutOfBoundsException |
checkPositionIndexes(intstart,intend,intsize) | 检查[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(Iterableiterable,intk) | 从最大到最小的顺序,根据此排序,返回指定可迭代的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提供的传播方法的快速摘要:
方法声明 | 说明 |
---|---|
RuntimeExceptionpropagate(Throwable) | 如果它是RuntimeException或Error,则按原样抛出,或者将其包装在RuntimeException中抛出。返回类型是RuntimeException,因此可以如上所述编写throwThrowables.propagate(t),Java将意识到该行肯定会抛出异常。 |
voidpropagateIfInstanceOf(Throwable,Class)throwsX | 仅当它是X的实例时,才按原样抛出该throwable。 |
voidpropagateIfPossible(Throwable) | 仅当它是RuntimeException或Error时才可以抛出throwable。 |
voidpropagateIfPossible(Throwable,Class)throwsX | 仅当它是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
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。