让使用 Java 语言变得更舒适

本文主要通过例子的方式简单介绍了一下基本工具的使用。
如果希望了解更多的细节,可以可以查看目录中的链接进行查看。
— By Syahfozy

1 使用和避免 null:null 是模棱两可的,会引起令人困惑的错误,有些时候它让人很不舒服。很多 Guava 工具类用快速失败拒绝 null 值,而不是盲目地接受

2 前置条件: 让方法中的条件检查更简单

3 常见 Object 方法: 简化 Object 方法实现,如 hashCode() 和 toString()

4 排序: Guava 强大的” 流畅风格比较器”

5 Throwables:简化了异常和错误的传播与检查

一、使用和避免null

很多 Guava 工具类用快速失败拒绝 null 值,而不是盲目地接受

大多数情况下,开发人员使用null表明的是某种缺失情形:可能是已经有一个默认值,或没有值,或找不到值。例如,Map.get返回null就表示找不到给定键对应的值

Guava用Optional表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用

1. 创建Optional实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 创建Optional实例
*/
public void makingAnOptional() {

// 创建指定引用的Optional实例,若引用为null则快速失败
Optional<Integer> possible = Optional.of(5);

// 抛出 NullPointerException
// Optional possible2 = Optional.of(null);


// 创建引用缺失的Optional实例
possible = Optional.absent();


// 创建引用缺失的Optional实例
possible = Optional.fromNullable(5);
possible = Optional.fromNullable(null);
}

2. 创建Optional实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 用Optional实例查询引用
*/
public void queryMethods() {
Optional<Integer> possible = Optional.of(5);
// 如果Optional包含非null的引用(引用存在),返回true
possible.isPresent();

// 返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException
possible.get();

// 返回Optional所包含的引用,若引用缺失,返回指定的值
possible.or(3);

// 返回Optional所包含的引用,若引用缺失,返回null
possible.orNull();

// 返回Optional所包含引用的单例不可变集
// 如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合
possible.asSet();
}

3. 其他处理null的便利方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 其他处理null的便利方法
*/
public void convenienceMethods() {

// return 5
MoreObjects.firstNonNull(5, 6);
// return 6
Optional.fromNullable(null).or(6);

/**
* 专门处理null或空字符串的方法
*/
// return null
emptyToNull("");

// return true
isNullOrEmpty("");
isNullOrEmpty(null);

// return ""
nullToEmpty(null);
// return "hello"
nullToEmpty("hello");
}

二、前置条件:让方法调用的前置条件判断更简单

Guava在Preconditions类中提供了若干前置条件判断的实用方法,每个方法都有三个变种:

  • 没有额外参数:抛出的异常中没有错误消息;
  • 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
  • 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符

1. 使用示例

1
2
3
4
5
6
7
8
// 没有额外参数:抛出的异常中没有错误消息
checkArgument(i <= 0);

// 有一个Object对象作为额外参数
checkArgument(i <= 0, "Argument Exception");

// 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象
checkArgument(i < j, "Expected i < j, but %s > %s", i, j);

2. 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 检查boolean是否为true,用来检查传递给方法的参数
* 检查失败时抛出 IllegalArgumentException
*/
checkArgument(Boolean.TRUE);

/**
* 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull
* 可以在构造函数中保持字段的单行赋值风格:this.field = checkNotNull(field)
* 检查失败时抛出 NullPointerException
*/
checkNotNull(null);

/**
* 用来检查对象的某些状态
* 检查失败时抛出 IllegalStateException
*/
checkState(Boolean.FALSE);

/**
* 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size
* 检查失败时抛出 IndexOutOfBoundsException
*/
checkElementIndex(3,3);

/**
* 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size
* 检查失败时抛出 IndexOutOfBoundsException
*/
checkPositionIndex(3, 3);

/**
* 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效
* 检查失败时抛出 IndexOutOfBoundsException
*/
checkPositionIndexes(0, 3, 3);

三、常见Object方法

1. equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 使用Objects.equal帮助执行null敏感的equals判断,从而避免抛出NullPointerException
* 注意:JDK7引入的Objects类提供了一样的方法Objects.equals
*/
public static void equalsMethod() {
// returns true********
Objects.equal("a", "a");
// returns false
Objects.equal(null, "a");
// returns false
Objects.equal("a", null);
// returns true
Objects.equal(null, null);

}

2. hashCode

1
2
3
4
5
6
7
8
9
/**
* 对传入的字段序列计算出合理的、顺序敏感的散列值
* 可以使用Objects.hashCode(field1, field2, …, fieldn)来代替手动计算散列值
* 注意:JDK7引入的Objects类提供了一样的方法Objects.hash(Object...)
*/
public static void hashCodeMethod() {
int hash = Objects.hashCode(2, "hello", null);
System.out.println(hash);
}

3. toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 轻松编写有用的toString方法
*/
public void toStringHelperMethod() {

// Returns "ClassName{x=1}"
String s1 = MoreObjects.toStringHelper(this)
.add("x", 1)
.toString();
System.out.println(s1);

// Returns "MyObject{x=1}"
String s2 = MoreObjects.toStringHelper("MyObject")
.add("x", 1)
.toString();
System.out.println(s2);

}

4. compare/compareTo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person implements Comparable<Person> {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}

/**
* 执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略
* @param that
* @return
*/
public int compareTo(Person that) {
return ComparisonChain.start()
.compare(this.age, that.age)
.compare(this.name, that.name)
.result();
}
}
public void compare() {
Person a = new Person("a", 10);
Person b = new Person("a", 10);
// Returns 1
System.out.println(b.compareTo(a));
}

四、排序: Guava强大的”流畅风格比较器”

排序器[Ordering]是Guava流畅风格比较器[Comparator]的实现,它可以用来为构建复杂的比较器,以完成集合排序的功能

从实现上说,Ordering实例就是一个特殊的Comparator实例。Ordering把很多基于Comparator的静态方法(如Collections.max)包装为自己的实例方法(非静态方法),并且提供了链式调用方法,来定制和增强现有的比较器

1. 创建排序器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 创建排序器
* 常见的排序器可以由下面的静态方法创建
*/
public static void creationMethod(){

// 对可排序类型做自然排序,如数字按大小,日期按先后排序
Ordering<String> naturalOrdering = natural();
// 按对象的字符串形式做字典排序
Ordering<Object> usingToStringOrdering = usingToString();
// 把给定的Comparator转化为排序器
Ordering<String> fromOrdering = Ordering.from(new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.hashCode() - o2.hashCode() ;
}
});
// 直接继承Ordering
Ordering<String> byLengthOrdering = new Ordering<String>() {
public int compare(String left, String right) {
return Ints.compare(left.length(), right.length());
}
};
}

2. 链式调用方法

通过链式调用,可以由给定的排序器衍生出其它排序器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 链式调用方法
* 通过链式调用,可以由给定的排序器衍生出其它排序器
* 当阅读链式调用产生的排序器时,应该从后往前读,因为每次链式调用都是用后面的方法包装了前面的排序器
* 用compound方法包装排序器时,不遵循从后往前读的原则
*/
public static void chainingMethod(){

List<String> withNullList = Lists.newArrayList(list);
withNullList.add(null);
System.out.println("withNullList:"+ withNullList);

List<List> listList = Lists.newArrayList();
listList.add(Lists.newArrayList("perter", "jerry"));
listList.add(Lists.newArrayList("perter"));
listList.add(Lists.newArrayList("perter", "jerry", null));
System.out.println("listList:"+ withNullList);

// 对可排序类型做自然排序,如数字按大小,日期按先后排序
Ordering<String> naturalOrdering = natural();
// 获取语义相反的排序器
Ordering<String> reverseOrdering = natural().reverse();
// 使用当前排序器,但额外把null值排到最前面
Ordering<String> nullsFirst = natural().nullsFirst();
// 使用当前排序器,但额外把null值排到最后面
Ordering<String> nullsLast = natural().nullsLast();
// 合成另一个比较器,以处理当前排序器中的相等情况
Ordering<People> secondaryOrdering = new PeopleAgeOrder().compound(new PeopleNameLengthOrder());
// 返回该类型的可迭代对象Iterable<T>的排序器
Ordering lexicographicalOrdering = naturalOrdering.lexicographical();
// 对集合中元素调用Function,再按返回值用当前排序器排序
Ordering<String> resultOfOrdering = natural().nullsFirst().onResultOf(new Function<String, Integer>() {
public Integer apply(String input) {
return input == null ? null : input.length();
}
});
}

3. 运用排序器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 运用排序器
* Guava的排序器实现有若干操纵集合或元素值的方法
*/
public static void applicationMethod() {

// 获取可迭代对象中最大的k个元素
System.out.println("greatestOfOrdering:"+ natural().greatestOf(list, 3));
System.out.println("leastOfOrdering:"+ natural().leastOf(list, 3));

// 判断可迭代对象是否已按排序器排序
// 允许有排序值相等的元素
System.out.println("isOrdered:"+ natural().isOrdered(natural().sortedCopy(list)));
// 判断可迭代对象是否已严格按排序器排序
// 不允许有排序值相等的元素
System.out.println("isStrictlyOrdered:"+ natural().isStrictlyOrdered(natural().sortedCopy(list)));

// 以列表形式返回指定元素的已排序副本
System.out.println("isOrdered:"+ natural().sortedCopy(list));
// 返回包含按此排序排序的元素的不可变列表
natural().immutableSortedCopy(list);

// 返回两个参数中最小的那个。如果相等,则返回第一个参数
System.out.println("min:" + natural().min("abc", "ab"));
// 返回两个参数中最大的那个。如果相等,则返回第一个参数
System.out.println("max:" + natural().max("abc", "ab"));

// 返回多个参数中最小的那个。如果有超过一个参数都最小,则返回第一个最小的参数
System.out.println("min:" + natural().min("ab", "cd", "abc"));
// 返回多个参数中最大的那个。如果有超过一个参数都最大,则返回第一个最大的参数
System.out.println("max:" + natural().max("ab", "cde", "abc"));

// 返回迭代器中最小的元素。如果可迭代对象中没有元素,则抛出NoSuchElementException
System.out.println("min:" + natural().min(list));
// 返回迭代器中最大的元素。如果可迭代对象中没有元素,则抛出NoSuchElementException
System.out.println("max:" + natural().max(list));

}

五、Throwables:简化异常和错误的传播与检查

1. 异常传播方法

Guava中的异常传播方法简要列举如下:

1
2
3
4
5
// 类型为 X 时才抛出
Throwables.throwIfInstanceOf(t, SQLException.class);

// 类型为Error或RuntimeException时抛出
Throwables.throwIfUnchecked(t);

2. 异常原因链

Guava提供了如下三个有用的方法,让研究异常的原因链变得稍微简便了,这三个方法的签名是不言自明的:

1
2
3
4
// 获取异常原因链
Throwable throwable = Throwables.getRootCause(t);
List<Throwable> throwables = Throwables.getCausalChain(t);
String str = Throwables.getStackTraceAsString(t);