Guava常用工具类总结
- “我想写得更优雅,可是没人告诉我怎么写得更优雅”
- “Null的含糊语义让人很不舒服。Null很少可以明确地表示某种语义,例如,Map.get(key)返回Null时,可能表示map中的值是null,亦或map中没有key对应的值。Null可以表示失败、成功或几乎任何情况。使用Null以外的特定值,会让你的逻辑描述变得更清晰。”
此文档只是Guava最常用工具介绍,guava存在更多本文档没有介绍的api
Optional
Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)。
这里讲的optional 也是指jdk中的optional,其实二者类似,
但是编码使用gauva的optional,阿里巴巴编程规范会提醒换成jdk自带的optional
不要用isPressent判断一个对象是否为空
这种用法不但没有减少null的防御性检查,而且增加了Optional包装的过程,违背了Optional设计的初衷,因此开发中要避免这种糟糕的使用
1 | public enum TestEnum { |
考虑让方法返回optional
《Effective Java》中对方法返回Optional的一些观点
- 容器(包括,集合,映射,数组,stream,optional)都不应该包装在Optional进行返回,返回空的容器能让客户端免于处理一个Optional
- 如果无法返回结果,且没有返回结果客户端必须进行特殊的处理,那么就应该声明返回optional
- 返回optional并不是一个不需要成本的操作,无论返回空,还是非空,使用optional作为返回值的方法都是需要初始化的,所以optional在看重性能的情况下使用不当是一种性能的浪费
- 永远不要返回基本类型对于包装类型的Optional,这需要进行基本类型->包装类型->optional的三层包装,可以使用OptionalInt,optionallong等
PreConditions前置条件检查
前置条件:让方法调用的前置条件判断更简单。api比较见名知意,根据参数分为三种:
- 没有额外参数:抛出的异常中没有错误消息;
- 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
- 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。
例子:
- test1 缺点:if看起来臃肿,优点:可用抛出我们系统的自定义异常便于前端反馈
- test2 优点:简单直接,缺点:抛出的都是jdk中的异常,通一异常处理可能无法返回正确提示的通一结果集给前端
我们可用写一个带异常Class的工具类或者直接代理guava中的Predition 加一层 try-catch 使我其抛出我们系统的自定义异常
1 | public static String test1(Integer index, List<String> list) { |
ComparisonChain和Ordering
想象一个场景,人先根据age排序后根据height排序
实现comparable
写法1
1 |
|
缺点:
- 写法繁琐
- 忽略了空指针, return this.age - o.age; 这一句存在空指针的情况,对null进行拆箱直接NPE
- 维护复杂,再加一个存款,加逻辑复杂
写法2
1 |
|
解决了空指针,还实现了Null指在最前或者最后
缺点:
- 繁琐
- 语法化不明显,看懂代码太累了
- 维护复杂,再加一个存款,加逻辑复杂
写法3
1 | return ComparisonChain.start() |
优点:
- 优雅的处理空指针,传入比较器 Ordering.natural().nullsFirst() 让null在最前面
- 链式调用yyds
- 语义化明显:先比较age 后比较 height吗,null在最前面
- 更易于维护,只需要加一行
使用Comparator
典型的策略模式,将算法和业务分离开
大概基础的写法和上述的写法1、2差不多,不做过多赘述,直接上干货
请使用Ordering
方法 | 描述 |
---|---|
natural() |
对可排序类型做自然排序,如数字按大小,日期按先后排序 |
usingToString() |
按对象的字符串形式做字典排序[lexicographical ordering] |
from(Comparator) |
把给定的Comparator转化为排序器 |
reverse() |
获取语义相反的排序器 |
nullsFirst() |
使用当前排序器,但额外把null值排到最前面。 |
nullsLast() |
使用当前排序器,但额外把null值排到最后面。 |
compound(Comparator) |
合成另一个比较器,以处理当前排序器中的相等情况。 |
1 | public static void main(String[] args) { |
Throwables
Throwables 可用简化异常和错误的传播与检查,什么叫错误的传播——不捕获异常向上抛出,什么是异常的检查——多个catch,catch不同类型的异常进行不同的处理
//好用,但是差点意思
方法 | 含义 |
---|---|
Throwables.throwIfInstanceOf(e, FileNotFoundException.class); | 如果异常属于第二个参数的类型,那么抛出 |
Throwables.throwIfUnchecked(e); | 如果这个异常是非Unchecked Exception(即运行时异常)那么抛出 |
Throwable getRootCause(Throwable | |
List getCausalChain(Throwable | |
String getStackTraceAsString(Throwable |
例子:比较test1 和test2 使用Throwables似乎能让代码更加简洁
1 | public class ThrowablesLearn { |
不可变集合
- 使用场景:
- 如定义一系列状态比如吃饭,睡觉,过马路,需要根据这个状态判断是否可以玩手机,可以在类中定义集合包装这个三个状态,如果当前状态属于三个之一那么不可以玩手机,你可以使用基本的hashset,但是hashset的元素可以被更改,导致可能方法的判断和原本的语义出现出入
- 优点
- 当对象被不可信的库调用时,不可变形式是安全的;
- 不可变对象被多个线程调用时,不存在竞态条件问题
- 不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
- 不可变对象因为有固定不变,可以作为常量来安全使用。
1 | public class ImmutableLearn { |
新集合类型
Multiset
可以用两种方式看待Multiset
- 没有元素顺序限制的ArrayList当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:add(E)添加单个给定元素iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)size()返回所有元素的总个数(包括重复的元素)
- Map<E, Integer>,键为元素,值为计数
- Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作:
- count(Object)返回给定元素的计数。HashMultiset.count的复杂度为O(1),TreeMultiset.count的复杂度为O(log n)。
- entrySet()返回Set<Multiset.Entry>,和Map的entrySet类似。
- elementSet()返回所有不重复元素的Set,和Map的keySet()类似。
- 所有Multiset实现的内存消耗随着不重复元素的个数线性增长。
- Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作:
使用Multiset例子
1.统计一个list中单词出现的次数
1 | public class MultiSetLearn { |
SortedMultiset
//就这?
Multiset 接口的变种,它支持高效地获取指定范围的子集。
1 | public class SortMultiSetLearn { |
Multimap
Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式
合并两个map
1 | public static Map<String, Collection<Integer>> mergeMap1(Map<String, Integer> map1, Map<String, Integer> map2) { |
BiMap
BiMap是特殊的Map:
- 可以用 inverse()反转BiMap<K, V>的键值映射
- 保证值是唯一的,因此 values()返回Set而不是普通的Collection
- 在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。
1 | public class BiMapLearn { |
Table
使用场景:当你需要多个字段作为key时,你可能为这个key编写一个类,重写equals和hashMap。或者使用形同Map<FirstName, Map<LastName, Person>>的map结构,前者编码繁琐,后者使用不友好(第一个get后判空,后才能左第二次get)
Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。Table提供多种视图,以便你从各种角度使用它:
- rowMap():用Map<R, Map<C, V>>表现Table<R, C, V>。同样的, rowKeySet()返回”行”的集合Set。
- row(r) :用Map<C, V>返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。
- 类似的列访问方法:columnMap()、columnKeySet()、column(c)。(基于列的访问会比基于的行访问稍微低效点)
- cellSet():用元素类型为Table.Cell的Set表现Table<R, C, V>。Cell类似于Map.Entry,但它是用行和列两个键区分的。
使用示例
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
39
40
41
42
43
44
45
46
47
48
49
50
51public class TableLearn {
public static void main(String[] args) {
System.out.println(getNameByAgeAndNo1(17, "201715600"));
System.out.println(getNameByAgeAndNo2(17, "201715600"));
}
//根据年龄和编号 获取名字,编写KeyOfAgeAndNo 重写equals hashcode
public static String getNameByAgeAndNo1(int age,String no){
HashMap<KeyOfAgeAndNo, String> memory = Maps.newHashMap();
memory.put(KeyOfAgeAndNo.of(17,"201715600"),"大一的陈兴");
memory.put(KeyOfAgeAndNo.of(14,"0929"),"高一的陈兴");
memory.put(KeyOfAgeAndNo.of(20,"80303697"),"实习的陈兴");
return Optional.ofNullable(memory.get(KeyOfAgeAndNo.of(age, no)))
.orElseThrow(() -> new RuntimeException("查无此人"));
}
//编写KeyOfAgeAndNo 重写equals hashcode
static class KeyOfAgeAndNo{
Integer age;
String no;
static KeyOfAgeAndNo of( Integer age,String no){
KeyOfAgeAndNo res = new KeyOfAgeAndNo();
res.age=age;
res.no=no;
return res;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof KeyOfAgeAndNo)) return false;
KeyOfAgeAndNo that = (KeyOfAgeAndNo) o;
return Objects.equals(age, that.age) &&
Objects.equals(no, that.no);
}
public int hashCode() {
return Objects.hash(age, no);
}
}
//使用table
public static String getNameByAgeAndNo2(int age,String no){
HashBasedTable<Integer ,String,String>table=HashBasedTable.create();
table.put(17,"201715600","大一的陈兴");
table.put(14,"0929","高一的陈兴");
table.put(20,"80303697","实习的陈兴");
return Optional.ofNullable(table.get(age, no))
.orElseThrow(() -> new RuntimeException("查无此人"));
}
}
ClassToInstanceMap
使用场景,类型指向实例,使用普通map需要
示例
getInstanceByClass1需要进行强转因为map get方法返回object类型,不能限制key的类型
getInstanceByClass2则没有这种需要,且可以限定key的类型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
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class ClassToInstanceMapLearn {
//静态内部类实现单例 和ClassToInstanceMap 使用没有必要关系
private static class SingletonHolder {
private static final ClassToInstanceMapLearn INSTANCE;
static {
INSTANCE = new ClassToInstanceMapLearn();
}
}
private ClassToInstanceMapLearn() {
System.out.println("ClassToInstanceMapLearn Constructor");
}
public static ClassToInstanceMapLearn newInstance() {
return SingletonHolder.INSTANCE;
}
private static final Map<Class<?>, ? super ClassToInstanceMapLearn> Memory1 = new HashMap<>();
static {
Memory1.put(ClassToInstanceMapLearn.class, ClassToInstanceMapLearn.newInstance());
//加入从 简单工场拿SingletonHolder实例 强转化 将抛出异常
Memory1.put(SingletonHolder.class, ClassToInstanceMapLearn.newInstance());
}
public static <T extends ClassToInstanceMapLearn> T getInstanceByClass1(Class<T> clazz) {
//需要强转需要去判断 是否是clazz的实例 错误写法
return (T) Optional.ofNullable(Memory1.get(clazz))
.orElseThrow(() -> new RuntimeException("不存在"));
}
private static final ClassToInstanceMap<? super ClassToInstanceMapLearn> Memory2 = MutableClassToInstanceMap.create();
static {
Memory2.putInstance(ClassToInstanceMapLearn.class, ClassToInstanceMapLearn.newInstance());
//无法加入
// Memory2.put(SingletonHolder.class, ClassToInstanceMapLearn.newInstance());
}
public static <T extends ClassToInstanceMapLearn> T getInstanceByClass2(Class<T> clazz) {
//不需要强转
return Optional.ofNullable(Memory2.getInstance(clazz))
.orElseThrow(() -> new RuntimeException("不存在"));
}
public static void main(String[] args) {
System.out.println(getInstanceByClass1(ClassToInstanceMapLearn.class));
System.out.println(getInstanceByClass2(ClassToInstanceMapLearn.class));
}
}
集合工具类
guava 中的集合工具常常以集合名称加s出现
- Collections2 因为java存在Collections guava加了2
- Lists
- Maps
- Sets
- 等等 上面介绍的新集合类型也存在对应的工具类
这些工具类的共性
都存在静态工厂方法
为什么要使用静态工厂方法,它相比于构造方法(这里的静态工厂方法不是指,设计模式中的工厂模式)
《Effective Java》第一条 使用静态工厂方法代替构造器,给予了解答
静态工厂方法有名字
1
2
3
4
5
6//这一句是什么意思
BigInteger big1 = new BigInteger(10, 100, new Random(10));
System.out.println(big1);
//这一句又是什么意思
BigInteger big2 = BigInteger.probablePrime(10, new Random(10));
System.out.println(big2);静态工厂方法,不必每次都生成一个对象
1
2
3
4
5
6//虽然下面两句都在放屁,但是前者的屁更臭
boolean flag = new Random().nextInt() % 2 == 0;
//每次生成一个新对象
Boolean b1 = new Boolean(flag);
//不会生成新对象
Boolean b2 = Boolean.valueOf(flag);静态工厂方法可以返回任何原返回类型的子类型,如guava中的api
静态工厂的返回对象的类可也随着每次调用而变化,取决于入参类似于简单工厂模式
静态工厂方法返回的对象所属的类可以在,在编写百行该静态工厂方法的类时不存在,如JDBC数据库连接
Collections2
过滤
1
2
3
4
5
6
7
8public static void filterLearn() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, null);
Collection<Integer> filter = Collections2.filter(list, Objects::nonNull);
System.out.println(list);
System.out.println(filter);
//返回一个继承了AbstractCollection的集合
System.out.println(filter.getClass());
}转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Collections2Learn {
private Integer nums;
public Collections2Learn(Integer nums) {
this.nums = nums;
}
public static void transformLearn() {
com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn c1 = new com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn(1);
com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn c2 = new com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn(2);
com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn c3 = new com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn(3);
List<com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn> list = Arrays.asList(c1, c2, c3);
Collection<Integer> transform = Collections2.transform(list,
t -> Optional.ofNullable(t)
.orElse(new com.cuzz.miscellaneous.guava.collectionutils.Collections2Learn(0)).nums);
System.out.println(transform);
System.out.println(transform.getClass());
}
}全排列
1
2
3
4
5
6
7
8public static void main(String[] args) {
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);
Collection<List<Integer>> lists = Collections2.orderedPermutations(list);
lists.forEach(System.out::println);
Collection<List<Integer>> permutations = Collections2.permutations(list);
System.out.println("====");
permutations.forEach(System.out::println);
}
lists
切割
1
2
3
4
5
6
7//获取一个字符串中的全部字符,返回不可变集合
ImmutableList<Character> chars = Lists.charactersOf("123");
System.out.println(chars);
//按照大小分割list
ArrayList<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7);
List<List<Integer>> partitionList = Lists.partition(intList, 2);
System.out.println(partitionList);
sets
交集
1
2
3
4
5HashSet<Integer> set1 = Sets.newHashSet(1, 2, 3);
HashSet<Integer> set2 = Sets.newHashSet(1, 2, 4,5);
//返回交集
Sets.SetView<Integer> intersection = Sets.intersection(set1, set2);
System.out.println(intersection);差集
1
2
3//返回set1中存在 s2中不存在的元素
System.out.println(Sets.difference(set1, set2));
System.out.println(Sets.difference(set2, set1));并集
1
2
3//返回并集
Sets.SetView<Integer> union = Sets.union(set1, set2);
System.out.println(union);过滤
1
System.out.println(Sets.filter(union, t -> t % 2 == 0));
maps
uniqueIndex 根据传入的function生成map
1
2
3ArrayList<Integer> list1 = Lists.newArrayList(1, 2, 3, 4, 6);
//传入function根据function生成map 要求 key 不可重复
ImmutableMap<String, Integer> integerImmutableMap = Maps.uniqueIndex(list1, String::valueOf);获取两个map的不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//如果你预计hashMap的大小请使用这个方法
HashMap<String, Integer> map1 = Maps.newHashMapWithExpectedSize(3);
map1.put("1", 1);
map1.put("2", 2);
map1.put("3", 3);
map1.put("4", 3);
map1.put("5", 5);
ArrayList<Integer> list1 = Lists.newArrayList(1, 2, 3, 4, 6);
//传入function根据function生成map 要求 key 不可重复
ImmutableMap<String, Integer> integerImmutableMap = Maps.uniqueIndex(list1, String::valueOf);
MapDifference<String, Integer> difference = Maps.difference(map1, integerImmutableMap);
//左边独有key
Map<String, Integer> mapLeft = difference.entriesOnlyOnLeft();
//右边独有key
Map<String, Integer> mapRight = difference.entriesOnlyOnRight();
//两个map相同key 但是不同value
Map<String, MapDifference.ValueDifference<Integer>> valueDifferenceMap = difference.entriesDiffering();
//左边map的值 有边map的值
System.out.println(valueDifferenceMap.get("4").rightValue());
System.out.println(valueDifferenceMap.get("4").leftValue());过滤
1
2
3
4
5
6
7
8
9
10
11
12//过滤map 中的Entries
Map<String, Integer> filterEntriesMap = Maps.filterEntries(map1, e -> {
assert e != null;
return StringUtils.equals(e.getKey(), String.valueOf(e.getValue()));
});
//过滤key
Map<String, Integer> filterKeysMap = Maps.filterKeys(map1, StringUtils::isNotBlank);
//过滤value
Map<String, Integer> filterValuesMap = Maps.filterValues(map1, v -> {
assert v != null;
return v % 2 == 0;
});根据map构造转换器
1
2
3
4HashBiMap<String, Integer> biMapForConverter = HashBiMap.create(integerImmutableMap);
Converter<String, Integer> converter = Maps.asConverter(biMapForConverter);
System.out.println(converter.convert("1"));
Iterable<Integer> convertRes = converter.convertAll(Arrays.asList("1", "2"));转换
1
2
3Map<String, String> transformEntriesMap = Maps.transformEntries(map1, (key, value) -> String.valueOf(map1.get(key)));
//同样还存在
// Maps.transformValues()
字符串处理
连接器Joiner
连接任何实现了Iterable结果的类型
1
2
3
4
5
6
7
8
9
10List<Integer> list = Arrays.asList(1, 2, 3, 4, null, 5, null);
//跳过null
String str1 = Joiner.on("-").skipNulls().join(list);
System.out.println(str1);
//用NNNN代替空
String str2 = Joiner.on("-").useForNull("NNNN").join(list);
System.out.println(str2);
//空指针
String str3= Joiner.on("-").join(list);
System.out.println(str3);连接map
1
2
3
4
5
6HashMap<String, String> map = Maps.newHashMap();
map.put("a","1");
map.put("b","2");
//每一个k-v连接方式为\n kv连接方式为->
String str1 = Joiner.on("\n").withKeyValueSeparator("->").join(map);
System.out.println(str1);连接实现了Appendable的任何类型
1
2StringBuilder str3 = Joiner.on("-").appendTo(new StringBuilder(), Arrays.asList("1", "a","2"));
System.out.println(str3);
分割器
JDK内建的字符串拆分工具有一些古怪的特性。比如,String.split悄悄丢弃了尾部的分隔符。 问题:”,a,,b,”.split(“,”)返回?
1.“”, “a”, “”, “b”, “”
2.null, “a”, null, “b”, null
3.“a”, null, “b”
4.“a”, “b”
5.以上都不对
正确答案是5:””, “a”, “”, “b”。只有尾部的空字符串被忽略了。 Splitter使用令人放心的、直白的流畅API模式对这些混乱的特性作了完全的掌控。
分割成list
1
2
3
4
5
6
7String str="1-2 -3 - 4- - - ";
List<String> list1 = Splitter.fixedLength(2).splitToList(str);
System.out.println(list1);
List<String> list2 = Splitter.on("-").splitToList(str);
System.out.println(list2);
List<String> list3 = Splitter.on("-").trimResults().splitToList(str);
System.out.println(list3);分割成map
1
2
3
4String str2="1#2-2#3-3#1";
//每一组entry使用的是-分割 k和v使用的#分割
Map<String, String> map = Splitter.on("-").withKeyValueSeparator("#").split(str2);
map.forEach((k,v)-> System.out.println(k+"->"+v));分割成Iterable
1
2Iterable<String> stringIterable = Splitter.on("-").split(str);
stringIterable.iterator().forEachRemaining(System.out::println);
字符匹配器
方法 | 描述 |
---|---|
anyOf(CharSequence) |
枚举匹配字符。如CharMatcher.anyOf(“aeiou”)匹配小写英语元音 |
is(char) |
给定单一字符匹配。 |
[inRange(char, char) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/CharMatcher.html#inRange(char, char)) |
给定字符范围匹配,如CharMatcher.inRange(‘a’, ‘z’) |
1 | //删除字符 |
字符集和大小写格式
Charsets
针对所有Java平台都要保证支持的六种字符集提供了常量引用。尝试使用这些常量,而不是通过名称获取字符集实例。- CaseFormat
格式 | 范例 |
---|---|
LOWER_CAMEL |
lowerCamel |
LOWER_HYPHEN |
lower-hyphen |
LOWER_UNDERSCORE |
lower_underscore |
UPPER_CAMEL |
UpperCamel |
UPPER_UNDERSCORE |
UPPER_UNDERSCORE |
1 | CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, "caseFormat") |