写了好多和Java集合类有关的文章,学习了好多集合类的用法,有没有感觉还是有一些常见的需求集合类没有办法满足呢?需要自己使用Java集合中的类去实现,但是这种常用的轮子Google和apache都帮我们造好啦.
Java相关的工具包中有两个很有名,Google Guava
和Apache Commons
,今天就来看一下Guava中实现的一些其他的集合类,基本上都是在JDK的集合类上做了一些增强.
Immutable Collections -> 真正的不可修改的集合
在上文Java Collections中,提到了Collections
类中提供了一些可以返回集合不可变视图的方法,我们现在来试用一下.
我们新建一个包含5个元素的list.然后创建一个它的不可变视图.
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
List<Integer> umList = Collections.unmodifiableList(list);
复制代码
经过上面的步骤,我们拿到了umList
,他是不可变的,但是list
没有消失,它仍然是可变的,我们可以通过给list
添加一个值来改变umList
的元素集.
因为在Collections.unmodifiableList
中,持有了一个list
的引用,所有对list的更改也会同步体现在umList
上.
而且上面的代码中,多了一个中间变量list
.因为我们不需要它,我们创建它只是为了获取后面的不可变集合.比较繁琐.
使用Guava
怎么做呢?像下面这样:
ImmutableCollection li = ImmutableList.of(1, 2, 3, 4, 5);
复制代码
是不是感觉清晰了很多,同时,这个li
是真正的不可变的.
ImmutableList还提供了多种创建的方法,比如:
- 使用任意个元素的参数直接创建.
- 使用
copyOf
从一个已有的List来创建 - 提供Builder模式来进行链式调用.
上面的代码以ImmutableList
举例,但是Guava
还提供了ImmutableSet
,ImmutableMap
,ImmutableCollection
等类,可以根据需要的数据结构分别调用.
MultiMap -> Map<Strinh,List>的另一种解决办法
我们经常会有一种需求,需要在Map结构里面存List/Set.
比如,统计用户的签到日期用来画日历,那么我们想要的数据是:name->[2019-04-01,2019-04-28]
这样子的数据结构.
那么我们先加入一个用户在5月1号的签到记录怎么办呢?写一下子代码,
// 模拟已有的数据结构
static Map<String, List<String>> userSign = new HashMap<>();
// 新放进去一条数据
public static void putIt(String name, String date) {
// 正式的逻辑部分
List<String> dates = userSign.getOrDefault(name, new ArrayList<>());
if (!dates.contains(date)) {
dates.add(date);
}
userSign.put(name, dates);
}
复制代码
可以看到比较麻烦,而且要不是有Map的getOrDefault()
方法,代码还得多几行,因为还要分为已存在和未存在来搞..
Guava中提供了一种数据结构,来保存这种一个key对应多个value的情况,就是MultiMap
.
虽然他的名字带有map
,但是看源码可以发现,他的类生命没有继承Map接口.
要使用MultiMap
来实现上面的方法,只需要这样子:
ArrayListMultimap<String, String> userSign = ArrayListMultimap.create();
userSign.put("huyan", "2019-05-01");
userSign.put("huyan", "2019-05-02");
List<String> huyanSign = userSign.get("huyan");
复制代码
是的,直接声明放入就好了,要消费的时候,使用get方法可以获得一个Arratlist
,遍历操作即可.
下载了Guava的源码就可以发现,其实他里面就是用Map<String,List>来实现的,这是定义的地方:
可以看到定义了一个:Map<K,Collection<V>
.定义为Collection
是为了实现其他的集中数据结构.
比如:
HashMultimap
的值是放在set中LinkedListMultimap
的值放在LinkedList中.
等等.
Multiset -> 一个名叫set的计数器
老实说这个挺好用的,但是为啥要叫set呢...大家对set的印象都是不可以放入重复元素,但是Multiset
的作用就是对重复元素计数..
使用方式如下:
Multiset<String> s = HashMultiset.create();
s.add("pf");
s.add("pf");
s.add("pf");
s.add("hh");
// i =3
int i = s.count("pf");
复制代码
这个和我前几天写的计数器的作用是一样的,计数器传送门.
内部实现使用HashMap来保存key->count
的一个映射关系.
BiMap -> value也不可以重复的双向Map
这个类是真的实现了JDK的Map接口的,使用它必须保证key和value都没有重复值.因为他支持根据value获取key,即将HashMap的key和value进行调换.
BiMap<String, String> m = HashBiMap.create();
m.put("pf", "111");
String value = m.get("pf");
String key = m.inverse().get("111");
复制代码
这个类适合用在key和value都唯一,且经常会出现根据value来获取key的情况.
Table -> Map<String,Map<String,Object>>的解决方案
碰到多个索引一个结果的时候,Map<String,Map<String,Object>>
这种实现方式当然是可以的,但是实在是太让人难以看懂和编码了.
Guava提供了一种名叫Table
的数据结构,可以优雅的实现.
使用如下:
Table<Integer, Integer, String> tt = HashBasedTable.create();
tt.put(1, 2, "huyan");
String name = tt.get(1, 2);
Map<Integer, String> row = tt.row(1);
Map<Integer, String> colum = tt.column(1);
Set<Table.Cell<Integer, Integer, String>> ha = tt.cellSet();
复制代码
初始化方式和上面的几种结构没有什么区别,都是通过静态工厂方法进行初始化,get和put方法根据两个索引来存放和唯一索引一条数据.
此外,还可以拿到某一行或者某一列的Map结构,可以拿到所有单元格的一个set.极大的方便了处理类似于表格的数据.
当然,看一下源码就会发现,其实Table底层也是使用两个map的嵌套实现的,但是Java语言嘛,讲究的就是一个封装,虽然我们可以自己实现,但是我们应该做的是去学习一下好的实现方法,看懂,理解并且能够在其他场景应用类似的思想,而不是每次都要自己写两个map.毕竟现成好用的轮子,在适用的场景下还是应该多多使用加深理解.
ComparisonChain -> 功能强大且好看的多字段比较方法
在面对多个字段排序比较的场景,一般我们的代码都会比较难看,比如对下面这个类:
private static class Student {
int id;
String name;
int age;
}
复制代码
我们现在是没有办法对其进行比较,或者进行排序的,因为没有定义对于他的比较策略,假设我们的策略是:
首先比较id,id相等比较name,name相等比较age,这是一种很常见的多字段比较策略.那么我们给Student
类加上Comparable
的实现.
// 为了简洁起见,没有写外部类代码,只贴了重写的comparTo方法.
@Override
public int compareTo(Object o) {
Student s = (Student) o;
int idResult = s.id - this.id;
int nameResult = s.name.compareTo(this.name);
int ageResult = s.age - this.age;
return idResult != 0 ? idResult : nameResult != 0 ? nameResult : ageResult;
}
复制代码
最后那一串?:?:
是不是看的眼睛疼,当然你可以选择三重if-else,我觉得也没好到哪里去.
但是可以使用ComparisonChain
,这名字一看就是比较链,非常适合我们的场景.改写如下:
@Override
public int compareTo(Object o) {
Student s = (Student) o;
return ComparisonChain.start().compare(s.id, this.id).compare(s.name, this.name).compare(s.age, this.age).
result();
}
复制代码
这个代码可读性的提升可谓是十分大了,语义十分清晰,可以很清楚的看懂开始,先比较,再比较,返回结果这样的一个过程.
Ordering -> 多种比较器的组合
上面的ComparisonChain解决了在实现Comparable时候多个字段排序的情况,那么JDK中有很多的方法需要提供外部的比较器,这时候我们希望以多个比较器进行排序呢?
Ordering提供了使用多个比较器的方法,它自身实现了Comparator
接口,可以集成多个比较器.
Ordering<Student> studentOrdering = Ordering.compound(Arrays.asList((o1, o2) -> {
return ComparisonChain.start().result();
}, (o1, o2) -> 0, (o1, o2) -> 0));
Collections.sort(students, studentOrdering);
复制代码
上面的代码中,使用Ordering集成了多个比较器,之后将其自身传入Collections
的sort
方法,使用它对list进行排序.
ChangeLog
2019-05-01 完成以上皆为个人所思所得,如有错误欢迎评论区指正。
欢迎转载,烦请署名并保留原文链接。
联系邮箱:huyanshi2580@gmail.com
更多学习笔记见个人博客------>呼延十