避免空指针
不小心的使用 null 会导致各种各样的问题。在 Guava 中,会发现 95% 的集合,都不支持接收 null 值,让程序快速失败,而不是默默地接受 null,对开发人员来说是有帮助的。
此外,很难清晰地解释 null 返回值意味着什么。例如,Map.get(key) 可以返回 null,有两种含义,要么这个值在 map 中就是 null,要么这个值在 map 中不存在。null 可以意味着失败,也可以意味着成功。
虽然,有的时候,使用 null 也是正确的,首先,null 从内存和速度来说,是消耗很小的,而且,在集合中,也是不可避免的。但是在应用程序代码中,与库相反,它是混淆、困难和奇怪 bug 的主要来源。
由于这些原因,Guava 的许多实用工具都被设计为在出现 null 时,快速失败,而不是允许使用 null。此外,Guava 还提供了许多工具,可以在必须使用 null 时,使其更容易使用,并帮助我们避免使用 null。例如 MoreObjects 和 Strings 等
Optional
许多时候, 开发人员使用 null 表示某种不存在的场景。比如,这里应该有值,或者可能有值,实际上没有值,又或者没有找到。
Optional<T> 是一种可以替代 nullable 引用的方式,Optional 表示可能存在,也可能不存在值。
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // return true
possible.get(); // return 5
乍一看,可能与直接返回 null 没有区别,都需要判断值是否存在。
首先,Optional 就是一强制性的约定,表示可能会有不存在的场景。相对于方法直接返回一个 null 引用,时间长后,调用者可能会忘记该方法返回值可能存在空值,Optional 最起码可以提醒开发人员记得处理值缺失的场景。
当直接判断值是否存在,其实和之前差距不大, 但 Optional 还提供了 map、filter、flatMap 等更方便获取值的方法。
例如:
@Data
class User {
Address address;
}
@Data
class Address {
String province;
}
public String getProvince(String userId) {
User user = getUser(userId);
if (user != null) {
Address address = user.getAddress();
if (address != null) {
return address.getProvince();
}
}
// ...
return null;
}
使用 Optional
@Data
class User {
// 可以为空
Optional<Address> addressOptional;
}
@Data
class Address {
// 不能为空
String province;
}
public Optional<String> getProvince(User user) {
return Optional.ofNullable(user)
.flatMap(User::getAddress)
.map(Address::getProvince);
}
先决条件校验
Guava 提供了一些条件校验工具 —— Preconditions。
实例:
checkArgument(i >= 0, "Argument was %s bug expected nonegative", i);
checkArgument(i < j, "Expected i < j, but %s >= %s", i, j);
String userId = checkNotNull(userId);
这里的 checkArgument 是 静态导入的 Preconditions 的方法
其内部实现也很简单,以 checkNotNull 为例:
public static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
实践
实际的开发中,可以根据参考该写法,封装相应的校验类,抛出项目中的业务异常,再通过拦截全局异常的方式,接口返回相应的异常信息。
-
业务异常
@Data public class BusinessException extends RuntimeException { } -
Preconditions
public class Preconditions { //.... @CanIgnoreReturnValue public static <T> T checkNotNull( T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) { if (reference == null) { throw new BusinessException(format(errorMessageTemplate, errorMessageArgs)); } return reference; } @CanIgnoreReturnValue public static <T> T checkNotNull(T obj, @Nullable String errorMessage) { if (obj == null) { throw new BusinessException(errorMessage); } return obj; } //.... }
Object command methods
equals
当你的对象属性可以为 null 时,实现或者使用 equals 方法可能是一个痛苦的过程,因为必须单独检查 null。
使用 Objects.equal 可以避免空指针异常。
Objects.equal("a", "a"); // true
Objects.equal(null, "a"); // false
Objects.equal("a", null); // false
Objects.equal(null, null); // true
注意:JDK 7 中新引入的
Objects类提供了等效的Object.equal方法
hashCode
Guava 的 Objects.hashCode(Object... objs) 为指定的字段序列创建了合理的、顺序敏感的散列。可以使用 Objects.hashCode(field1, field2, ..., fieldn) 代替手工构建散列。
toString
一个好的 toString 方法,对于排查问题来说是非常有用的,但是需要重写 toString 方法。Guava 提供了 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();
根据结果可以看到,打印出来的字符串,是 Json 格式的,但效率比 Json 序列化要高得多。
其内部实现,也比较简单,保存了一个类名和属性链表,依据这两个构建的 toString
public static final class ToStringHelper {
private final String className;
private final ValueHolder holderHead = new ValueHolder();
private ValueHolder holderTail = holderHead;
private boolean omitNullValues = false;
private boolean omitEmptyValues = false;
private ToStringHelper(String className) {
this.className = checkNotNull(className);
}
public ToStringHelper add(String name, @CheckForNull Object value) {
return addHolder(name, value);
}
private ToStringHelper addHolder(String name, @CheckForNull Object value) {
ValueHolder valueHolder = addHolder();
valueHolder.value = value;
valueHolder.name = checkNotNull(name);
return this;
}
private ValueHolder addHolder() {
ValueHolder valueHolder = new ValueHolder();
holderTail = holderTail.next = valueHolder;
return valueHolder;
}
//...省略
@Override
public String toString() {
// create a copy to keep it consistent in case value changes
boolean omitNullValuesSnapshot = omitNullValues;
boolean omitEmptyValuesSnapshot = omitEmptyValues;
String nextSeparator = "";
StringBuilder builder = new StringBuilder(32).append(className).append('{');
for (ValueHolder valueHolder = holderHead.next;
valueHolder != null;
valueHolder = valueHolder.next) {
Object value = valueHolder.value;
if (valueHolder instanceof UnconditionalValueHolder
|| (value == null
? !omitNullValuesSnapshot
: (!omitEmptyValuesSnapshot || !isEmpty(value)))) {
builder.append(nextSeparator);
nextSeparator = ", ";
if (valueHolder.name != null) {
builder.append(valueHolder.name).append('=');
}
if (value != null && value.getClass().isArray()) {
Object[] objectArray = {value};
String arrayString = Arrays.deepToString(objectArray);
builder.append(arrayString, 1, arrayString.length() - 1);
} else {
builder.append(value);
}
}
}
return builder.append('}').toString();
}
}
像这里只是简单的遍历查询场景,用链表比数组效率要高
使用
StringBuilder构建字符串
compare/compareTo
当想要比较一个对象时,直接实现 Comparator 或 Comparable 接口,重写 compareTo 方法,比较需要的属性,是比较麻烦的。例如:
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:
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();
}
ComparisonChain 是一种“延迟”比较,它只执行比较,直到找到一个非零结果,然后忽略进一步的输入。
这个流式比较方式,可读性更高,也不容易出现粗心搞错的情况,不会做过多的工作。
其内部实现很有意思,内部维护了三个 ComparisonChain 单例,分别代表相等、小于、大于三种情况,当小于或大于时,再执行 comparision 方法,会直接返回小于或者大于,当等于时,直接拿入参两个引用进行比较。
处理异常
Guava 提供了 Throwables 工具类,更加简单的处理异常。
传播异常
有时候,当在 catch 捕获了一个异常,想要往上抛到下一个 try/catch 块。Throwables 提供了几个方法,更加便携的传播异常。
try {
someMethodThatCouldThrowAnything();
} catch (IKnowWhatToDoWithThisException e) {
handle(e);
} catch (Throwable t) {
Throwables.throwIfInstanceOf(t, IOException.class);
Throwables.throwIfInstanceOf(t, SQLException.class);
Throwables.throwIfUnchecked(t);
throw new RuntimeException(t);
}
下面是传播异常方法的总结:
void propagateIfPossible(Throwable, Class<X extends Throwable>) throw X:只有RuntimeException、Error、X,才会抛出throwvoid throwIfInstanceOf(Throwable, Class<X extends Exception> throws X):只有throw是X型异常时,才会抛出void throwIfUnckecked(Throwable):只有RuntimeException、Error时,才会抛出throw
异常原因链
Throwable getRootCause(Throwable)List<Throwable> getCausalChain(Throwable)String getStackTraceAsString(Throwable)