Guava(1) Basic Utils

1.null

开销小,速度快,在对象数组中不可避免。

另一方面,歧义、奇怪的bug、混乱也不可避免,比如说在Map.get()中,null可以意味着key不存在,也可以意味着存在且值为null。

guava的很多工具包都不允许null;

在set以及map的key中,不要使用null;

在map的value中,不推荐将key不存在和value为null混在一起;

在list中,推荐使用Map<Integer,E>替代;

在枚举类中,使用指定值表达null的含义;

如果实在需要使用null,那就用,但是用不了一些禁用null的实现了,如:

使用Collections.unmodifiableList(Lists.newArrayList())替代ImmutableList.

2.Optional

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

表达一种模棱两可的状态,可能有值,也可能没有。

静态方法:

Optional.of(T); // not null
Optional.absent(); // 不存在的对象
Optional.fromNullable(T); // 将一个可能为null的值转为Optional对象

非静态方法:

boolean isPresent(); 
T get();
T or(T); // orElse(T)
T orNull(); // orElse(null)
Set<T> asSet(); // Optional.of(1) >> [1]

使用Optional的意义在于什么?

它强迫开发者正视且必须正视null的处理,null会让人轻易地回避潜在的问题。

3.Strings

emptyToNull(String);
isNullOrEmpty(String);
nullToEmpty(String);

便捷地处理string中的null。

4.preconditions

// IllegalArgumentException
checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
checkArgument(i < j, "Expected i < j, but %s >= %s", i, j);
// NullPointerException
checkNotNull(T);
// IllegalStateException
checkState(boolean);
// IndexOutOfBoundsException
checkElementIndex(int index, int size); // < size
checkPositionIndex(int index, int size); // <= size
checkPositionIndexes(int start, int end, int size); // start < 0 || end < start || end > size

直白实用。

5.Object common methods

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

顺利逃避令人痛苦的null检查。jdk7已实装。

Objects.hash(Object...);

简洁地hashCode构建方式。jdk7已实装。

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

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

这个方法还挺迷惑的,主要用于debug log,比map稍微好写点。

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
   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();
   }

简化compareTo,可读性好些。

6.Ordering

https://github.com/google/guava/wiki/OrderingExplained

一个特殊的Comparator实例,与原生Comparator相性良好。

Ordering.from(Comparator);

虽然如此,直接通过Ordering构建更为容易。

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

链的顺序是从右往左。从右往左。从右往左。

Ordering.compound()从左往右,所以不要混用。

greatestOf(Iterable iterable, int k);
isOrdered(Iterable);
sortedCopy(Iterable);
min(E...);
min(Iterable)

7.Throwables

https://github.com/google/guava/wiki/ThrowablesExplained

https://github.com/google/guava/wiki/Why-we-deprecated-Throwables.propagate

有时候可能需要捕获某种异常,然后抛出到上一层try/catch。

try {
    someMethod();
} catch (Throwable t) {
    Throwables.throwIfInstanceOf(t, IOException.class);
    Throwables.throwIfInstanceOf(t, SQLException.class);
    Throwables.throwIfUnchecked(t);
    throw new RuntimeException(t);
}

@GwtIncompatible // Class.cast, Class.isInstance
public static <X extends Throwable> void throwIfInstanceOf(
  Throwable throwable, Class<X> declaredType) throws X {
checkNotNull(throwable);
if (declaredType.isInstance(throwable)) {
  throw declaredType.cast(throwable);
}
}

public static void throwIfUnchecked(Throwable throwable) {
checkNotNull(throwable);
if (throwable instanceof RuntimeException) {
  throw (RuntimeException) throwable;
}
if (throwable instanceof Error) {
  throw (Error) throwable;
}
}

throwIfInstanceOf抛出指定类型异常的实例。

throwIfUnchecked仅抛出Error或者RuntimeException,Error是编程无法解决的错误,而RuntimeException往往是编程错误。

guava在throwable这一节特别提到了一个过时的方法:Throwables.propagate(Throwable),并详细解释了为什么弃用了这个封装。

@GwtIncompatible
@Deprecated
public static RuntimeException propagate(Throwable throwable) {
  throwIfUnchecked(throwable);
  throw new RuntimeException(throwable);
}  

一般来说,弃用一个方法,都会定义一个等效的新方法替代它,或者至少会定义成单行的,毕竟它本身就只有一行,但是这个方法没有新方法替换它,它的等效实现是:

throwIfUnchecked(e);
throw new RuntimeException(e);

这个封装好的propagate方法在Google内部,有超过10000次调用。

尽管作用有限,调用得足够多的话,也足以发挥作用。

但是实际上有75%的调用都用它来抛出一个已检查异常,这是违背方法初衷的。

try {
  return something(...);
} catch (IOException e) {
  throw Throwables.propagate(e);
}

因为它就相当于。

try {
  return something(...);
} catch (IOException e) {
  throw new RuntimeException(e);
}

又或者拿它来抛出一个未检查异常。

try {
  return something(...);
} catch (CancellationException e) {
  throw Throwables.propagate(e);
}

这也相当于。

try {
  return something(...);
} catch (CancellationException e) {
  throw e;
}

也就是说,75%的调用反而造成了冗余。

而剩下的25%中,也有很多情况可以通过直接throw new RuntimeException(e)解决,不管它是不是已检查的,用RuntimeException包一层抛出都是可以接受的。

还有一个不好的点是,在多层嵌套中捕获异常再抛出。

在不使用propagate的情况下,有了如下简化。

try {
  return something(...);
} catch (CancellationException e) {
  throw e;
}

但是很明显,它是一个未检查异常,完全可以不做捕获。

return something(...);

实际上推荐的propagate用法如下。

throw Throwables.propagate(e);

这么写的话,编译器马上就知道这行代码后面的内容不会被执行。大部分人都是这么用的,但是还是有一部分人写成了这样。

Throwables.propagate(e);
return new MyType<Something>(
    ImmutableList.<Foo>of(),
    ImmutableList.<Bar>of(),
    Something.empty()); // unreachable

只要使用throw就可以规避这种写法,它的起因就是propagate本身。

考虑如下代码。

try {
  return callable.call();
} catch (AssertionError e) {
  int delay = retryStrategy.getDelayMillis(tries);
  if (delay >= 0) {
    try {
      Thread.sleep(delay);
    } catch (InterruptedException ie) {
      throw Throwables.propagate(e);
    }
  }
}

注意到propagate抛出的是e而不是ie,这种实现很容易让人误解又迷惑。

又或者是这样的代码。

try {
  return something(...);
} catch (SomeException e) {
  Throwables.propagate(e);
  log.log(SEVERE, "error", e);
  return default;
}

一些人会认为propagate做了一些比throw更多的事情,实际上并没有。

为什么guava要定义一个跟throw差不多的方法,又为什么它的命名偏偏不是throwXXX而是propagate。

同样令人迷惑的还有这样的代码。

private Something foo() throws IOException {
  try {
    return something(...);
  } catch (IOException e) {
    log.log(SEVERE, "error", e);
    Throwables.propagate(e);
  }
}

很明显,方法体上压根就不需要抛出IOException。

但是写这段代码的人似乎期望propagate原样抛出IOException,ta可能以为propagate是这样的。

<X extends Throwable> void propagate(X x) throws X;

又或者ta是初学者,并不知道这种写法是否可行。

又或者ta本意如此,只是忘记删掉方法体上抛出的IOException。

这些意图都会因为propagate的存在变得不可捉摸,让人迷惑。

又或者是。

logAndRethrow(Throwables.propagate(e))

实际上logAndRethrow永远不会被调用。

另一方面,propagate变相鼓励了”不关心异常处理”,这不是guava希望看到的。

如果不使用propagate,可以怎么做呢?

  • 使用throw e / RuntimeException(e)完美替换,基本不影响原实现;

  • 使用限定场景下更高级更合适的API,比如Futures.getUnchecked等;

  • 自定义相似实现,继续使用。

从这篇弃用说明可以看到,guava的开发者并非制定规范,更多地是通过行为观察、大量的代码分析,尝试让使用者觉得天生就该有这么个工具类/包/功能。在命名上尤其苛刻,力求准确、无歧义,哪怕像propagate这样就两行代码的方法也能被注意到并详细分析。