JAVA程序员必学的Google Guava库(第一篇)


要说Java程序员必须要学习的东西的话,Spring当仁不让,必须是第一个框架,也有另外一个东西,也是新手程序员必须要学习的东西,那就是Google的Guava库,这个库的作者,和Effective Java的作者是同一人,都来自于Google,学好了这个库,你的Java的能力,相信会更上一个台阶。Java的优势是什么?那就是各种成熟的轮子,Guava就是其中之一,如果想代码很少BUG,就开始使用它吧。

目录
1、基本工具
2、集合

1、基本工具

1.1 避免NULL

NULL的含义是不确定的,应该避免,尤其是在Map和Set中,返回NULL,并不清楚是不存在,还是有值,但是是NULL,最常用的一个技巧就是:

1
2
3
4
5
6
7
8
9
10
// 好的代码
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类型引用:

1
2
3
Optional<Integer> possible = Optional.of(50);
possible.isPresent(); // true
possible.get(); // returns 50

2、集合类

2.1 不可变集合

这个属于防御性编程内容,不可变集合的意思就是集合创建了之后,就不能改变了,类似于:

1
2
3
4
5
6
7
8
// SIZE 的值是不可以改变的
final int SIZE = 10;
// final修饰传统的map,起不到效果
final Map<Integer,String> map = new HashMap<>();
// 不可以
map = new HashMap<>();
// 可以
map.put(123,"123")

想要设计不可变集合类的主要原因有:

  • 当对象被不可信的库调用时,不可变形式是安全的。
  • 线程安全。
  • 容易测试。
  • 安全,不容易出BUG,以防某些错误的代码修改了集合的内容。

创建的方法(还有1个copyOf方法,这里不打算引入了):

1
2
3
4
5
6
7
8
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的出现的数量。比方说你要统计一篇文章中出现的单词的次数:

1
2
3
4
5
6
7
8
9
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);
}
}

这种写法很笨拙,也容易出错,并且不支持同时收集多种统计信息,如总词数。我们可以做的更好。替代方法:

1
2
3
4
5
6
7
8
9
10
//模拟文章内容
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。

1
2
3
4
5
6
7
8
9
10
11
12
//传统方法
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中的情况,会变得非常混乱。例如:

1
2
3
4
5
6
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发生...

替代方法:

1
2
3
4
5
6
7
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>>这样的结构来实现。替代方法:

1
2
3
4
5
6
7
8
9
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的特性。

1
2
3
4
5
6
7
8
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(C):RangeSet最基本的操作,判断RangeSet中是否有任何区间包含给定元素。
  • rangeContaining(C):返回包含给定元素的区间;若没有这样的区间,则返回null。
  • encloses(Range):简单明了,判断RangeSet中是否有任何区间包括给定区间。
  • span():返回包括RangeSet中所有区间的最小区间。

    2.3.6 其它

    还有一些其它的类,就不多写了,可以自己去看看API文档
  • ClassToInstanceMap
  • RangeMap 这个和RangeSet有点像,相当于某个区间对应了某个值,例如:
    1
    2
    RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
    rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
    具体用到了的时候,可以再研究一下。
    全文完。 JAVA程序员必学的Google Guava库(第二篇)
    参考文档:
    并发编程网 http://ifeve.com/google-guava/