Guava 工程包含了若干被Google的 Java项目广泛依赖的核心库。或者说是 Google 工程师的瑞士军刀。Guava 里包含了集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中。
想要使用 Guava 首先需要添加依赖。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
Guava 用 Optional表示可能为 null 的 T 类型引用。一个 Optional 实例可能包含非 null 的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是 null 值,而是用存在或缺失来表示。但 Optional 从不会包含 null 值引用。
当某个方法有可能返回 null 值时候,我们可以返回一个 Optional 的泛型对象。代码如下:
/**
* 使用 empty 代替 null
*
* @param num
* @return
*/
private Optional<Integer> getNum(int num) {
if (num > 0) {
return Optional.of(num);
} else {
//使用 empty 代替 null
return Optional.empty();
}
}
public void testGetNum() {
Optional<Integer> num = this.getNum(5);
if (num.isPresent()) {
System.out.println(num.get());
}
//也可以使用 ifPresent 方法
num.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
使用 Optional.of()
来创建一个可用对象,使用 Optional.empty();
来创建一个不可引用的对象。用来替代之前直接返回 null 的情况。
使用 Optional 时,可以通过Optional.isPresent()
方法,或者是Optional.ifPresent()
方法来使用。
可以使用 filter 方法 对返回结果做二次判断。
Predicates.in()
方法,判断返回结果是否在集合内。 public static void testIn(){
List<AccountInfo> data = new ArrayList<>();
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName("aaa");
accountInfo.setBalance(123);
data.add(accountInfo);
AccountInfo accountInfo2 = new AccountInfo();
accountInfo2.setName("aaa");
accountInfo2.setBalance(123);
System.out.println("equals:"+accountInfo.equals(accountInfo2));
Optional<AccountInfo> optional = Optional.of(accountInfo2);
//通过 调用对象的 equals 方法来判断是否存在
optional.filter(Predicates.in(data)).ifPresent(new Consumer<AccountInfo>() {
@Override
public void accept(AccountInfo accountInfo) {
System.out.println("1:"+accountInfo.toString());
}
});
}
in 方法调用的是对象的 equals 方法,所以如果对象的 equals 方法返回 true,那么就会访问到该对象,否则就访问不到。
Predicates 类还有一些其他方法,用于过滤 Optional 对象。可以参考 Predicates 的API。
Guava 在 Preconditions 类中提供了若干前置条件判断的实用方法。这些方法让调用的前置条件判断更简单。
/**
* 检查参数是否为 true
* @param attr
*/
private static void testAttrs(boolean attr){
Preconditions.checkArgument(attr,"参数错误");
}
/**
* 检查参数是否为 null
* @param accountInfo
*/
private static void testAttr2(AccountInfo accountInfo) {
Preconditions.checkNotNull(accountInfo, "参数不能为 null");
}
/**
* 检查索引是否越界
* @param index
* @param size
*/
private static void testAttr3(int index, int size) {
Preconditions.checkElementIndex(index, size);
}
/**
* 检查区间是否越界
* @param start
* @param end
* @param size
*/
private static void testAttr4(int start,int end, int size) {
Preconditions.checkPositionIndexes(start,end, size);
}
public static void main(String[] args) {
//testAttrs(Boolean.FALSE);
//testAttr2(null);
//testAttr3(3, 2);
testAttr4(2, 4, 3);
}
Guava 建议直接静态导入,就不用 Preconditions.checkArgument(), 来调用了,直接使用 checkArgument(),比较简洁。
排序器[Ordering]是 Guava 流畅风格比较器[Comparator]的实现,它可以用来为构建复杂的比较器,以完成集
合排序的功能。
Guava 提供了 3 中常见的比较器。
默认比较器,针对可比较对象。
使用字符串的字典顺序排序。
插入顺序的倒序。
还可以根据 Comparator 生成 Ordering.from(Comparator)
。
或者自定义比较器:
private static void lengthOrdering(List<String> list) {
Ordering<String> lengthOrdering = new Ordering<String>() {
@Override
public int compare(@Nullable String left, @Nullable String right) {
return left.length() - right.length();
}
};
list.sort(lengthOrdering);
//倒序
list.sort(lengthOrdering.reverse());
}
如果要根据对象的某个具体的属性排序,可以使用 onResultOf 方法。
private static void lengthOrdering2(List<AccountInfo> list) {
Ordering<AccountInfo> lengthOrdering = Ordering.natural().nullsFirst().onResultOf(new Function<AccountInfo, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable AccountInfo accountInfo) {
return accountInfo.getBalance();
}
});
list.sort(lengthOrdering);
//倒序
list.sort(lengthOrdering.reverse());
}
注意:这里跟平常的链式调用的不同是,执行顺序是从后往前执行!!!比如上面的例子。
Ordering.natural().nullsFirst().onResultOf()
首先执行 onResultOf 获取所有对象的 balance 值,然后将 null 值排在最前,然后再按照数字顺序排序。
Ordering 还提供了一些集合遍历、迭代的方法。比如:
获取最大的 k 个元素
获取最小的一个元素
判断对象是否按照给定的排序器排序
创建对象的不可变拷贝是一项很好的防御性编程技巧。Guava 为所有 JDK 标准集合类型和 Guava 新集合类型都提供了简单易用的不可变版本。
使用方式:
public static void testImmutable(){
ImmutableSet<String> immutableSet = ImmutableSet.<String>builder().add("c")
.add("b").build();
for (String s : immutableSet) {
System.out.print(s + ",");
}
}
Guava 还提供了有序的不可变集合。
private static void testSort(){
ImmutableSortedSet<String> immutableSortedSet = ImmutableSortedSet.of("c", "a", "b");
for (String s:immutableSortedSet) {
System.out.print(s + ",");
}
}
Guava 提供了一个新集合类型 Multiset ,它可以多次添加相等的元素。你可以将 Multiset 类比成 Map<E, Integer>,键为元素,值为计数的一个 Map 集合。添加相同元素会增加计数,删除元素会减少计数。
private static void testMultiset(){
Multiset<String> multiset = HashMultiset.create();
multiset.add("a");
multiset.add("a");
multiset.add("b");
multiset.add("c");
multiset.setCount("b", 3);
System.out.println(multiset.contains("a"));
System.out.println(multiset.count("a"));
System.out.println(multiset.remove("b"));
System.out.println(multiset.count("b"));
System.out.println(multiset.size());
}
当然,Guava 还提供了 TreeMultiset、LinkedHashMultiset、ConcurrentHashMultiset、ImmutableMultiset
的实现,需要注意的是,这些类都提供了 create 方法,所以尽量不要使用 new 来创建对象。更贴心的是,Guava 还提供了一个 SortedMultiset ,默认排序。
private static void testSortMultiset(){
SortedMultiset<Integer> multiset = TreeMultiset.create();
multiset.addAll(Lists.newArrayList(3, 2, 5, 6, 9, 2, 5, 6, 8));
for (int num:multiset) {
System.out.print(num + ",");
}
}
Guava 的 Multimap 可以很容易地把一个键映射到多个值。换句话说,Multimap 是把键映射到任意多个值的一般方式。你可以将 Multimap 类比成 Map<K, List> 或 Map<K, Set>。
private static void testMultimap(){
Multimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.put("a", 1);
multimap.put("a", 2);
multimap.put("a", 3);
multimap.put("a", 4);
System.out.println(multimap.containsKey("a"));
System.out.println(multimap.containsEntry("a", 1));
System.out.println(multimap.containsEntry("a", 0));
System.out.println(multimap.remove("a", 3));
System.out.println(Arrays.deepToString(multimap.get("a").toArray()));
}
Multimap 还提供了若干个视图
返回一个 Map<K,Collection>形式的视图。
返回一个 Collection<Map.Entry<K, V>> 包含所有的键值对。
返回一个 Set
返回一个 Multiset
返回一个 Collection
Multimap 的具体实现类包括 ArrayListMultimap、HashMultimap、TreeMultimap、ImmutableListMultimap 等。
BiMap 是一个特殊的 Map ,可以用 inverse()反转 BiMap<K, V>的键值映射。所以,在插入值时需要注意,保证值时唯一的。强制将某个值插入到 BiMap 中,会更新该值对应的 key。
private static void testBiMap(){
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("a", 1);
biMap.put("b", 2);
System.out.println(biMap.inverse().get(2));
biMap.forcePut("c", 2);
System.out.println(biMap.inverse().get(2));
}
BiMap 的常见实现类 HashBiMap、ImmutableBiMap、EnumBiMap、EnumHashBiMap。
Guava 提供了大量的集合工具。包括 Lists、Maps、Sets 等等。
Guava 提供了能够推断范型的静态工厂方法。
private static void testUtil1(){
List<AccountInfo> accountInfoList = Lists.newArrayList();
List<AccountInfo> accountInfoList2 = Lists.newArrayListWithCapacity(3);
List<AccountInfo> accountInfoList3 = Lists.newArrayListWithExpectedSize(3);
List<AccountInfo> accountInfoList4 = Lists.newArrayList(new AccountInfo());
}
Iterables 提供了一些集合操作的方法。
private static void testUtil1(){
List<AccountInfo> accountInfoList = Lists.newArrayList();
List<AccountInfo> accountInfoList2 = Lists.newArrayListWithCapacity(3);
List<AccountInfo> accountInfoList3 = Lists.newArrayListWithExpectedSize(3);
List<AccountInfo> accountInfoList4 = Lists.newArrayList(new AccountInfo());
Iterable<Integer> concatenated = Iterables.concat(Ints.asList(1, 2, 3),
Ints.asList(3, 4, 5));
System.out.println(Iterables.frequency(concatenated, 3));
System.out.println(Iterables.getFirst(concatenated, 0));
}
串联多个 iterables 的懒视图
返回对象在 iterable 中出现的次数
把 iterable 按指定大小分割,得到的子集都不能进行修改操作
返回 iterable 的第一个元素,若 iterable 为空则返回默认值
返回 iterable 的最后一个元素,若 iterable 为空则抛出NoSuchElementException
如果两个 iterable 中的所有元素相等且顺序一致,返回 true
返回 iterable 的不可变视图
限制 iterable 的元素个数限制给定值
获取 iterable 中唯一的元素,如果 iterable 为空或有多个元素,则快速失败
Guava 提供了很多标准的集合运算的方法。比如集合间的交集、并集、补集等
返回两个集合的并集,并去除重复元素
返回两个集合的交集
返回 set1 中,不存在在 set2 中的元素,或者说是 set2 在 set1 中的绝对补集
private static void testSets() {
Set<Integer> set = Sets.union(Sets.newHashSet(1, 2, 3), Sets.newHashSet(3, 4, 5));
System.out.println(Arrays.deepToString(set.toArray()));
Set<Integer> set2 = Sets.intersection(Sets.newHashSet(1, 2, 3), Sets.newHashSet(3, 4, 5));
System.out.println(Arrays.deepToString(set2.toArray()));
Set<Integer> set3 = Sets.difference(Sets.newHashSet(1, 2, 3), Sets.newHashSet(1,2));
System.out.println(Arrays.deepToString(set3.toArray()));
}
Maps 里有两个比较有意思的方法。
根据给定的规则过滤键值对
用来比较两个 Map 以获取所有不同点。
这个方法可以将一个 Iterable 对象 转成 Map 集合。
PS: 有点没看懂这个方法,有了解的朋友可以留言
private static void testMaps() {
Map<String, Integer> hashMap = Maps.newHashMap();
hashMap.put("a", 1);
hashMap.put("bb", 2);
hashMap.put("ccc", 3);
Map<String, Integer> hashMap1 = Maps.filterKeys(hashMap, new Predicate<String>() {
@Override
public boolean apply(@Nullable String input) {
return input.length() > 2;
}
});
System.out.println(Arrays.deepToString(hashMap1.entrySet().toArray()));
Map<String, Integer> hashMap2 = Maps.filterEntries(hashMap, new Predicate<Map.Entry<String, Integer>>() {
@Override
public boolean apply(Map.@Nullable Entry<String, Integer> input) {
return input.getValue() > 2;
}
});
System.out.println(Arrays.deepToString(hashMap2.entrySet().toArray()));
Map<Integer, String> hashMap3 = Maps.uniqueIndex(Lists.newArrayList("sss", "ss", "ssss"),
new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String input) {
return input.length();
}
});
System.out.println(Arrays.deepToString(hashMap3.entrySet().toArray()));
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("a", 1, "b", 2, "c", 3);
MapDifference<String, Integer> diff = Maps.difference(left, right);
diff.entriesInCommon(); // {"b" => 2}
diff.entriesInCommon(); // {"b" => 2}
diff.entriesOnlyOnLeft(); // {"a" => 1}
diff.entriesOnlyOnRight(); // {"d" => 5}MapDifference
}
针对所有类型的集合接口,Guava 都提供了 Forwarding 抽象类以简化装饰者模式的使用
private static void testForwarding(){
ForwardingList<String> forwardingList = new ForwardingList<String>() {
final List<String> delegate = new ArrayList<>(); // backing list
@Override
protected List<String> delegate() {
return delegate;
}
@Override
public void add(int index, String element) {
System.out.println("add:" + element);
super.add(index, element);
}
@Override
public String get(int index) {
System.out.println("get:" + index);
return super.get(index);
}
};
forwardingList.add(0,"aaa");
System.out.println(forwardingList.get(0));
}
类似于我们对 List 做了一层装饰(或者说说封装),在执行 List 的相关方法时,可以做一些其他的处理。
当然也可以通过一个类,来实现 Forwarding 的相关接口,比如:
class AddLoggingList<E> extends ForwardingList<E> {
final List<E> delegate; // backing list
@Override protected List<E> delegate() {
return delegate;
}
@Override public void add(int index, E elem) {
log(index, elem);
super.add(index, elem);
}
@Override public boolean add(E elem) {
return standardAdd(elem); // 用add(int, E)实现
}
@Override public boolean addAll(Collection<? extends E> c) {
return standardAddAll(c); // 用add实现
}
}
上面的 AddLoggingList 在使用时与普通的 ArrayList 并没有什么区别,唯一的区别是,使用 AddLoggingList 时,添加元素会打印出相应的 log。
通常来说,Guava Cache 适用于:
如果你的场景符合上述的每一条,Guava Cache 就适合你。
private static LoadingCache<String, AccountInfo> loadingCache = CacheBuilder.newBuilder()
//最大缓存数量
.maximumSize(10).build(new CacheLoader<String, AccountInfo>() {
@Override
public AccountInfo load(String key) throws Exception {
System.out.println("load 方法执行:" + key);
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName(key);
return accountInfo;
}
});
private static void testCaches() throws ExecutionException {
//如果缓存中没有该值,就会添加新值
loadingCache.get("aaa").setBalance(123);
System.out.println(loadingCache.get("aaa"));
}
可以通过 Builder 来创建一个缓存对象(或者叫本地缓存池)。可以设定缓存对象的一些基本参数,比如最大数量等。
load 方法用来加载需要缓存的对象。这些对象可以是手动创建的,也可以是从DB、Redis等其他外部存储加载进来的。
可以直接使用 get() 方法获取 LoadingCache 中的对象。注意:这里如果在缓存中没有找到对应的值,通过 load 方法创建一个新的值添加到缓存中,并返回该值。
如果我们不希望 使用默认的 load 方法创建对象。也可以在使用 get 方法时传入一个 Call 对象,用来创建需要缓存的值。
private static void testCaches() throws ExecutionException {
//如果缓存中没有该值,就会添加新值
loadingCache.get("aaa").setBalance(123);
System.out.println(loadingCache.get("aaa"));
//如果没有值,就会执行 call 方法,创建对应的值,并返回
AccountInfo accountInfo = loadingCache.get("bbb", new Callable<AccountInfo>() {
@Override
public AccountInfo call() throws Exception {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName("bbb");
accountInfo.setPwd("call-add");
return accountInfo;
}
});
System.out.println(accountInfo.toString());
}
如果你执行了上面的代码,就会发现,获取 “aaa” 时,调用了 load 方法,而获取 “bbb” 时,则没有执行 load 方法。
也可以直接向缓存中插入值。
loadingCache.put("ccc",accountInfo);
System.out.println(loadingCache.get("ccc"));
不过,尽量优先使用 Cache.get(K, Callable) 方法
Guava Cache 提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。
我们可以指定缓存的容量,通过 maximumSize() 方法,当超过容量时,缓存将尝试回收最近没有使用或总体上很少使用的缓存项
private static LoadingCache<String, AccountInfo> loadingCache = CacheBuilder.newBuilder()
//最大缓存数量
.maximumSize(10).build(new CacheLoader<String, AccountInfo>() {
@Override
public AccountInfo load(String key) throws Exception {
System.out.println("load 方法执行:" + key);
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName(key);
return accountInfo;
}
});
另外,Guava 缓存还提供了一种根据“权重(weights)” 来删除缓存的方式。你可以使用 CacheBuilder.weigher(Weigher)指定一个权重函数,并且用 CacheBuilder.maximumWeight(long)指定最大总重。
注意:maximumWeight() 方法与 maximumSize() 方法不能同时使用
private static void testEviction() throws ExecutionException {
LoadingCache<String, AccountInfo> cache = CacheBuilder.newBuilder()
//最大缓存数量
//.maximumSize(10)
//最大权重
.maximumWeight(10L)
.weigher(new Weigher<String, AccountInfo>() {
@Override
public int weigh(String key, AccountInfo value) {
return value.getBalance();
}
})
.build(new CacheLoader<String, AccountInfo>() {
@Override
public AccountInfo load(String key) throws Exception {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName(key);
return accountInfo;
}
});
for (int i = 0; i < 12; i++) {
AccountInfo accountInfo = new AccountInfo();
accountInfo.setName("aaa" + i);
accountInfo.setBalance(i);
cache.put(accountInfo.getName(), accountInfo);
}
System.out.println(cache.asMap().size());
System.out.println(Arrays.deepToString(cache.asMap().entrySet().toArray()));
}
CacheBuilder 提供两种定时回收的方法:
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache 可以把缓存设置为允许垃圾回收:
任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
注意:这里的清除缓存并不一定会删除元素,准确的说法是将缓存置为无效。如果你调用 Cache.asMap 方法,你会发现,里面的元素并没有少。但是当调用 invalidate() 方法之后,再次调用 get() 方法,会重新执行 load 方法,说明原来的数据已经是无效的了
用分隔符把字符串序列连接起来也可能会遇上不必要的麻烦。如果字符串序列中含有 null,那连接操作会更难。Fluent 风格的 Joiner 让连接字符串更简单。
private static void testJoiner(){
//使用 '-' 链接,遇到 null 值用 '#' 代替
Joiner joiner = Joiner.on("-").useForNull("#");
System.out.println(joiner.join("aaa","bbb",null,"","ccc"));
//忽略空值
joiner = Joiner.on("-").skipNulls();
//将拼接好的 String append 到 StringBuilder 之后
StringBuilder sb = Joiner.on("-").appendTo(new StringBuilder("sss"), "aaa","bbb");
System.out.println(sb.toString());
}
非常友好的 String 拆分器。
private static void testSplitter(){
//使用 ; 分隔,并且忽略结果集中的空字符串,移除结果中的开头和结尾的空白字符
Splitter splitter = Splitter.on(';').omitEmptyStrings().trimResults();
Iterable<String> arr = splitter.split("a;aa ; a ;;;");
for (String str : arr) {
System.out.print(str+",");
}
System.out.println();
//按照固定长度拆分
arr = Splitter.fixedLength(3).split("aaabbbcccdd");
for (String str : arr) {
System.out.print(str+",");
}
System.out.println();
}
CharMatcher 实例代表着某一类字符,如数字或空白字符。使用 CharMatcher 的好处更在于它提供了一系列方法,让你对字符作特定类型的操作:修剪[trim]、折叠[collapse]、移除[remove]、保留[retain]等等。
private static void testCharMatcher(){
//去掉控制字符(回车、换行、tab等)
String noControl = CharMatcher.javaIsoControl().removeFrom("aa\tbb\ncc\\d\\.");
System.out.println(noControl);
String theDigits = CharMatcher.inRange('0', '9').retainFrom("1a2b3c4d");
System.out.println(theDigits);
//去除两端的空格,并把中间的连续空格替换成 '-'
String spaced = CharMatcher.whitespace().trimAndCollapseFrom("aaaa bbb cc ", '-');
System.out.println(spaced);
//用*号替换所有数字
String noDigits = CharMatcher.inRange('0', '9').replaceFrom("1a2b3c4d", "*");
System.out.println(noDigits);
String lowerAndDigit = CharMatcher.inRange('0', '9').or(CharMatcher.javaLowerCase()).retainFrom("1Aa2Bb3Cc");
System.out.println(lowerAndDigit);
}