要说Java程序员必须要学习的东西的话,Spring当仁不让,必须是第一个框架,也有另外一个东西,也是新手程序员必须要学习的东西,那就是Google的Guava库,这个库的作者,和Effective Java的作者是同一人,都来自于Google,学好了这个库,你的Java的能力,相信会更上一个台阶。Java的优势是什么?那就是各种成熟的轮子,Guava就是其中之一,如果想代码很少BUG,就开始使用它吧。
目录
1、基本工具
2、集合
1、基本工具
1.1 避免NULL
NULL的含义是不确定的,应该避免,尤其是在Map和Set中,返回NULL,并不清楚是不存在,还是有值,但是是NULL,最常用的一个技巧就是:
// 好的代码
List arr = new ArrayList();
// 不好的代码
List arr = null;
Map<Integer,String> map = new HashMap<>();
//处理默认值
map.getOrDefault(123,"默认值");
//没有123这个KEY的时候,才执行put方法,如果map中已经有值了,就不执行put方法
//并且返回旧值
map.putIfAbsent(123,"123");
这样可以避免空指针。Guava的建议是: 使用快速失败操作拒绝null值对开发者更有帮助。
1.2 Guava用Optional表示可能为null的T类型引用:
Optional<Integer> possible = Optional.of(50);
possible.isPresent(); // true
possible.get(); // returns 50
2、集合类
2.1 不可变集合
这个属于防御性编程内容,不可变集合的意思就是集合创建了之后,就不能改变了,类似于:
// SIZE 的值是不可以改变的
final int SIZE = 10;
// final修饰传统的map,起不到效果
final Map<Integer,String> map = new HashMap<>();
// 不可以
map = new HashMap<>();
// 可以
map.put(123,"123")
想要设计不可变集合类的主要原因有:
- 当对象被不可信的库调用时,不可变形式是安全的。
- 线程安全。
- 容易测试。
- 安全,不容易出BUG,以防某些错误的代码修改了集合的内容。
创建的方法(还有1个copyOf方法,这里不打算引入了):
final ImmutableSet<Integer> set = ImmutableSet.<Integer>builder()
.add(12)
.add(30)
.add(12)
.build();
ImmutableSortedSet<Integer> sortedSet = ImmutableSortedSet.of(1, 2, 3, 4, 5);
//转List,取第K小个元素
sortedSet.asList().get(k)
2.2 新集合类型
有一些新的类型可以使用,例如java没有Table类型,下面将一一介绍。
2.2.1 Multiset
这个东西,可以想象成1个ArrayList,没有顺序,但是它是一个Set,不过可以有重复的Key,还记录了重复的Key的出现的数量。比方说你要统计一篇文章中出现的单词的次数:
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
这种写法很笨拙,也容易出错,并且不支持同时收集多种统计信息,如总词数。我们可以做的更好。替代方法:
//模拟文章内容
String [] arr = {"123","123","1234","12345"};
Multiset<String> multiset = HashMultiset.create();
for(String s : arr) {
multiset.add(s);
}
//统计123出现了几次
System.out.println(multiset.count("123"));
//统计set中一共有几个元素
System.out.println(multiset.size());
Multiset接口的方法:
方法 | 描述 |
---|---|
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry>,其中包含的Entry支持getElement()和getCount()方法 |
add(E, int) | 增加给定元素在Multiset中的计数 |
remove(E, int) | 减少给定元素在Multiset中的计数 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
该接口的实现类:
Map | 对应的****Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支持的话) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
2.2.2 Multimap
一般都遇到过以下场景,这种可以使用Multimap替代,需要注意Multimap不是Map。
//传统方法
Map<K, List<V>>
Map<K, Set<V>>
// 替代方法 这里用ArrayListMultimap来举例
ArrayListMultimap<String,String> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("abc","1");
arrayListMultimap.put("abc","12");
arrayListMultimap.put("abc","123");
arrayListMultimap.put("abc","1234");
System.out.println(arrayListMultimap.get("abc"));
// [1, 12, 123, 1234]
//arrayListMultimap.get("abc") 返回的是1个List<String>的值
Multimap提供了多种形式的实现。在大多数要使用Map<K, Collection>的地方,你都可以使用它们:
实现 | 键行为类似 | 值行为类似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashMap |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
2.2.3 BiMap
传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...
替代方法:
BiMap<String, Integer> nameIdBiMap = HashBiMap.create();
nameIdBiMap.put("a",1);
nameIdBiMap.put("b",2);
nameIdBiMap.put("c",3);
//nameIdBiMap.put("a",4);
System.out.println("a的id:" + nameIdBiMap.get("a"));
System.out.println("id为1:" + nameIdBiMap.inverse().get(1));
如果不是唯一的,会出现什么情况呢? 例如2个人重名了。如果我们放开了上面的注释语句,模拟下这种冲突,就会出现ID为1的人被覆盖掉了,这点在使用的时候,需要特别注意。
BiMap的各种实现
键值实现 | 值键实现 | 对应的BiMap实现 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
2.3.4 Table
Table有点类似于2维数组,根据行和列,确认某一个值,没有Table,老方法就是搞个Map<V,Map<K,V>>这样的结构来实现。替代方法:
Table<Integer, Integer, Integer> table = HashBasedTable.create();
table.put(1, 1 ,3);
table.put(1, 2, 4);
table.put(2, 1, 5);
table.put(2, 2, 6);
System.out.println(table.row(1)); //{1=3, 2=4}
System.out.println(table.column(1)); //{1=3, 2=5}
System.out.println(table.get(1, 1)); //3
是不是感觉很方便?
2.3.5 RangeSet
这个东西很有意思,在某些场景,我感觉可能会有用,RangeSet的意思是一些非连续区间,例如:
{[1,10], [11,15)} {[1,10], [11,20)} 在数学上,它是不连续的,也可能是连续的。这个时候,想要描述这段区间的时候,可以使用RangeSet这个类。
注:RangeSet不支持GWT,也不支持JDK5和更早版本;因为,RangeSet需要充分利用JDK6中NavigableMap的特性。
RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10)); // {[1,10]}
rangeSet.add(Range.closedOpen(11, 15));//不相连区间:{[1,10], [11,15)}
rangeSet.add(Range.closedOpen(15, 20)); //相连区间; {[1,10], [11,20)}
rangeSet.add(Range.openClosed(0, 0)); //空区间; {[1,10], [11,20)}
rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}
System.out.println(rangeSet.contains(1)); //true
System.out.println(rangeSet.contains(6)); //false
1、RangeSet的实现支持非常广泛的视图:
- complement():返回RangeSet的补集视图。complement也是RangeSet类型,包含了不相连的、非空的区间。
- subRangeSet(Range):返回RangeSet与给定Range的交集视图。这扩展了传统排序集合中的headSet、subSet和tailSet操作。
- asRanges():用Set<Range>表现RangeSet,这样可以遍历其中的Range。
- asSet(DiscreteDomain)(仅ImmutableRangeSet支持):用ImmutableSortedSet表现RangeSet,以区间中所有元素的形式而不是区间本身的形式查看。(这个操作不支持DiscreteDomain 和RangeSet都没有上边界,或都没有下边界的情况)
2、RangeSet的查询方法 为了方便操作,RangeSet直接提供了若干查询方法,其中最突出的有: - contains©:RangeSet最基本的操作,判断RangeSet中是否有任何区间包含给定元素。
- rangeContaining©:返回包含给定元素的区间;若没有这样的区间,则返回null。
- encloses(Range):简单明了,判断RangeSet中是否有任何区间包括给定区间。
- span():返回包括RangeSet中所有区间的最小区间。
2.3.6 其它
还有一些其它的类,就不多写了,可以自己去看看API文档
- ClassToInstanceMap
- RangeMap 这个和RangeSet有点像,相当于某个区间对应了某个值,例如:
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
具体用到了的时候,可以再研究一下。
全文完。 JAVA程序员必学的Google Guava库(第二篇)
参考文档:
并发编程网 http://ifeve.com/google-guava/
关注我的博客,获取更多Java编程知识: 双King的技术博客