Guava学习笔记
前言
该篇文章为转载文章,Guava
的功能强大,由于自己使用Guava
并没有特别频繁,很多工具类也是在阅读本篇文章之后才发现原来开发的过程中可以省去很多的繁琐,例如Charsets
的使用,例如HttpHeaders
的使用,例如Multimap
相关集合类的使用,都是在阅读过本篇文章之后才发现可以在实际开发中省去很多的繁琐的。
只不过本篇文章中还有挺多需要补充的地方,后续有时间我会自己实现代码然后做补充的。
另外,本篇文章在做转载时,已经把Guava
的版本升级为23.0
,所以有一些方法做了修改
Guava介绍
Guava
工程包含了若干被Google
的Java
项目广泛依赖的核心库,例如:集合 [collections
] 、缓存 [caching
] 、原生类型支持 [primitives support
] 、并发库 [concurrency libraries
] 、通用注解 [commonannotations
] 、字符串处理 [string processing
] 、I/O
等等。所有这些工具每天都在被Google
的工程师应用在产品服务中。
这些高质量的API
可以使你的Java
代码更加优雅,更加简洁,让你的工作更加轻松愉悦,下面我们来开启Java
编程学习之旅。
源码包的简单说明
com.google.common.annotations
:普通注解类型com.google.common.base
:基本工具类库和接口com.google.common.cache
:缓存工具包,非常简单易用且功能强大的JVM
内缓存com.google.common.collect
:带泛型的集合接口扩展和实现,以及工具类,这里你会发现很多好玩的集合com.google.common.eventbus
:发布订阅风格的事件总线com.google.common.hash
: 哈希工具包com.google.common.io
:I/O
工具包com.google.common.math
:原始算术类型和超大数的运算工具包com.google.common.net
:网络工具包com.google.common.primitives
:八种原始类型和无符号类型的静态工具包com.google.common.reflect
:反射工具包com.google.common.util.concurrent
:多线程工具包
1 基本工具(Basic utilities
)
1.1 使用和避免null
(Optional
)
null
会引起歧义,会造成让人迷惑的错误,有时也会让人感到不爽。Guava
中的许多工具遇到null
时,会拒绝或者马上报错,而不是盲目的接受。
鉴于此Google
的Guava
库中提供了Optional
接口来使null
快速失败,即在可能为null
的对象上做了一层封装,在使用Optional
静态方法of
时,如果传入的参数为null
就抛出NullPointerException
异常。
在Guava
中Optional
类就是用来强制提醒程序员,注意对null
的判断。Optional
的另外几个方法
-
Optional<T>.of(T)
为Optional
赋值,当T
为null
直接抛NullPointException
,建议这个方法在调用的时候直接传常量,不要传变量 -
Optional<T>.fromNullable(T)
为Optional
赋值,当T
为null
则使用默认值。建议与or
方法一起用,风骚无比 -
Optional<T>.absent()
为Optional
赋值,采用默认值 -
T or(T)
当Optional
的值为null
时,使用or
赋予的值返回。与fromNullable
是一对好基友 -
T get()
当Optional
的值为null
时,抛出IllegalStateException
,返回Optional
的值 -
boolean isPresent()
如果Optional
存在值,则返回true
-
T orNull()
当Optional
的值为null
时,则返回null
。否则返回Optional
的值 -
Set<T> asSet()
将Optional
中的值转为一个Set
返回,当然只有一个值啦,或者为空,当值为null
时。
使用
Optional
的意义?
使用Optional
除了赋予null
语义,增加了可读性,最大的优点在于它一种傻瓜式的防护,Optional
迫使你积极思考引用缺失的情况,因为你必须显式地从Optional
获取引用。直接使用null
很容易让人忘掉某些情形,尽管FindBugs
可以帮助查找null
相关的问题,但是我们还是认为它并不能准确地定位问题根源。
1.2 前提条件(Preconditions
)
使方法的条件检查更简单。Guava
在Preconditions
类中提供了若干前置条件判断的实用方法,我们强烈建议在Eclipse
中静态导入这些方法。每个方法都有三个变种:
- 没有额外参数:抛出的异常中没有错误消息;
- 有一个
Object
对象作为额外参数:抛出的异常使用Object.toString()
作为错误消息; - 有一个
String
对象作为额外参数,并且有一组任意数量的附加Object
对象:这个变种处理异常消息的方式有点类似printf
,但考虑GWT
的兼容性和效率,只支持%s
指示符。
例如:查看源代码打印帮助
1
|
checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
|
方法声明(不包括额外参数) | 描述 | 检查失败时抛出的异常 |
---|---|---|
checkArgument(boolean) |
检查boolean 是否为true ,用来检查传递给方法的参数。 |
IllegalArgumentException |
checkNotNull(T) |
检查value 是否为null ,该方法直接返回value ,因此可以内嵌使用checkNotNull 。 |
NullPointerException |
checkState(boolean) |
用来检查对象的某些状态。 | IllegalStateException |
checkElementIndex(int index, int size) |
检查index 作为索引值对某个列表、字符串或数组是否有效。index >= 0 && index < size |
IndexOutOfBoundsException |
checkPositionIndex(int index, int size) |
检查index 作为位置值对某个列表、字符串或数组是否有效。index >= 0 && index <= size |
IndexOutOfBoundsException |
checkPositionIndexes(int start, int end, int size) |
检查[start, end] 表示的位置范围对某个列表、字符串或数组是否有效 |
IndexOutOfBoundsException |
1.3 常见的对象方法(Objects
)
简化Object
方法实现,如hashCode()
和equals()
;
-
equals()
当一个对象中的字段可以为null
时,实现Object.equals
方法会很痛苦,因为不得不分别对它们进行null
检查。使用Objects.equal
帮助你执行null
敏感的equals
判断,从而避免抛出NullPointerException
。 -
hashCode()
用对象的所有字段作散列[hash]
运算应当更简单。Guava
的Objects.hashCode(Object...)
会对传入的字段序列计算出合理的、顺序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)
来代替手动计算散列值。 -
toString()
好的toString
方法在调试时是无价之宝,但是编写toString
方法有时候却很痛苦。使用MoreObjects.toStringHelper
可以轻松编写有用的toString
方法。
PS:
toStringHelper()
方法在Guava18+
的版本中已经移除出Objects
类,并且感觉toStringHelper()
并没有什么卵用,使用自己实现的toString
方法就可以的
1.4 排序
Guava
强大的”流畅风格比较器”,具体到下章会介绍到。
1.5 Throwable
类
简化了异常和错误的传播与检查;Guava
类库中的Throwables
提供了一些异常处理的静态方法,这些方法的从功能上分为两类,一类是帮你抛出异常,另外一类是帮你处理异常。
方法 | 作用 |
---|---|
void throwIfInstanceOf(Throwable throwable, Class<X> declaredType) throws X |
Throwable 类型为X 才抛出 |
void propagateIfPossible(@Nullable Throwable throwable, Class<X> declaredType) throws X |
Throwable 类型为X ,Error 或RuntimeException 才抛出 |
2 集合(Collections
)
介绍Guava
对Jdk
集合类的扩展,包括不可变集合,新集合类型包括Multisets
, Multimaps
, Tables
等。强大的集合工具类,提供java.util.Collections
中没有的集合工具,扩展工具类。让实现和扩展集合类变得更容易,比如创建Collection
的装饰器,或实现迭代器。集合API
的使用,可以简化集合的创建和初始化。Guava API
提供了有用的新的集合类型,协同已经存在的Java
集合工作。
分别是MultiMap
, Multiset
, Table
, BiMap
,ClassToInstanceMap
2.1 Guava
的不可变集合
不可变对象有很多优点:
- 当对象被不可信的库调用时,不可变形式是安全的
- 当不可变对象被多个线程调用时,不存在竞态条件问题
- 不可变集合不需要考虑变化,因此可以节约时间和空间,所有不可变集合都比可变集合形式有更好的内存利用率(分析和测试细节)
- 不可变对象因为有固定不变,可以用作常量来安全使用
总结:数据不可变;不需要同步逻辑;线程安全;自由共享;容易设计和实现;内存和时间高效
创建对象的不可拷贝是一项很好的防御性编程技巧,Guava
为所有JDK
标准集合类型和Guava
新集合类型都提供了简单易用的不可变版本。
JDK
也提供了可以将集合变成不可变的方法,Collections.unmodifiableXXX
,但是被认为是不好的。
- 笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景
- 不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的
- 低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等
提示:
Guava
不可变集合的实现都不接受null
值,经过对5%
的情况下集合中允许了null
值,其他情况下都不允许。如果我们想用null
的不可变集合,那我们就用JDK
中的集合类进行操作,然后进行集合工具类的处理Collections.unmodifiableXXX
。
关联可变集合和不可变集合
可变集合接口 | 属于JDK 还是Guava | 不可变版本 |
---|---|---|
Collection |
JDK |
ImmutableCollection |
List |
JDK |
ImmutableList |
Set |
JDK |
ImmutableSet |
SortedSet/NavigableSet |
JDK |
ImmutableSortedSet |
Map |
JDK |
ImmutableMap |
SortedMultiset |
Guava |
ImmutableSortedMultiset |
Multimap |
Guava |
ImmutableMultimap |
ListMultimap |
Guava |
ImmutableListMultimap |
SetMultimap |
Guava |
ImmutableSetMultimap |
BiMap |
Guava |
ImmutableBiMap |
ClassToInstanceMap |
Guava |
ImmutableClassToInstanceMap |
Table |
Guava |
ImmutableTable |
2.2 Guava
集合之Multiset
Multiset
看似是一个Set
,但是实质上它不是一个Set
它没有继承Set
接口,它继承的是Collection<E>
接口
你可以向Multiset
中添加重复的元素,Multiset
会对添加的元素做一个计数
它本质上是一个Set
加一个元素计数器
显然计数不是问题,Multiset
还提供了add
和remove
的重载方法,可以在add
或remove
的同时指定计数的值。
常用实现Multiset
接口的类有:
HashMultiset
: 元素存放于HashMap
LinkedHashMultiset
:元素存放于LinkedHashMap
,即元素的排列顺序由第一次放入的顺序决定TreeMultiset
:元素被排序存放于TreeMap
EnumMultiset
: 元素必须是Enum
类型ImmutableMultiset
:不可修改的Mutiset
看到这里你可能已经发现Guava
Collections
都是以create
或of
这样的静态方法来构造对象。这是因为这些集合类大多有多个参数的私有构造方法,由于参数数目很多,程序员使用起来就很不方便。而且以这种方式可以返回原类型的子类型对象。另外,对于创建范型对象来讲,这种方式更加简洁。
2.3 Guava
的BiMap
:双向Map
我们知道Map
是一种键值对映射,这个映射是键到值的映射
而BiMap首先也是一种Map
他的特别之处在于,既提供键到值的映射,也提供值到键的映射
所以它是双向Map
BiMap
的常用实现有:
HashBiMap
:key
集合与value
集合都有HashMap
实现EnumBiMap
:key
与value
都必须是Enum
类型ImmutableBiMap
: 不可修改的BiMap
2.4 Guava
的Multimap
:一键多值的Map
有时候我们需要这样的数据类型Map<String,Collection<String>>
,Guava
中的Multimap
就是为了解决这类问题的。Multimap
提供了丰富的实现,所以你可以用它来替代程序里的Map<K, Collection<V>>
,具体的实现如下:
实现 | Key 实现 | Value 实现 |
---|---|---|
ArrayListMultimap |
HashMap |
ArrayList |
HashMultimap |
HashMap |
HashSet |
LinkedListMultimap |
LinkedHashMap |
LinkedList |
LinkedHashMultimap |
LinkedHashMap |
LinkedHashSet |
TreeMultimap |
TreeMap |
TreeSet |
ImmutableListMultimap |
ImmutableMap |
ImmutableList |
ImmutableSetMultimap |
ImmutableMap |
ImmutableSet |
2.5 Guava
集合之Table
在Guava
库中还提供了一种二维表结构:Table
使用Table
可以实现二维矩阵的数据结构,可以是稀疏矩阵。
通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, Map<LastName, Person>>
的实现
这种方式很丑陋,使用上也不友好Guava
为此提供了新集合类型Table
它有两个支持所有类型的键:行
和列
Table
提供多种视图,以便你从各种角度使用它:
rowMap()
:用Map<R, Map<C, V>>
表现Table<R, C, V>
同样的, rowKeySet()
返回行
的集合Set<R>
。row(r)
:用Map<C, V>
返回给定行
的所有列
对这个map
进行的写操作也将写入Table
中。
类似的列访问方法:columnMap()
、columnKeySet()
、column(c)
。(基于列的访问会比基于的行访问稍微低效点)
cellSet()
:用元素类型为Table.Cell<R, C, V>
的Set
表现Table<R,C, V>
。Cell
类似于Map.Entry
,但它是用行和列两个键区分的。
2.6 Guava
集合:使用Iterators
简化Iterator
操作
Iterators
是Guava
中对Iterator
迭代器操作的帮助类,这个类提供了很多有用的方法来简化Iterator
的操作。
2.7 ClassToInstanceMap
可以实现map
的key
值是多个类型
有的时候,你的map
的key
并不是一种类型,他们是很多类型
你想通过映射他们得到这种类型Guava
提供了ClassToInstanceMap
满足了这个目的
除了继承自Map
接口,ClassToInstaceMap
提供了方法T getInstance(Class<T>)
和T putInstance(Class<T>, T)
,消除了强制类型转换。
2.8 Ordering
犀利的比较器
Ordering
是Guava
类库提供的一个犀利强大的比较器工具Guava
的Ordering
和JDK
Comparator
相比功能更强
它非常容易扩展,可以轻松构造复杂的comparator
,然后用在容器的比较、排序等操作中
本质上来说,Ordering
实例无非就是一个特殊的Comparator
实例Ordering
只是需要依赖于一个比较器(例如:Collections.max
)的方法,并使其可作为实例方法
另外,Ordering
提供了链式方法调用和加强现有的比较器。
常见的静态方法:
natural()
:使用Comparable
类型的自然顺序,例如:整数从小到大,字符串是按字典顺序usingToString()
:使用toString()
返回的字符串按字典顺序进行排序arbitrary()
:返回一个所有对象的任意顺序,即compare(a, b) == 0
就是a == b (identity equality)
。本身的排序是没有任何含义,但是在VM
的生命周期是一个常量
2.9 ComparisonChain
比较
实现一个比较器[Comparator
],或者直接实现Comparable
接口有时也伤不起。考虑一下这种情况:
1
|
class Student implements Comparable<Student> {
|
这部分代码太琐碎了,因此很容易搞乱,也很难调试
我们应该能把这种代码变得更优雅
为此,Guava
提供了ComparisonChain
ComparisonChain
执行一种懒比较:它执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略。
1
|
//guava比较优雅
|
这种Fluent
接口风格的可读性更高,发生错误编码的几率更小
并且能避免做不必要的工作。
3 缓存(Caches
)
Google Guava
框架提供了内存缓存的功能
可以很方便的缓存对象,设置生命周期
及缓存对象的弱引用,强应用,软引用等
3.1 使用Guava
做内存缓存
Guava
中有cache
包,此包提供内存缓存功能
内存缓存需要考虑很多问题,包括并发问题,缓存失效机制
内存不够用时缓存释放,缓存的命中率,缓存的移除等等
当然这些东西Guava
都考虑到了。Guava
的内存缓存非常强大,可以设置各种选项
而且很轻量,使用方便
另外还提供了下面一些方法,来方便各种需要:
ImmutableMap<K, V> getAllPresent(Iterable<?> keys)
一次获得多个键的缓存值put()
和putAll()
方法向缓存中添加一个或者多个缓存项invalidate()
和invalidateAll()
方法从缓存中移除缓存项asMap()
方法获得缓存数据的ConcurrentMap<K, V>
快照cleanUp()
清空缓存refresh(Key)
刷新缓存,即重新取缓存数据,更新缓存
3.2 Guava
缓存分析
Guava
缓存过期时间分为两种,一种是从写入时开始计时,一种是从最后访问时间开始计时
而且Guava
缓存的过期时间是设置到整个一组缓存上的
这和EHCache
,Redis
,Memcached
等不同
这些缓存系统设置都将缓存时间设置到了单个缓存上
Guava
缓存设计成了一组对象一个缓存实例
这样做的好处是一组对象设置一组缓存策略,你可以根据不同的业务来设置不同的缓存策略
包括弱引用,软引用,过期时间,最大项数等
另外一点好处是你可以根据不同的组来统计缓存的命中率,这样更有意义一些
这样做也是有缺点的
缺点是首先是每个缓存组都需要声明不同的缓存实例,具体到业务程序中可能就是每个业务对象一个缓存了。
这样就把不同的业务缓存分散到不同的业务系统中了,不太好管理。
4 函数式风格(Functional idioms
)
5 并发(Concurrency
)
并发编程是一个难题,但是一个强大而简单的抽象可以显著的简化并发的编写
出于这样的考虑,Guava
定义了ListenableFuture
接口并继承了JDK concurrent
包下的Future
接口。
5.1 Guava
并发:ListenableFuture
使用介绍以及示例
ListenableFuture
顾名思义就是可以监听的Future
它是对Java
原生Future
的扩展增强
本文介绍ListenableFuture
的用法和扩展实现
我们知道Future
表示一个异步计算任务,当任务完成时可以得到计算结果
如果我们希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态
这样做,代码复杂,而且效率低下
使用ListenableFuture
Guava
帮我们检测Future
是否完成了
如果完成就自动调用回调函数,这样可以减少并发程序的复杂度
另外ListenableFuture
还有其他几种内置实现:
SettableFuture
:不需要实现一个方法来计算返回值,而只需要返回一个固定值来做为返回值,可以通过程序设置此Future
的返回值或者异常信息CheckedFuture
:这是一个继承自ListenableFuture
接口,他提供了checkedGet()
方法,此方法在Future
执行发生异常时,可以抛出指定类型的异常。
5.2 Guava
并发:RateLimiter
限制资源的并发访问线程数
RateLimiter
类似于JDK
的信号量Semphore
用来限制对资源并发访问的线程数
1
|
RateLimiterlimiter = RateLimiter.create(4.0);//每秒不超过4个任务被提交
|
也可以以非阻塞的形式来使用
1
|
If(limiter.tryAcquire()){ //未请求到limiter则立即返回false
|
5.3 Guava
并发:使用Monitor
控制并发
Monitor
就像Java
原生的synchronized
,ReentrantLock
一样
每次只允许一个线程执行代码块,且可重占用
每一次占用要对应一次退出占用。
1
|
/**
|
就如上面,我们通过if条件来判断是否可进入Monitor
代码块,并再try/finally
中释放:
1
|
if(monitor.enterIf(guardCondition)) {
|
其他的Monitor
访问方法:
1
|
Monitor.enter //进入Monitor块,将阻塞其他线程直到Monitor.leave
|
这几个方法都有对应的超时设置版本。
6 字符串处理(Strings
)
6.1 连接器(Joiner
)
用分隔符把字符串序列连接起来也可能会遇上不必要的麻烦
如果字符串序列中含有null
,那连接操作会更难Fluent
风格的Joiner
让连接字符串更简单
警告:
Joiner
实例总是不可变的。用来定义Joiner
目标语义的配置方法总会返回一个新的Joiner
实例。这使得Joiner
实例都是线程安全的,你可以将其定义为static final
常量。
6.2 拆分器(Splitter
)
JDK
内建的字符串拆分工具有一些古怪的特性
比如,String.split
悄悄丢弃了尾部的分隔符。例如:
1
|
",a,,b,".split(",") // "", "a", "", "b"
|
只有尾部的空字符串被忽略了
Splitter
使用令人放心的、直白的流畅API
模式对这些混乱的特性作了完全的掌控。
a) 拆分器工厂:
方法 | 描述 |
---|---|
Splitter.on(char) |
按单个字符拆分 |
Splitter.on(CharMatcher) |
按字符匹配器拆分 |
Splitter.on(String) |
按字符串拆分 |
Splitter.on(Pattern) Splitter.onPattern(String) |
按正则表达式拆分 |
Splitter.fixedLength(int) |
按固定长度拆分;最后一段可能比给定长度短,但不会为空。 |
b) 拆分器修饰符:
方法 | 描述 |
---|---|
omitEmptyStrings() |
从结果中自动忽略空字符串 |
trimResults() |
移除结果字符串的前导空白和尾部空白 |
trimResults(CharMatcher) |
给定匹配器,移除结果字符串的前导匹配字符和尾部匹配字符 |
limit(int) |
限制拆分出的字符串数量 |
如果你想要拆分器返回List
只要使用splitToList
方法。
如果想要拆分器返回数组,只需要调用Iterables.toArray
方法进行改造,例如
1
|
String[] strArr = Iterables.toArray(Splitter.on(" ").split(",a,,b,c"), String.class);
|
警告:
Splitter
实例总是不可变的。用来定义Splitter
目标语义的配置方法总会返回一个新的Splitter
实例。这使得Splitter
实例都是线程安全的,你可以将其定义为static final
常量。
6.3 字符匹配器(CharMatcher
)
使用CharMatcher
的好处更在于它提供了一系列方法
让你对字符作特定类型的操作:
修剪[trim
]、折叠[collapse
]、移除[remove
]、保留[retain
]等等CharMatcher
实例首先代表
- 概念1:怎么才算匹配字符?
- 概念2:如何处理这些匹配字符?
这样的设计使得API
复杂度的线性增加可以带来灵活性和功能两方面的增长。
注:
CharMatcher
只处理char
类型代表的字符;它不能理解0x10000
到0x10FFFF
的Unicode
增补字符。这些逻辑字符以代理对[surrogatepairs
]的形式编码进字符串,而CharMatcher
只能将这种逻辑字符看待成两个独立的字符。
6.4 字符集(Charsets
)
Charsets
针对所有Java
平台都要保证支持的六种字符集提供了常量引用。尝试使用这些常量,而不是通过名称获取字符集实例。
6.5 大小写格式(CaseFormat
)
CaseFormat
被用来方便地在各种ASCII
大小写规范间转换字符串。
比如,编程语言的命名规范CaseFormat
支持的格式如下:
格式 | 范例 |
---|---|
LOWER_CAMEL |
lowerCamel |
LOWER_HYPHEN |
lower-hyphen |
LOWER_UNDERSCORE |
lower_underscore |
UPPER_CAMEL |
UpperCamel |
UPPER_UNDERSCORE |
UPPER_UNDERSCORE |
CaseFormat
的用法很直接:
1
|
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,"CONSTANT_NAME"
|
CaseFormat
在某些时候尤其有用,比如编写代码生成器的时候。
7 原生类型(Primitives
)
Java
的原生类型也称原始类型
也是基本数据类型byte
、short
、int
、long
、float
、double
、char
和boolean
在从Guava
查找原生类型方法之前,可以先查查Arrays
类
或者对应的基础类型包装类,如Integer
原生类型不能当作对象或泛型的类型参数使用
这意味着许多通用方法都不能应用于它们Guava
提供了若干通用工具,包括原生类型数组与集合API
的交互
原生类型和字节数组的相互转换
以及对某些原生类型的无符号形式的支持
原生类型 | Guava 工具类(都在com.google.common.primitives 包) |
---|---|
byte |
Bytes , SignedBytes , UnsignedBytes |
short |
Shorts |
int |
Ints , UnsignedInteger , UnsignedInts |
long |
Longs , UnsignedLong , UnsignedLongs |
float |
Floats |
double |
Doubles |
char |
Chars |
boolean |
Booleans |
Bytes
工具类没有定义任何区分有符号和无符号字节的方法
而是把它们都放到了SignedBytes
和UnsignedBytes
工具类中
因为字节类型的符号性比起其它类型要略微含糊一些
int
和long
的无符号形式方法在UnsignedInts
和UnsignedLongs
类中
但由于这两个类型的大多数用法都是有符号的Ints
和Longs
类按照有符号形式处理方法的输入参数
此外,Guava
为int
和long
的无符号形式提供了包装类
即UnsignedInteger
和UnsignedLong
,以帮助你使用类型系统
以极小的性能消耗对有符号和无符号值进行强制转换。
原生类型数组工具:
方法签名 | 描述 |
---|---|
List<Wrapper> asList(prim… backingArray) |
把数组转为相应包装类的List |
prim[] toArray(Collection<Wrapper> collection) |
把集合拷贝为数组,和collection.toArray() 一样线程安全 |
prim[] concat(prim[]… arrays) |
串联多个原生类型数组 |
boolean contains(prim[] array, prim target) |
判断原生类型数组是否包含给定值 |
int indexOf(prim[] array, prim target) |
给定值在数组中首次出现处的索引,若不包含此值返回-1 |
int lastIndexOf(prim[] array, prim target) |
给定值在数组最后出现的索引,若不包含此值返回-1 |
prim min(prim… array) |
数组中最小的值 |
prim max(prim… array) |
数组中最大的值 |
String join(String separator, prim… array) |
把数组用给定分隔符连接为字符串 |
Comparator<prim[]> lexicographicalComparator() |
按字典序比较原生类型数组的Comparator |
符号无关方法存在于
Bytes
,Shorts
,Ints
,Longs
,Floats
,Doubles
,Chars
,Booleans
。而UnsignedInts
,UnsignedLongs
,SignedBytes
,UnsignedBytes
不存在。符号相关方法存在于
SignedBytes
,UnsignedBytes
,Shorts
,Ints
,Longs
,Floats
,Doubles
,Chars
,Booleans
,UnsignedInts
,UnsignedLongs
。而
Bytes
不存在。
通用工具方法:
|方法签名|描述|
|int compare(prim a, prim b)
|传统的Comparator.compare
方法,但针对原生类型。JDK7
的原生类型包装类也提供这样的方法|
|prim checkedCast(long value)
|把给定long
值转为某一原生类型,若给定值不符合该原生类型,则抛出IllegalArgumentException
|
|prim saturatedCast(long value)
|把给定long
值转为某一原生类型,若给定值不符合则使用最接近的原生类型值|
这里的整型包括byte, short, int, long。不包括char, boolean, float, 或double。
字节转换方法:Guava
提供了若干方法,用来把原生类型按大字节序与字节数组相互转换。所有这些方法都是符号无关的,此外Booleans
没有提供任何下面的方法。
|方法签名或字段签名|描述|
|int BYTES
|常量:表示该原生类型需要的字节数|
|prim fromByteArray(byte[] bytes)
|使用字节数组的前Prims.BYTES
个字节,按大字节序返回原生类型值;如果bytes.length <= Prims.BYTES
,抛出IAE
|
|prim fromBytes(byte b1, …, byte bk)
|接受Prims.BYTES
个字节参数,按大字节序返回原生类型值|
|byte[] toByteArray(prim value)
|按大字节序返回value
的字节数组|
8 区间(Ranges
)
8.1 简介
区间,有时也称为范围
是特定域中的凸性(非正式说法为连续的或不中断的)部分
在形式上,凸性表示对a <= b <= c
, range.contains(a)
且range.contains(c)
意味着range.contains(b)
。
区间可以延伸至无限,例如范围x > 3
包括任意大于3
的值,也可以被限制为有限,如2 <= x < 5
Guava
用更紧凑的方法表示范围,有数学背景的程序员对此是耳熟能详的:
1
|
(a..b) = {x | a < x < b}
|
上面的a
、b
称为端点
为了提高一致性,Guava
中的Range
要求上端点不能小于下端点
上下端点有可能是相等的,但要求区间是闭区间或半开半闭区间(至少有一个端点是包含在区间中的):[a..a]:单元素区间
[a..a); (a..a]:空区间,但它们是有效的
(a..a):无效区间
Guava
用类型Range<C>
表示区间。所有区间实现都是不可变类型。
8.2 构建区间
区间实例可以由Range
类的静态方法获取:
数学形式区间 | 静态方法 |
---|---|
(a..b) |
open(C, C) |
[a..b] |
closed(C, C) |
[a..b) |
closedOpen(C, C) |
(a..b] |
openClosed(C, C) |
(a..+∞) |
greaterThan(C) |
[a..+∞) |
atLeast(C) |
(-∞..b) |
lessThan(C) |
(-∞..b] |
atMost(C) |
(-∞..+∞) |
all() |
此外,也可以明确地指定边界类型来构造区间:
区间类型 | 静态方法 |
---|---|
有界区间 | range(C, BoundType, C, BoundType) |
无上界区间:(a..+∞) 或[a..+∞) |
downTo(C, BoundType) |
无下界区间:(-∞..b) 或(-∞..b] |
upTo(C, BoundType) |
这里的BoundType是一个枚举类型,包含CLOSED和OPEN两个值。
8.3 区间运算
Range
的基本运算是它的contains(C)
方法
和你期望的一样,它用来区间判断是否包含某个值
此外,Range
实例也可以当作Predicate
,并且在函数式编程中使用
任何Range
实例也都支持containsAll(Iterable<? extends C>)
方法
8.4 查询运算
Range
类提供了以下方法来 查看区间的端点:
hasLowerBound()
和hasUpperBound()
:判断区间是否有特定边界,或是无限的;lowerBoundType()
和upperBoundType()
:返回区间边界类型,CLOSED
或OPEN
;如果区间没有对应的边界,抛出IllegalStateException
;lowerEndpoint()
和upperEndpoint()
:返回区间的端点值;如果区间没有对应的边界,抛出IllegalStateException
;isEmpty()
:判断是否为空区间。
8.5 关系运算
- 包含[
enclose
]
区间之间的最基本关系就是包含[encloses(Range)
]:如果内区间的边界没有超出外区间的边界,则外区间包含内区间。包含判断的结果完全取决于区间端点的比较 - 相连[
isConnected
]Range.isConnected(Range)
判断区间是否是相连的。具体来说,isConnected
测试是否有区间同时包含于这两个区间,这等同于数学上的定义“两个区间的并集是连续集合的形式”(空区间的特殊情况除外)。 - 交集[
intersection
]Range.intersection(Range)
返回两个区间的交集:既包含于第一个区间,又包含于另一个区间的最大区间。当且仅当两个区间是相连的,它们才有交集。如果两个区间没有交集,该方法将抛出IllegalArgumentException
。 - 跨区间[
span
]Range.span(Range)
返回“同时包括两个区间的最小区间”,如果两个区间相连,那就是它们的并集。span是可互换的[commutative
] 、关联的[associative
] 、闭合的[closed
]、运算[operation
]。
8.6 离散域
部分(但不是全部)可比较类型是离散的
即区间的上下边界都是可枚举的。
在Guava
中,用DiscreteDomain<C>
实现类型C
的离散形式操作
一个离散域总是代表某种类型值的全集
它不能代表类似“素数”、“长度为5的字符串”或“午夜的时间戳”这样的局部域。
DiscreteDomain
提供的离散域实例包括:
类型 | 离散域 |
---|---|
Integer | integers() |
Long | longs() |
一旦获取了DiscreteDomain
实例,你就可以使用下面的Range
运算方法:
ContiguousSet.create(range, domain)
:用ImmutableSortedSet<C>
形式表示Range<C>
中符合离散域定义的元素,并增加一些额外操作——译者注:实际返回ImmutableSortedSet
的子类ContiguousSet
。(对无限区间不起作用,除非类型C
本身是有限的,比如int
就是可枚举的)canonical(domain)
:把离散域转为区间的“规范形式”。如果ContiguousSet.create(a, domain).equals(ContiguousSet.create(b,domain))
并且!a.isEmpty()
,则有a.canonical(domain).equals(b.canonical(domain))
。(这并不意味着a.equals(b)
)
你可以创建自己的离散域,但必须记住DiscreteDomain
契约的几个重要方面。
一个离散域总是代表某种类型值的全集;它不能代表类似“素数”或“长度为5的字符串”这样的局部域。所以举例来说,你无法构造一个·DiscreteDomain·以表示精确到秒的JODA DateTime
日期集合:因为那将无法包含JODA DateTime
的所有值。DiscreteDomain
可能是无限的——比如BigInteger DiscreteDomain
。这种情况下,你应当用minValue()
和maxValue()
的默认实现,它们会抛出NoSuchElementException
。但Guava
禁止把无限区间传入ContiguousSet.create
。
9 I/O
9.1 Guava Files
中的文件操作
Java
的基本API
对文件的操作很繁琐,为了向文件中写入一行文本,都需要写十几行的代码。Guava
对此作了很多改进,提供了很多方便的操作。
10 散列(Hash
)
10.1 概述
Java
内建的散列码[hash code
]概念被限制为32
位,并且没有分离散列算法和它们所作用的数据,因此很难用备选算法进行替换。此外,使用Java
内建方法实现的散列码通常是劣质的,部分是因为它们最终都依赖于JDK
类中已有的劣质散列码。Object.hashCode
往往很快,但是在预防碰撞上却很弱,也没有对分散性的预期。这使得它们很适合在散列表中运用,因为额外碰撞只会带来轻微的性能损失,同时差劲的分散性也可以容易地通过再散列来纠正(Java
中所有合理的散列表都用了再散列方法)。然而,在简单散列表以外的散列运用中,Object.hashCode
几乎总是达不到要求,因此有了com.google.common.has
h包。
10.2 散列包的组成
在这个包的Java doc
中,我们可以看到很多不同的类,但是文档中没有明显地表明它们是怎样一起配合工作的。
HashFunction
HashFunction
是一个单纯的(引用透明的)、无状态的方法,它把任意的数据块映射到固定数目的位置,并且保证相同的输入一定产生相同的输出,不同的输入尽可能产生不同的输出。Hasher
HashFunction
的实例可以提供有状态的Hasher
,Hasher
提供了流畅的语法把数据添加到散列运算,然后获取散列值。Hasher
可以接受所有原生类型、字节数组、字节数组的片段、字符序列、特定字符集的字符序列等等,或者任何给定了Funnel
实现的对象。Hasher
实现了PrimitiveSink
接口,这个接口为接受原生类型流的对象定义了fluent
风格的API
Funnel
Funnel
描述了如何把一个具体的对象类型分解为原生字段值,从而写入PrimitiveSink
。
注:putString("abc",Charsets.UTF_8).putString("def", Charsets.UTF_8)
完全等同于putString("ab", Charsets.UTF_8).putString("cdef",Charsets.UTF_8)
,因为它们提供了相同的字节序列。这可能带来预料之外的散列冲突。增加某种形式的分隔符有助于消除散列冲突。HashCode
一旦Hasher
被赋予了所有输入,就可以通过hash()
方法获取HashCode
实例(多次调用hash()
方法的结果是不确定的)。HashCode
可以通过asInt()
、asLong()
、asBytes()
方法来做相等性检测,此外,writeBytesTo(array, offset, maxLength)
把散列值的前maxLength
字节写入字节数组。
10.3 布鲁姆过滤器[BloomFilter
]
布鲁姆过滤器是哈希运算的一项优雅运用
它可以简单地基于Object.hashCode()
实现
简而言之,布鲁姆过滤器是一种概率数据结构
它允许你检测某个对象是一定不在过滤器中
还是可能已经添加到过滤器了Guava
散列包有一个内建的布鲁姆过滤器实现
你只要提供Funnel
就可以使用它
你可以使用create(Funnel funnel, int expectedInsertions, doublefalsePositiveProbability)
方法获取BloomFilter<T>
缺省误检率[falsePositiveProbability
]为3%
BloomFilter<T>
提供了booleanmightContain(T)
和void put(T)
它们的含义都不言自明了。
10.4 Hashing
类
Hashing
类提供了若干散列函数
以及运算HashCode
对象的工具方法
已提供的散列函数
md5()
murmur3_128()
murmur3_32()
sha1()
sha256()
sha512()
goodFastHash(int bits)
HashCode运算
方法 | 描述 |
---|---|
HashCode combineOrdered( Iterable<HashCode>) |
以有序方式联接散列码,如果两个散列集合用该方法联接出的散列码相同,那么散列集合的元素可能是顺序相等的 |
HashCode combineUnordered( Iterable<HashCode>) |
以无序方式联接散列码,如果两个散列集合用该方法联接出的散列码相同,那么散列集合的元素可能在某种排序下是相等的 |
int consistentHash( HashCode, int buckets) |
为给定的“桶”大小返回一致性哈希值。当“桶”增长时,该方法保证最小程度的一致性哈希值变化。详见一致性哈希。 |
11 事件总线(EventBus
)
传统上,Java
的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的
设计EventBus
就是为了取代这种显示注册方式
使组件间有了更好的解耦EventBus
不是通用型的发布-订阅实现,不适用于进程间通信。
12 数学运算(Math
)
13 反射(Reflection
)
13.1 Guava
反射TypeToken
解决泛型运行时类型擦除的问题
介绍Guava
中的TypeToken
类解决Java
运行时泛型类型擦除问题。TypeToken
的方法列表如下:
方法 | 描述 |
---|---|
getType() |
获得包装的java.lang.reflect.Type |
getRawType() |
返回大家熟知的运行时类 |
getSubtype(Class<?>) |
返回那些有特定原始类的子类型。举个例子,如果这有一个Iterable 并且参数是List.class ,那么返回将是List 。 |
getSupertype(Class<?>) |
产生这个类型的超类,这个超类是指定的原始类型。举个例子,如果这是一个Set 并且参数是Iterable.class ,结果将会是Iterable |
isAssignableFrom(type) |
如果这个类型是assignable from 指定的类型,并且考虑泛型参数,返回true 。List<? extends Number> 是assignable from List ,但List 没有 |
getTypes() |
返回一个Set ,包含了这个所有接口,子类和类是这个类型的类。返回的Set 同样提供了classes() 和interfaces() 方法允许你只浏览超类和接口类 |
isArray() |
检查某个类型是不是数组,甚至是<? extends A[]> |
getComponentType() |
返回组件类型数组 |
13.2 Gava
反射之Invokable
使用
Guava
的Invokable
是对java.lang.reflect.Method
和java.lang.reflect.Constructor
的流式包装。它简化了常见的反射代码的使用。
一些使用例子:
-
方法是否是
public
的?JDK
:Modifier.isPublic(method.getModifiers());
Invokable
:invokable.isPublic();
-
方法是否是
package private
?JDK
:!(Modifier.isPrivate(method.getModifiers())||Modifier.isPublic(method.getModifiers()))
Invokable
:invokable.isPackagePrivate()
-
方法是否能够被子类重写?
JDK
:!(Modifier.isFinal(method.getModifiers()) || Modifiers.isPrivate(method.getModifiers()) || Modifiers.isStatic(method.getModifiers()) || Modifiers.isFinal(method.getDeclaringClass().getModifiers()))
Invokable
:invokable.isOverridable()
-
方法的第一个参数是否被定义了注解
@Nullable
?JDK
:1
2
3
4
5
6for (Annotation annotation : method.getParameterAnnotations[0]) {
if (annotation instanceof Nullable) {
return true;
}
}
return false;Invokable
:invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)
-
构造函数和工厂方法如何共享同样的代码?
你是否很想重复自己,因为你的反射代码需要以相同的方式工作在构造函数和工厂方法中?Invokable
提供了一个抽象的概念。下面的代码适合任何一种方法或构造函数:invokable.isPublic();
invokable.getParameters();
invokable.invoke(object,args);
List
的List.get(int)
返回类型是什么?Invokable
提供了与众不同的类型解决方案:1
2Invokable<List<String>,?> invokable = new TypeToken<List<String>>(){}.method(getMethod);
invokable.getReturnType(); // String.class
13.3 Guava
反射:Reflection.newProxy
方法简化动态代理
原理上Guava
的动态代理也是使用JDK
的动态代理,这是做了封装,更加简便
另外一点是能够很好的检查需要代理的对象必须拥有接口。
使用Class
类的isInterface()
来做检查。
14 注解(Annotations
)
com.google.common.annotations
包下注解类的含义和用法:
14.1 Beta
1
|
/**
|
14.2 GwtCompatible
1
|
/**
|
14.3 GwtIncompatible
1
|
/**
|
14.4 VisibleForTesting
1
|
/**
|
15 网络编程(Net
)
Guava
中的net
包目前提供的功能较少
而且大多类都标注了@Beta
的注解
在Guava
中标记Beta
注解表示这个类还不稳定
有可能在以后的版本中变化,或者去掉
所以不建议大量使用,这里也是只做简单的介绍
先介绍下唯一一个没有Beta
注解的类HttpHeaders
,
这个类中并没有实质的方法,只是定义了一些Http
头名称的常量
通常如果需要我们会自己定义这些常量
如果你引用了guava
包,那么就不再建议我们自己定义这些头名称的常量了,直接用它定义的即可
这里面应该有几乎所有的Http
头名称,例如:X_FORWARDED_FOR
,UPGRADE
,REFERER
等等
用法也没有必要介绍了,直接引用常量就可以了
再介绍下一个比较常用的小功能
有时候我们需要在配置文件中配置IP+PORT
这时候需要自己写解析ip,端口的方法Guava
为我们提供了解析类,我们看下用法实例:
1
|
HostAndPort hostAndPort = HostAndPort.fromString("127.0.0.1:8080");
|
HostAndPort
类的静态方法fromString(String)
可以解析出字符串中定义的Host
和端口信息。
另外Guava
包中还提供了InetAddresses
类
这个类是InetAddress
的帮助类
通过这个类可以方便的从字符串中解析出InetAddress
类
但是此类也有@Beta
的注解,所以要谨慎使用。
参考资料:
原文作者:文/debugo
原文标题:baoq_v5_java