guava学习笔记,google开发的开源java库,其中有很多实用、方便、高效、优化的工具,其源码更是被业界作为范本学习,可以说是java开发必备的利器。本部分为guava笔记第一部分,主要整理guava中的基本工具——Objects、Optional、Ordering、Preconditions。
简介
Guava 是什么
Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法。
Guava的好处
- 标准化 - Guava库是由谷歌托管。
- 高效 - 可靠,快速和有效的扩展JAVA标准库
- 优化 -Guava库经过高度的优化。
Objects
equals
当一个对象中的字段可以为null时,实现Object.equals方法会很痛苦,因为不得不分别对它们进行null检查。使用[Objects.equal
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Objects.html#equal(java.lang.Object, java.lang.Object))帮助你执行null敏感的equals判断,从而避免抛出NullPointerException。例如:、
1 | Objects.equal("a", "a"); // returns true |
注意:JDK7引入的Objects类提供了一样的方法[Objects.equals
](http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#equals(java.lang.Object, java.lang.Object))。
hashCode
用对象的所有字段作散列[hash]运算应当更简单。Guava的Objects.hashCode(Object...)
会对传入的字段序列计算出合理的、顺序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)
来代替手动计算散列值。
*注意:JDK7引入的Objects类提供了一样的方法Objects.hash(Object...)
toString
好的toString方法在调试时是无价之宝,但是编写toString方法有时候却很痛苦。使用 Objects.toStringHelper可以轻松编写有用的toString方法。例如:
1 | // Returns "ClassName{x=1}" |
compare/compareTo
实现一个比较器[Comparator],或者直接实现Comparable接口有时也伤不起。考虑一下这种情况:
1 | class Person implements Comparable<Person> { |
这部分代码太琐碎了,因此很容易搞乱,也很难调试。我们应该能把这种代码变得更优雅,为此,Guava提供了ComparisonChain
。
ComparisonChain执行一种懒比较:它执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略。
1 | public int compareTo(Foo that) { |
这种Fluent接口风格的可读性更高,发生错误编码的几率更小,并且能避免做不必要的工作。
Optional
避免使用null
轻率地使用null可能会导致很多令人惊愕的问题。通过学习Google底层代码库,我们发现95%的集合类不接受null值作为元素。我们认为, 相比默默地接受null,使用快速失败操作拒绝null值对开发者更有帮助。
此外,Null的含糊语义让人很不舒服。Null很少可以明确地表示某种语义,例如,Map.get(key)返回Null时,可能表示map中key的值是null,亦或map中没有该key。Null可以表示失败、成功或几乎任何情况。使用Null以外的特定值,会让你的逻辑描述变得更清晰。
Null确实也有合适和正确的使用场景,如在性能和速度方面Null是廉价的,而且在对象数组中,出现Null也是无法避免的。但相对于底层库来说,在应用级别的代码中,Null往往是导致混乱,疑难问题和模糊语义的元凶,就如同我们举过的Map.get(key)的例子。最关键的是,Null本身没有定义它表达的意思。鉴于这些原因,很多Guava工具类对Null值都采用快速失败操作,除非工具类本身提供了针对Null值的因变措施。此外,Guava还提供了很多工具类,让你更方便地用特定值替换Null值。
具体案例
不要在Set中使用null,或者把null作为map的键值。使用特殊值代表null会让查找操作的语义更清晰。
如果你想把null作为map中某条目的值,更好的办法是 不把这一条目放到map中,而是单独维护一个”值为null的键集合” (null keys)。Map 中对应某个键的值是null,和map中没有某个键,是非常容易混淆的两种情况。因此,最好把值为null的键分离开,并且仔细想想,null值的键在你的项目中到底表达了什么语义。
如果你需要在List中使用null——并且这个List的数据是稀疏的,使用Map<Integer, E>
可能会更高效,并且更准确地符合你的潜在需求。
此外,考虑一下使用自然的null对象——特殊值。举例来说,为某个enum类型增加特殊的枚举值表示null,比如java.math.RoundingMode就定义了一个枚举值UNNECESSARY,它表示一种不做任何舍入操作的模式,用这种模式做舍入操作会直接抛出异常。
如果你真的需要使用null值,但是null值不能和Guava中的集合实现一起工作,你只能选择其他实现。比如,用JDK中的Collections.unmodifiableList替代Guava的ImmutableList。
Optional
大多数情况下,开发人员使用null表明的是某种缺失情形:可能是已经有一个默认值,或没有值,或找不到值。例如,Map.get返回null就表示找不到给定键对应的值。
Guava用Optional表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用。
1 | Optional<Integer> possible = Optional.of(5); |
创建Optional实例(以下都是静态方法):
Optional.of(T) | 创建指定引用的Optional实例,若引用为null则快速失败 |
---|---|
Optional.absent() | 创建引用缺失的Optional实例 |
Optional.fromNullable(T) | 创建指定引用的Optional实例,若引用为null则表示缺失 |
用Optional实例查询引用(以下都是非静态方法):
boolean isPresent() | 如果Optional包含非null的引用(引用存在),返回true |
---|---|
T get() | 返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException |
T or(T) | 返回Optional所包含的引用,若引用缺失,返回指定的值 |
T orNull() | 返回Optional所包含的引用,若引用缺失,返回null |
Set asSet() | 返回Optional所包含引用的单例不可变集,如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合。 |
使用Optional的意义在哪儿?
使用Optional除了赋予null语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Optional迫使你积极思考引用缺失的情况,因为你必须显式地从Optional获取引用。直接使用null很容易让人忘掉某些情形,尽管FindBugs可以帮助查找null相关的问题,但是我们还是认为它并不能准确地定位问题根源。
如同输入参数,方法的返回值也可能是null。和其他人一样,你绝对很可能会忘记别人写的方法method(a,b)会返回一个null,就好像当你实现method(a,b)时,也很可能忘记输入参数a可以为null。将方法的返回类型指定为Optional,也可以迫使调用者思考返回的引用缺失的情形。
其他处理null的便利方法
当你需要用一个默认值来替换可能的null,请使用[Objects.firstNonNull(T, T)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Objects.html#firstNonNull(T, T)) 方法。如果两个值都是null,该方法会抛出NullPointerException。Optional也是一个比较好的替代方案,例如:Optional.of(first).or(second).
还有其它一些方法专门处理null或空字符串:emptyToNull(String),nullToEmpty(String),
isNullOrEmpty(String)。我们想要强调的是,这些方法主要用来与混淆null/空的API进行交互。当每次你写下混淆null/空的代码时,Guava团队都泪流满面。(好的做法是积极地把null和空区分开,以表示不同的含义,在代码中把null和空同等对待是一种令人不安的坏味道。
Preconditions
参数说明
Guava在Preconditions类中提供了若干前置条件判断的实用方法,每个方法都有三个变种:
没有额外参数:抛出的异常中没有错误消息;
有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。例如:
1
2checkArgument(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) | 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull。 | NullPointerException |
checkState(boolean) | 用来检查对象的某些状态。 | IllegalStateException |
[checkElementIndex(int index, int size)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Preconditions.html#checkElementIndex(int, int)) | 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size * | IndexOutOfBoundsException |
[checkPositionIndex(int index, int size)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Preconditions.html#checkPositionIndex(int, int)) | 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size * | IndexOutOfBoundsException |
[checkPositionIndexes(int start, int end, int size)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Preconditions.html#checkPositionIndexes(int, int, int)) | 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效* | IndexOutOfBoundsException |
索引值常用来查找列表、字符串或数组中的元素,如List.get(int), String.charAt(int);
位置值和位置范围常用来截取列表、字符串或数组,如List.subList(int,int), String.substring(int);
优势
相比Apache Commons提供的类似方法,我们把Guava中的Preconditions作为首选。Piotr Jagielski在他的博客中简要地列举了一些理由:
- 在静态导入后,Guava方法非常清楚明晰。checkNotNull清楚地描述做了什么,会抛出什么异常;
- checkNotNull直接返回检查的参数,让你可以在构造函数中保持字段的单行赋值风格:this.field = checkNotNull(field)
- 简单的、参数可变的printf风格异常信息。鉴于这个优点,在JDK7已经引入Objects.requireNonNull的情况下,我们仍然建议你使用checkNotNull。
在编码时,如果某个值有多重的前置条件,我们建议你把它们放到不同的行,这样有助于在调试时定位。此外,把每个前置条件放到不同的行,也可以帮助你编写清晰和有用的错误消息。
Ordering
排序器[Ordering]是Guava流畅风格比较器[Comparator]的实现,它可以用来为构建复杂的比较器,以完成集合排序的功能。
从实现上说,Ordering实例就是一个特殊的Comparator实例。Ordering把很多基于Comparator的静态方法(如Collections.max)包装为自己的实例方法(非静态方法),并且提供了链式调用方法,来定制和增强现有的比较器。
创建排序器
常见的排序器可以由下面的静态方法创建
方法 | 描述 |
---|---|
natural() |
对可排序类型做自然排序,如数字按大小,日期按先后排序 |
usingToString() |
按对象的字符串形式做字典排序[lexicographical ordering] |
from(Comparator) |
把给定的Comparator转化为排序器 |
实现自定义的排序器时,除了用上面的from方法,也可以跳过实现Comparator,而直接继承Ordering:
1 | Ordering<String> byLengthOrdering = new Ordering<String>() { |
链式调用方法
通过链式调用,可以由给定的排序器衍生出其它排序器
方法 | 描述 |
---|---|
reverse() |
获取语义相反的排序器 |
nullsFirst() |
使用当前排序器,但额外把null值排到最前面。 |
nullsLast() |
使用当前排序器,但额外把null值排到最后面。 |
compound(Comparator) |
合成另一个比较器,以处理当前排序器中的相等情况。 |
lexicographical() |
基于处理类型T的排序器,返回该类型的可迭代对象Iterable<T> 的排序器。 |
onResultOf(Function) |
对集合中元素调用Function,再按返回值用当前排序器排序。 |
例如,你需要下面这个类的排序器。
1 | class Foo { |
考虑到排序器应该能处理sortedBy为null的情况,我们可以使用下面的链式调用来合成排序器:
1 | Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(new Function<Foo, String>() { |
当阅读链式调用产生的排序器时,应该从后往前读。上面的例子中,排序器首先调用apply方法获取sortedBy值,并把sortedBy为null的元素都放到最前面,然后把剩下的元素按sortedBy进行自然排序。之所以要从后往前读,是因为每次链式调用都是用后面的方法包装了前面的排序器。
注:用compound方法包装排序器时,就不应遵循从后往前读的原则。为了避免理解上的混乱,请不要把compound写在一长串链式调用的中间,你可以另起一行,在链中最先或最后调用compound。
超过一定长度的链式调用,也可能会带来阅读和理解上的难度。我们建议按下面的代码这样,在一个链中最多使用三个方法。此外,你也可以把Function分离成中间对象,让链式调用更简洁紧凑。
1 | Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(sortKeyFunction); |
运用排序器
Guava的排序器实现有若干操纵集合或元素值的方法
非原创内容,转载自并发编程网 – ifeve.com。