用户指南
Guava项目包含几个在我们基于java项目中依赖的Google的核心类库:集合(collections),缓存(caching),原始类型支持,并发库,常用注解,字符串处理,I/O等等。这些工具每天都正在被google人在生产服务中使用。
但是全面研究javadoc并不总是学习如何最好地利用库的有效方法。这里,我们尝试提供的可读的,惬意的说明,一些最受欢迎和功能强大的功能的Guava。
这个viki还在进行中,部分内容可能仍在建设中
-
基本工具:使使用Java更加愉快。
-
集合:Guava对JDK集合生态系统的扩展。这些是Guava最受欢迎最成熟的部分。
-
各种图:图结构数据建模类库,即,实体和他们之间的关系。关键特性包含:
-
缓存:本地缓存,处理得当,支持各种各样的过期行为。
-
函数习惯:如果谨慎使用,Guava的函数习惯可以明显地简化代码。
-
并发(Concurrency):功能强大,简单抽象能够更容易写正确的并发代码。
- ListenableFeture:当完成时带有回调的Feture。
- Service:启动和关闭的东西,为你照顾困难的状态逻辑。
-
字符串:一个非常有用的字符串实用程序。分隔,拼接,填充等等。
-
基本数据类型:JDK没有提供的基本数据类型的操作,
int
和char
。包含一些类型无符号变量。 -
范围:Guava强大的API,用于处理
Comparable
上的范围,包含连续类型和离散类型。 -
I/O:简化I/O操作,特别是在整个I/O流和文件上,适用于Java 5和6。
-
哈希:比
Object.hashCode()
所提供的更复杂的哈希的工具,包括Bloom过滤器。 -
事件总线:在组件之间的发布订阅模式通信,无需组件显性地注册另外一个。
-
数学:JDK没有提供的,经过优化,彻底测试过的数学实用工具。
-
反射:Guava的Java的反射功能的工具。
-
提示:使用Guava使你的应用程序按照你希望的方式工作。
- 理念:Guava是什么,不是什么,以及目标。
- 在你的构建中使用Guava:使用构建系统,包含:Maven,Gradle等等。
- 使用ProGuard:避免绑定部分Guava,你没有使用你的JAR。
- Apache common 等价功能:帮助你从Apache Commons Collections转换代码。
- 兼容性:Guava版本之间的明细。
- 想法墓地:已经被断然拒绝的功能请求。
- 合作伙伴:我们喜欢和欣赏的开源项目。
- 如何贡献:如何向Guava贡献代码。
注意:要讨论此viki的内容,请使用Guava讨论邮件列表。
基本工具
使用和防止Null
“Null sucks” - Doug Lea
“I call it my billion-dollar mistake” - Sir C. A. R. Hoare ,关于他发明的空引用
null
的疏忽使用可能导致各种各样令人错愕的bug。通过研究Google代码库,我们发现像95%的集合中不应该有任何null值,并且应该快速失败,而不是默默接收null
值,这对开发人员是有帮助的。
此外,null
是令人讨厌的模糊。null
返回值的含义并不明显。–例如,Map.get(key)
可能返回null
,或者因为值在map中为null
,或者值没有在map中。Null可以代表失败,可以代表成功,可以代表任何东西。使用非null
的东西可以使你的含义更清晰。
也就是说,有时候使用null也是正确的。对内存和速度而言,null
是廉价的,在对象数组中它是不可避免的。但是在应用程序代码中,与类库不同的是,它是混淆,困难和奇怪的bug以及令人不快的歧义的主要来源–例如,当Map.get
返回null,它可以表示值是不存在的,或者值存在且为null,最重要的一点,null没有指明null值的含义。
出于这些原因,许多Guava工具被设计成在null出现时快速失败,而不是允许null被使用,只要有null值友好的解决方案可用。此外,Guava提供多个工具,既可以在必要时使使用null
更容易,也可以帮助你避免使用null
。
具体案例
如果你在尝试在Set
中或者在map中使用null
值,或者作为Map
中键–不要这么做。如果在查找操作期间显示地使用特殊情况下的null
它会更清晰(不那么令人惊讶)。
如果你想要使用null
作为map的值,不考虑此项;保留一个单独的Set的非空键(或空键);它是非常容易混淆Map
包含一个值为null
的key的条目的情况和Map
没有此key的条目。最好将这些键分开,并考虑当这些值与为null
的key关联时,对于你的应用程序的意义。
如果你正在List
中使用null,如果list是稀疏的,你是否更愿意使用Map<Integer,E>
?这可能实际上更有效,可能更精确地匹配你应用程序的需求。
考虑是否有一个天然的“空对象”可以使用。并不总是这样。有时候是的。例如,如果它是一个枚举,添加一个常量来表示你期望的null值在这里表示的任何意义。例如,java.math.RoundingMode
有UNNECESSARY
来表示不取舍,而且如果取舍是必须的,则会抛出一个异常。
如果你确实需要null值,并且使用了敌视null值的集合实现,使用不同的实现。例如,使用Collections.unmodifiableList(Lists.newArrayList())
代替ImmutableList
。
Optional
许多情况,程序员使用null
表示某种形式的缺席:也许有值的地方,一个值也没有,或者找不到。例如,当key找不到值时,Map.get
返回null
。
Optional<T>
是使用一个非空的值替换一个可空的T
引用,Optional
可能包含一个非空的引用(此情况称为引用是“存在的”),或者什么也没包含(此情况称为“不存在”)。它从来不称为“包含null”。
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Optional
并不打算作为其他程序环境已有的“option”或者“maybe”结构的类似物,尽管它有一些类似之处。
这里我们列出一些最常用的Optional
操作。
创建一个Optional
这些都是在Optional
上的静态方法:
方法 | 描述 |
---|---|
Optional.of(T) | 创建一个包含给定非空的值的Optional,否则当为null 时快速失败 |
Optional.absent() | 返回一个不存在的某种类型的Optional |
Optional.fromNullable(T) | 将一个可能为空的引用放入到Optional,将非空视为存在(present),将空视为不存在(absent) |
查询方法
这些都是在特别的Optional<T>
值上的非静态的方法。
方法 | 描述 |
---|---|
boolean isPresent() | 如果此Optional 包含一个非空的实例则返回true |
T get() | 返回包含的T 实例,其必须存在;否则,抛出一个IllegalStateException |
T or(T) | 返回在Optional 中已存在的值,或者如果没有值,返回默认值 |
T orNull() | 返回在Optional 中已存在的值,或者如果没有值,则返回为null ,fromNullable 的相反操作 |
Set<T> asSet() | 返回不可变的单例Set ,其包含Optional 的实例,如果没有值,则返回一个空的不可变Set |
除了这些,Optional
提供了几种更加方便的工具方法;请查阅Javadoc了解更多详情。
有什么意义?
除了为给定null
一个名称增加可读性之外,Optional最大的优势是它的易于操作的。如果你想要编译程序,它迫使你主动考虑不存在的情况,因为你必须主动打开Optional并处理该情况。Null很容易让人不安地忘记一些事情,尽管FindBugs有帮助,但是我们认为它没有很好的解决这个问题。
当你返回的值可能存在或者不存在时这就比较有意义的。对于other.method(a, b)
方法,当你实现other.method
方法是,与忘记a
可能是null相比,你(或者其他人)可能更容易忘记可能返回空值。返回Optional
使调用者不可能忘记此情况,因为他们必须打开他们以编译他们。
方便的方法
当你想要null
值被一些默认值替换,使用MoreObjects.firstNonNull(T, T)
。正如方法名所示,如果输入都是空值,它将使用NullPointerException
快速失败。如果你正在使用Optional
,有更好的替代方法–即,first.or(second)
。
在Strings
中提供了一些处理可能为null字符串的方法。确切来说,我们提供了恰当命名的:
我们想要强调的是这些方法主要是用于与使人不快的API进行接口,那些接口将empty字符串和null字符串等价。每次你写合并null字符串和empty字符串的代码,Guava都会感到很难过(如果null字符串和empty字符串实际表示不同的含义,那最好了,当时如果将他们视为相同的东西是一个令人不安的常见的代码味道)。
预先处理(precondition)
Guava在Preconditaions
类中提供了多个预先检查的工具。我们强烈建议静态引入这些类。
每个方法有三个变体:
- 没有额外的参数。抛出的任何异常都不会带有错误信息。
- 一个额外的
Object
参数。抛出的任何异常都会带有object.toString()
错误信息。 - 一个额外的
String
参数,带有任意数量的额外Object
参数。此行为有点像printf,都是为了GWT的兼容性和效率,它只支持%s
标志符。- 注意:
checkNotNull
,checkArgument
,checkState
有大量的重载,将原生类型和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 可以使用它来检查在removie 调用之前,next 是否已经被调用 | IllegalStateException |
checkElementIndex(int index, int size) | 检查index 是否是指定大小的的list,string或者array的有效的元素(element)索引。元素索引可能可能包含0到不包含size大小。你不能直接传入到list,string,array;你只能传入它的长度。返回index 。 | IndexOutOfBoundsException |
checkPositionIndex(int index, int size) | 检查index 是否是指定大小的的list,string或者array的有效的位置(element)索引,位置索引可能包含0到包含size大小。你不能直接传入到list,string,array。你只能传入它的长度。返回index 。 | IndexOutOfBoundsException |
checkPositionIndexes(int start, int end, int size) | 检查start 和end 是否在范围[0-size] (并且end 至少于start 一样大。自带错误信息。) | IndexOutOfBoundsException |
相比于Apache Common的类似的工具,我们首选我们自己的前置条件检查,有几点原因。
Briefly:
- 静态引入之后,Guava方法是清晰的,不模糊的。
checkNotNull
明确正在做什么,以及什么异常被抛出。 checkNotNull
在验证之后返回它的参数,允许在构造器中使用一行程序this.field = checkNotNull(field);
- 简单,可变参数的printf风格的异常信息。(这个优点也就是为什么我们推荐继续使用
checkNotNull
而不是Objects.requireNonNull
)。
我们建议你将多个前置条件分为不同的行,可以帮助当你调试时,断定哪个前置条件失败。此外,你应该提供有帮助的错误信息,当每一个检查是在它自己的行时,这就更容易了。
条件失败
条件失败或者运行时检查是当且仅当一个boolean条件成立时抛出异常的任何代码。当然,这样的代码在设计较好的软件中是常见的。本章节提供这种常见类型检查的概述。
条件失败的种类
一种可以非常容易处理条件失败的相同的方式:if(!condition) throw new RuntimeException();
。但是如果你花一点时间考虑正在执行的检查的性质,并以合适的方式处理它,你的代码将更容易被理解和错误更容易被诊断。
下面是主要的几种运行时检查。
预先处理检查确保公共方法的调用者已经遵守了方法规范的要求。例如,sqrt
功能可能只接收非负数参数。
一个常规的assertion是一个检查,只有在类本身(包含检查)以某种方式出错时才会失败(在某种情况下,还能扩展到包)。他们可以采用多种形式,包括后置 条件,类不变量和内部前置条件(针对非公共方法)。
当你对你所使用的API满足它的规范(实际或隐含的)缺乏自信时进行验证检查。最容易理解的是这种类型的检查“在任何方面都像断言,我们不想禁用他们”。
测试断言只有在测试代码中发现,并确保测试下的代码已经遵守他自己的规范要求。注意,这种“断言”与生产代码中的真正断言几乎没有任何共同之处。
一个不可能的条件的检查是不可能失败的检查,除非后来修改了周围的代码,或者严重违反了我们关于平台行为的最深层次假设。这些应该是不必要的,但通常是被强制的,因为编译器不能识别语句是不可达的,或者因为我们知道一些关于控制流的信息,而编译器无法推断。
最后,例外结果意味着方法不能提供所期望的结果,既不是由于它自己的错误,也不是任何其他任何代码的错误。这就类似于前提条件检查,除了在这种情况下,不期望调用者知道更多信息。它类似于验证检查,但是依赖的失败并不意外。例如,当已经到达文件末尾时,尝试从文件读取一行,这不是任何人的错;这只是一个例外的结果。根据在Effective Java,Second Edition Item 58,page 244的建议,使用一个检查或者非检查异常。
总结
检查类型 | 抛出方法在说… | 通常表明… |
---|---|---|
前置条件[Precondition] | “你搞砸了(针对调用者).” | IllegalArgumentException ,IllegalStateException |
断言[Assertion] | “我搞砸了.” | assert ,AssertionError |
验证[Verification] | “我依赖的人搞砸了.” | VerifyException |
测试断言[Test assertion] | “我测试的代码搞砸了.” | assertThat ,assertEquals ,AssertionError |
不可能的条件[Impossible condition] | “什么?这个世界太乱了.” | AssertionError |
异常结果[Exceptional result] | “确切地说,没有人搞砸.” | 其他检查或者未检查异常 |
重要的不是条件本身,而是上下文
注意每一个相同的条件,例如负数员工ID,在一部分系统(用户系统或者系统对系统接口)中可能是一个“异常的结果”,而在该点以下的所有公共API边界上它是一个“前置条件”。并且如果处于某种原因对非公共方法参数执行相同的检查,它将被正确地认为是“断言”,因为我们应该阻止rogue值走那么远。上下文才是最重要的,并使用不同种类的条件失败传到上下文。
排序(Ordering)
示例
assertTrue(byLengthOrdering.reverse().isOrdered(list));
概览
Ordering
是Guava的流式Comparator
类,其可以用于构建负责的比较器并将他们应用到对象集合。
在他的核心,Ordering
实例只不过是一个特殊的Comparator
实例。Ordering
只是接受依赖于Comparator
的方法(例如,Collections.max
)并使他们作为实例方法可用。为了获取更强大的功能,Ordering
类提供了调整和增强已有比较器的链式方法。
创建
常用的排序通过静态方法提供:
方法 | 描述 |
---|---|
natural() | 在排序类型上使用natural ordering |
usingToString() | 通过字符串表示的字典排序比较对象,按照toString 返回的。 |
将一个预存在的Comparator
放入到Ordering
就像使用Ordering.from(Compatator)
那么简单。
但是创建一个自定义的Ordering
更常用的方式是完全跳过Comparator
,直接继承Ordering
抽象类。
Ordering<String> byLengthOrdering = new Ordering<String>() {
public int compare(String left, String right) {
return Ints.compare(left.length(), right.length());
}
};
链式方式
给定的Ordering
可以进行包装来获取衍生的排序。一些最常用的变体包括:
方法 | 描述 |
---|---|
reverse() | 返回反向的排序 |
nullsFirst() | 返回空在非空元素前面的Ordering ,其他行为与原有Ordering 一样。也可以查看nullsLast() |
compound(Comparator) | 返回使用了“打破关系”的特殊Comparator |
compound(Comparator) | 返回使用了“打破关系”的特殊Comparator 的Ordering |
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
调用的链式,是从右到左的“反向”工作。以上示例通过查找他们的sortedBy
字段值排序Foo
实例,优先移动任何空sortedBy
值到顶端,然后根据自然字符串顺序排序其余的值。发生向后排序因为每一个链式调用是前面的Ordering
“封装”到新的一个中。
(“向后”规则的异常:对于调用compound
的链,从左向右。为防止混淆,避免将compound
调用与其他链式调用混合。)
比一些调用还要长的一些链可能比较难以理解。我们推荐像上面示例中一样,限制三次调用的链。即使那样,你可能希望通过分割中间对象来简化代码,就像Function
实例。
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(sortKeyFunction);
应用程序
Guava提供多个通过使用ordering操纵或者检查值和集合的方法。这里我们列出一些最流行的:
方法 | 描述 | 还可以查看 |
---|---|---|
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) |
Object常用方法
equals
当你的对象字段可以为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
注意:在JDK7中最新引进的Objects
类提供了等价的Objects.equals
方法。
hashCode
哈希Object
的所有字段更简单。Guava的Objects.hashCode(Object...)
为指定字段序列创建一个合理的,顺序敏感的hash。使用Objects.hashCode(field1, field2, ..., fieldn)
代替手动构建hash。
注意:在JDK7中最新引进的Objects
类提供了等价的Objects.hash(Object...)
。
toString
一个好的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();
compare/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);
}
}
这个代码非常容易混乱,难以扫描bug并且冗长得令人讨厌。我们可以做的更好。
处于这样的目的,Guava提供ComparisonChain
。
ComparisonChain
提供一个”懒式“比较:它只直到它发现一个非0结果时才会执行比较,之后它忽略进一步的输入。
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
找到,解释请查看这里。
Throwables
Guava的Throwables
工具通常可以简化异常处理。
传播
有时,当你捕获一个异常,你想要将他抛回到下一个try/catch块。RuntimeException
或者Error
实例通常是这种情况,他们不需要try/catch块,但是当你不希望他们被try/catch捕获时,已经通过try/catch块捕获到。
Guava提供了几个工具来简化传递异常。例如:
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);
}
下面是Guava提供的传播方法的快速汇总:
签名 | 说明 |
---|---|
void propagateIfPossible(Throwable, Class<X extends Throwable>) throws X | 只有当异常是RuntimeException ,Error 和一个X 原样抛出throwable |
void throwIfInstanceOf(Throwable, Class<X extends Exception>) throws X | 只有当异常是X 实例,原样传递throwable |
void throwIfUnchecked(Throwable) | 只有异常是RuntimeException 或者Error ,原样抛出异常 |
注意:我们在v20.0废弃了Throwables.propagate(Throwable)
。阅读关于为什么。
因果链
Guava使它稍微简化异常的因果链的了解,提供三个有用的方法,他们的签名是自解释的: