Google Guava与基本工具操作相关的类

Guava项目包含一些我们在基于Java的项目中依赖的Google核心库:集合[collections]、缓存[caching]、原生类型支持[primitives support]、并发库[concurrency libraries]、通用注解[common annotations]、字符串处理[string processing]、I/O等。这些工具中的每一种确实每天都会被Google员工用于生产服务中。

但是,通过Javadoc并不总是最有效的去学习如何充分使用库。在这里,我们尝试提供一些Guava最流行和最强大的功能的可读且令人愉快的解释说明。

该Wiki正在开发中,部分内容可能仍在建设中。

基本工具:让使用Java语言更加愉快。

  • 使用和避免使用nullnull可能会造成歧义,导致混乱的错误,有时甚至是令人不快的。许多Guava实用工具拒绝null并快速失败,而不是盲目地接受它们。
  • 前置条件:更轻松地测试方法的前置条件。
  • 通用对象方法:简化实现Object的方法,例如hashCode()toString()
  • 排序:Guava功能强大的"流利Comparator"类。Guava’s powerful "fluent Comparator" class.
  • 异常:简化了异常和错误的传播与检查。

1.使用和避免使用null

1.1Optional

程序员使用null的许多情况是表示某种缺席:也许在可能有值、无值或找不到值的地方。例如,当找不到键值时,Map.get返回null

Optional<T>是用非空值替换可空的T引用的一种方法。Optional可以包含非空T引用(在这种情况下,我们称该引用为“存在”),也可以不包含任何内容(在这种情况下,我们称该引用为“不存在”)。它从来不说“包含null”。

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

Optional打算直接模拟其它编程环境中的任何现有“option”或“maybe”构造,尽管它可能有一些相似之处。

我们在这里列出了一些最常见的Optional操作。

1.1.1创建Optional

这些都是Optional上的静态方法。

方法 描述
Optional.of(T) 使Optional包含给定的非null值,或者在null上快速失败。
Optional.absent() 返回某种引用缺失的Optional。
Optional.fromNullable(T) 将给定的可能为null的引用转换为Optional,将non-null视为存在,将null视为不存在。

1.1.2查询方法

这些都是基于特定Optional<T>值的非静态方法。

方法 描述
boolean isPresent() 如果此Optional包含非空实例,则返回true。
T get() 返回包含的T实例,该实例必须存在;否则,抛出IllegalStateException。
T or(T) 返回此Optional中的当前值,如果没有,则返回指定的默认值。
T orNull() 返回此Optional中的当前值,如果没有,则返回null。fromNullable的逆运算。
Set asSet() 如果存在实例,则返回Optional所包含此实例的不可变单例Set,否则返回一个空的不可变Set。

Optional提供了除这些方法之外的更多便捷实用方法。有关详细信息,请查阅Javadoc。

1.1.3重点是什么?

除了为null命名而提高了可读性之外,Optional的最大优点是它的防白痴。如果你要完全编译程序,它会迫使你积极考虑不存在的情况,因为你必须主动打开Optional并解决该情况。令人不安的是,Null很容易让人忘记一些事情,尽管FindBugs有所帮助,但我们认为它没有很好的解决问题。

当你返回的值可能存在或不存在时,这一点尤其重要。你(和其他人)更可能忘记在实现other.method时,other.method(a, b)可能返回null值,而你可能忘记了a可能为null。返回Optional使调用者无法忘记这种情况,因为调用者必须自己打开对象去编译代码。

1.2便利方法

每当你希望将null值替换为某些默认值时,请使用MoreObjects.firstNonNull(T, T)。就像方法名称所暗示的那样,如果两个输入都为null,则它会由于NullPointerException而快速失败。如果你使用的是Optional,则有更好的选择——例如,first.or(second)

字符串中提供了一些处理可能为空的字符串值的方法。具体来说,我们提供恰当的名称:

我们想强调的是,这些方法主要用于与使null字符串和空字符串等同看待的令人不愉快的API联合使用。每次你编写将null字符串和空字符串合在一起的代码时,Guava团队都会哭泣。(如果null字符串和空字符串表示的是完全不同的东西,那会更好,但是将它们视为同一种东西是一种令人不安的常见代码坏味道)

1.3使用示例

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

2.前置条件

2.1介绍

Guava提供了许多前置条件检查工具。每个方法有3个变体:

  1. 没有额外参数。抛出异常而不会出现错误消息。
  2. 一个额外的Object参数。抛出异常使用object.toString()作为错误消息。
  3. 一个额外的String参数,带有任意数量的Object对象参数。行为类似于printf,但是为了GWT的兼容性和效率,它仅允许%s指示符。
    • 注意:checkNotNullcheckArgumentcheckState具有大量重载,这些重载采用原始参数和Object参数而不是可变数组的组合——这样,在大多数情况下,上述调用都可以避免原始装箱和可变数组分配

第三种变体的示例:

checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
checkArgument(i < j, "Expected i < j, but %s >= %s", i, j);
方法声明(不包括额外参数) 描述 失败时抛出异常
checkArgument(boolean) 检查boolean是否为true。用于验证方法的参数。 IllegalArgumentException
checkNotNull(T) 检查值是否不为null。直接返回值,因此可以内联使用checkNotNull(value) NullPointerException
checkState(boolean) 检查对象的某些状态,而不依赖于方法参数。例如,一个Iterator可能会使用它来检查在调用任何remove之前是否已调用next IllegalStateException
checkElementIndex(int index, int size) 检查index是否为具有指定大小的列表、字符串或数组的有效元素索引。元素索引的范围可以从0(含0)到size(不含)。不直接通过列表、字符串或数组;而是只要通过它的size。返回index IndexOutOfBoundsException
checkPositionIndex(int index, int size) 检查index是否为具有指定大小的列表、字符串或数组的有效位置索引。位置索引的范围可以是0(含0)到size(含)。不直接通过列表、字符串或数组;而是只要通过它的size。返回index IndexOutOfBoundsException
checkPositionIndexes(int start, int end, int size) 检查[start, end)是否为具有指定大小的列表、字符串或数组的有效子范围。带有自己的错误消息。 IndexOutOfBoundsException

出于几个原因,我们倾向于使用我们自己的先决条件检查,而不是Apache Commons中的类似工具。简要地:

  • 静态导入后,Guava方法清晰明了。checkNotNull可以清楚地说明正在执行的操作以及将抛出的异常。
  • checkNotNull在验证后返回其参数,从而允许在构造函数中使用简单的一列式:this.field = checkNotNull(field);
  • 简单的可变参数"printf样式"异常消息。(这也是我们建议继续对Objects.requireNonNull使用checkNotNull的原因)

我们建议你将前置条件分成不同的行,这可以帮助你确定调试时哪个前置条件失败。此外,你应该提供有用的错误消息,当每项检查都在自己的行上时,会更容易。

2.2使用示例

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    private static final String NON_NULL_STRING = "foo";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    private static class Message {
        boolean invoked;

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

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

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

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

3.通用对象方法

3.1equals

当对象字段可以为null时,实现Object.equals会很麻烦,因为必须分别检查null。使用Objects.equal可以以null敏感的方式执行equals检查,没有NullPointerException的风险。

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

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

3.2hashCode

Object对象的所有字段作散列(hash)应该更简单。Guava的Objects.hashCode(Object ...)为指定的字段序列创建一个合理的,顺序敏感的哈希。使用Objects.hashCode(field1, field2, ..., fieldn)代替手动构建哈希。

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

3.3toString

好的toString方法在调试中可能是无价的,但是编写起来很麻烦。使用MoreObjects.toStringHelper()轻松创建有用的toString

示例:

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

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

3.4compare/compareTo

实现Comparator比较器或直接实现Comparable接口可能很麻烦。

考虑一下这种情况:

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

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

这段代码很容易弄乱,难以扫描错误,而且冗长。应该把这种代码变得更优雅,为此,Guava提供了ComparisonChain

ComparisonChain执行“惰性”比较:它仅执行比较,直到找到非零结果为止,此后它将忽略进一步的输入。

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

这种流利的习惯用法更具可读性,不容易出现偶然的错误,并且足够聪明,不用去做必须的更多的工作。其它比较实用工具可以在Guava的“流利比较器”类Ordering中找到,在此进行解释。

3.5使用示例

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

public class ObjectsTest extends TestCase {

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

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

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

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

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

}

4.排序

4.1示例

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

4.2概述

Ordering排序是Guava的“流利”比较器Comparator类,可用于构建复杂的比较器并将其应用于对象集合。

从本质上讲,Ordering实例不过是一个特殊的Comparator实例。Ordering排序仅采用依赖于Comparator的方法(例如Collections.max),并使它们作为实例方法可用。为了增加功能,Ordering类提供了链式调用方法来调整和增强现有的比较器。

4.3创建

静态方法提供了常见的排序方式:

方法 描述
natural() 对可比较类型使用自然排序。
usingToString() 按对象的字符串表示形式按字典顺序对对象进行比较,该对象由toString()返回。

使一个预先存在的Comparator比较器成为Ordering就像使用Ordering.from(Comparator)一样简单。

但是创建自定义Ordering的更常见方法是完全跳过Comparator,而直接扩展Ordering抽象类:

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

4.4链式调用

可以包装给定的Ordering以获得派生的排序器。一些最常用的变体包括:

方法 描述
reverse() 返回相反的排序器
nullsFirst() 返回一个Ordering在非null元素之前对null进行排序,否则其行为与原始Ordering相同。参见nullsLast()
compound(Comparator) 合成另一个比较器,以处理当前排序器中的相等情况。
lexicographical() 返回一个Ordering,根据元素按字典顺序对可迭代对象进行排序。
onResultOf(Function) 返回一个Ordering通过函数返回结果,然后使用原始Ordering比较对返回值进行排序。

例如,假设想要该类的比较器…

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

…可以处理sortedBy的空值。这是建立在链式方法之上的解决方案:

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

当阅读Ordering的链式调用方法,应从右到左向后阅读。上面的示例通过查找Foo实例的sortedBy字段值对它们进行排序,首先将所有空的sortedBy值移到顶部,然后通过自然字符串排序对其余值进行排序。之所以会出现这种向后顺序,是因为每个链接调用都将先前的Ordering顺序“包装”到一个新的顺序中。

“向后”规则的例外:对于compound的调用链,请从左至右读取。为避免混淆,请避免将compound调用与其它链式调用混在一起。

链式调用超过一定长度可能很难理解。如上例所示,建议将链式调用限制为大约三个。此外,也可以通过分离中间对象(例如Function实例)来简化代码:

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

4.5应用

Guava提供了多种方法使用排序来操纵或检查值或集合。在这里列出了一些最常用的:

方法 描述 另请参见
greatestOf(Iterable iterable, int k) 从最大到最小的顺序,根据此排序,返回指定可迭代的k个最大元素。不一定稳定。 leastOf
isOrdered(Iterable) 测试指定的Iterable是否按照此顺序为非递减顺序。 isStrictlyOrdered
sortedCopy(Iterable) List列表的形式返回指定元素的排序副本。 immutableSortedCopy
min(E, E) 根据此顺序返回其两个参数中的最小值。如果值比较相等,则返回第一个参数。 max(E, E)
min(E, E, E, E...) 根据此顺序返回其参数的最小值。如果存在多个最小值,则返回第一个。 max(E, E, E, E...)
min(Iterable) 返回指定Iterable的最小元素。如果Iterable为空,则抛出NoSuchElementException max(Iterable), min(Iterator), max(Iterator)

4.6使用示例

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

public class OrderingTest extends TestCase {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        private static final long serialVersionUID = 0;
    }
  
}

5.异常

Guava的Throwables 实用工具可以简化异常处理。

5.1传播

有时,当捕获异常时,想将其抛出返回到下一个try/catch块。对于RuntimeExceptionError实例,通常是这种情况,它们不需要try/catch块,但是当它们不希望被抛出时可以用try/catch块捕获。

Guava提供了几种实用程序来简化传播异常。例如:

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

这些方法各自都会抛出异常,但是也能抛出方法返回的结果——例如,throw Throwables.propagate(t)——有助于向编译器证明将抛出异常。

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

方法声明 说明
RuntimeException propagate(Throwable) 如果它是RuntimeExceptionError,则按原样抛出,或者将其包装在RuntimeException中抛出。返回类型是RuntimeException,因此可以如上所述编写throw Throwables.propagate(t),Java将意识到该行肯定会抛出异常。
void propagateIfInstanceOf(Throwable, Class) throws X 仅当它是X的实例时,才按原样抛出该throwable
void propagateIfPossible(Throwable) 仅当它是RuntimeExceptionError时才可以抛出throwable
void propagateIfPossible(Throwable, Class) throws X 仅当它是RuntimeExceptionErrorX时,才可以按原样抛出throwable

5.2Throwables.propagate用法

5.2.1模拟Java 7多重捕获并重新抛出

通常,如果想让异常在堆栈中传播,则根本不需要catch块。由于你不会从异常中恢复,因此你可能不应该记录该异常或采取其他措施。你可能需要执行一些清除操作,但是通常无论操作是否成功,都需要进行清除操作,因此该操作最终会在finally块中结束。但是,重新抛出的catch块有时很有用:也许你必须在传播异常之前更新失败计数,或者可能只想有条件地传播异常。

仅处理一种异常时,捕获和重新抛出异常就很简单。当处理多种异常时,它变得凌乱:

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

Java 7通过multicatch解决了这个问题:

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

非Java 7用户被卡住了。他们想编写如下代码,但是编译器不允许他们抛出Throwable类型的变量:

(这种写法把原本是Error或RuntimeException类型的异常修改成了Throwable,因此调用者不得不修改方法签名)

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

解决方案是用throw Throwables.propagate(t)替换throw t。在这种有限的情况下,Throwables.propagate的行为与原始代码相同。但是,使用具有其他隐藏行为的Throwables.propagate编写代码很容易。特别要注意的是,上述模式仅适用于RuntimeExceptionError。如果catch块可能捕获了受检查的异常,则还需要调用propertyIfInstanceOf以保持行为,因为Throwables.propagate无法直接传播受检查的异常。

总体而言,对传播的这种使用很一般。在Java 7中是不必要的。在其他版本下,它可以节省少量重复,但是可以简化简单的“提取方法”重构。此外,使用propagate容易意外包装检查的异常

5.2.2不必要:从"throws Throwable"转换为"throws Exception"

少数API,特别是Java反射API和(以此为基础的)JUnit,声明了抛出Throwable的方法。与这些API交互可能会很痛苦,因为即使是最通用的API通常也只能声明throws Exception。一些知道其具有非EXception异常,非Error错误的调用者使用Throwables.propagate转化Throwable。这是一个声明执行JUnit测试的Callable的示例:

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

  return null;
}

此处无需进行propagate(),因为第二行等效于throw new RuntimeException(t)。(题外话:这个示例还提醒我们,propagateIfPossible可能会引起混淆,因为它不仅传播给定类型的参数,而且传播ErrorsRuntimeExceptions。)

这种模式(或类似的变体,例如引发throw new RuntimeException(t))在Google的代码库中显示了约30次。(搜索'propagateIfPossible[^;]* Exception.class[)];')它们中的绝大多数采用显式throw new RuntimeException(t)方法。对于ThrowableException的转换,我们可能需要一个throwWrappingWeirdThrowable方法,但是考虑到两行替代,除非我们也要弃用propagateIfPossible,否则可能不需要太多。

5.2.3有争议的Throwables.propagate用途

5.2.3.1有争议的:将检查的异常转换为未检查的异常

原则上,未检查的异常表示错误,而检查的异常表示无法控制的问题。实际上,即使JDK有时也会出错ObjectIntegerURI(或者至少对于某些方法,没有答案适合每个人URI)。

结果,调用者有时不得不在异常类型之间进行转换:

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

有时,这些调用者使用Throwables.propagate。缺点是什么?

最主要的是,代码的含义不太明显。Throwables.propagate(ioException)会做什么?throw new RuntimeException(ioException)有什么作用?两者的作用相同,但后者更直接。前者提出了一个问题:“这是做什么的?它不仅包装在RuntimeException中,是吗?如果是,为什么他们要编写一个方法包装器?”

诚然,这里的部分问题是“propagate”是一个模糊的名称。(这是抛出未声明异常的一种方法吗?)也许“ wrapIfChecked”会更好。即使调用了该方法,在已知的已检查异常上调用它没有任何好处。甚至还有其它缺点:也许有比普通的RuntimeException更合适的类型供抛出-例如IllegalArgumentException

有时,当仅异常可能是已检查的异常时,有时还会使用propagate。结果比替代方法小一些,直接性也小一些:

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

然而,这里是将已检查的异常转换为未检查的异常的一般做法。在某些情况下,无疑这是正确的做法,但更经常地,它是用来避免处理合法的已检查异常。这使我们开始辩论有关检查异常是否总体上是一个坏主意。我不想在这里讨论所有内容。可以说,Throwables.propagate不存在为了鼓励Java用户忽略IOException等的目的。

5.2.3.2有争议的:异常隧道

但是,当要实现不允许抛出异常的方法时,该怎么办?有时,需要将异常包装在未经检查的异常中。很好,但同样,对于简单包装而言,不需要propagate。实际上,手动包装可能是更好的选择:如果包装每个异常(而不是仅检查的异常),则可以在另一端解包每个异常,从而减少特殊情况。此外,可能希望对包装使用自定义异常类型。

5.2.3.3有争议的:从其它线程重新抛出异常

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

这里有很多事情要考虑:

  1. 可能是经过检查的异常导致。请参阅上面的“将检查的异常转换为未检查的异常”。但是,如果已知该任务不抛出已检查异常,该怎么办?(也许这是Runnable的结果)如上所述,可以捕获异常并抛出AssertionError;propagate没什么好提供的。特别是对于Future,还要考虑Futures.get

  2. 可能是非Exception,非ErrorThrowable导致。(嗯,实际上不可能是一个,但是如果尝试直接将其重新抛出,编译器将迫使您考虑这种可能性。)请参见上面的"从throwable Throwable转换为throws Exception"。

  3. 可能是未经检查的异常或错误导致。如果是这样,它将直接重新抛出。不幸的是,它的堆栈跟踪将反映最初在其中创建异常的线程,而不是当前正在其中传播的线程。通常最好在异常链中包含两个线程的堆栈跟踪,如get抛出的ExecutionException一样。(此问题实际上与propagate无关;它与在不同线程中重新抛出异常的任何代码有关。)

5.3因果链

Guava使研究异常的因果链更为简单,它提供了三种有用的方法,其方法签名是不言自明的:

5.4使用示例

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

public class ThrowablesTest extends TestCase {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    private static class SomeError extends Error {
    }

    private static class SomeCheckedException extends Exception {
    }

    private static class SomeOtherCheckedException extends Exception {
    }

    private static class SomeUncheckedException extends RuntimeException {
    }

    private static class SomeUndeclaredCheckedException extends Exception {
    }

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

    static class Sample {
        void noneDeclared() {
        }

        void oneDeclared() throws SomeCheckedException {
        }

        void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {
        }
    }

    static void methodThatDoesntThrowAnything() {
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if (!lazyStackTraceIsLazy()) {
            return;
        }

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

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

        Exception e = new Exception();

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

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

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

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

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

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