Guava学习记录

Guava简介

The Guava project contains several of Google’s core libraries that we rely on in our Java-based projects: collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, and so forth. Each of these tools really do get used every day by Googlers, in production services.

But trawling through Javadoc isn’t always the most effective way to learn how to make best use of a library. Here, we try to provide readable and pleasant explanations of some of the most popular and most powerful features of Guava.

Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中。


基本工具

避免null

  • 返回的集合类型不要使用null
    • 是不是没有记录??比较含糊
  • set中不要使用null
    • 如果有多个null放到set无法区分,如果是一个变量放入set,你不知道他是不是null
  • map中的key不要使用null
    • 多个null的key被覆盖,如果是变量作为key,你不知道他是不是null

示例:

1
2
3
4
5
6
7
8
Optional<Object> opt = Optional.fromNullable(null);
assertFalse("不应该有", opt.isPresent());
assertEquals("null", opt.or("null"));
opt = Optional.absent();
assertFalse("不应该有", opt.isPresent());
opt = Optional.of("null");
assertEquals("null", opt.get());
Optional.of(null);

对于jdk1.8来说可以使用java.util.Optional来代替

前置条件

参数检查

1
2
3
int i = -1, j = -32;
assertThrows(IllegalArgumentException.class, () -> checkArgument(i >= 0, "参数是%s,预期是一个正数", i));
assertThrows(IllegalArgumentException.class, () -> checkArgument(i < j, "需要i < j,但是 %s >= %s", i, j));

这些可以帮助方法快速失败

通用Object方法

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
assertTrue(Objects.equal("a", "a")); // returns true
assertFalse(Objects.equal(null, "a")); // returns false
assertFalse(Objects.equal("a", null)); // returns false
assertTrue(Objects.equal(null, null)); // returns true

public class TestPojo implements Comparable<TestPojo> {

...

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("desc", desc)
.add("user", user)
.add("price", price)
.add("title", title)
.add("buyCount", buyCount)
.add("viewCount", viewCount)
.add("contentSize", contentSize)
.add("img", img)
.add("category", category)
.add("createdAt", createdAt)
.add("updatedAt", updatedAt)
.add("finishedAt", finishedAt)
.add("isFinished", isFinished)
.add("isDeleted", isDeleted)
.add("isHot", isHot)
.add("isPublish", isPublish)
.add("isShow", isShow)
.add("profile", profile)
.add("lastSectionCount", lastSectionCount)
.add("pv", pv)
.add("timeLimitDiscountFirstDay", timeLimitDiscountFirstDay)
.add("timeLimitDiscount", timeLimitDiscount)
.add("wechatSignal", wechatSignal)
.add("wechatImg", wechatImg)
.add("wechatImgDesc", wechatImgDesc)
.add("url", url)
.add("section", section)
.add("tags", tags)
.toString();
}

@Override
public int compareTo(TestPojo that) {
return ComparisonChain.start()
.compare(this.buyCount, that.buyCount)
.compare(this.contentSize, that.contentSize)
.result();
}
}

对Objects来说如果使用的是jdk1.7,那么可以使用java.util.Object来代替

排序器

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
String[] array = {"1", "2", null, "3", "12", null, "14"};
assertThrows(NullPointerException.class, () -> Arrays.sort(array, Ordering.natural()));
logger.info(Arrays.toString(array)); // [1, 2, null, 3, 12, null, 14]

Arrays.sort(array, Ordering.natural().nullsFirst());
assertArrayEquals(new String[]{null, null, "1", "12", "14", "2", "3"}, array);
logger.info(Arrays.toString(array)); // [null, null, 1, 12, 14, 2, 3]

Arrays.sort(array, Ordering.natural().nullsLast());
assertArrayEquals(new String[]{"1", "12", "14", "2", "3", null, null}, array);
logger.info(Arrays.toString(array)); // [1, 12, 14, 2, 3, null, null]

Arrays.sort(array, Ordering.natural().reverse().nullsLast());
assertArrayEquals(new String[]{"3", "2", "14", "12", "1", null, null}, array);
logger.info(Arrays.toString(array)); // [3, 2, 14, 12, 1, null, null]

Arrays.sort(array, Ordering.natural().nullsLast().reverse());
assertArrayEquals(new String[]{null, null, "3", "2", "14", "12", "1"}, array);
logger.info(Arrays.toString(array)); // [null, null, 3, 2, 14, 12, 1]

Arrays.sort(array, Ordering.natural().onResultOf(new Function<String, Integer>() {
@Nullable
@Override
public Integer apply(@Nullable String input) {
return Integer.valueOf(Optional.fromNullable(input).or("-1"));
}
}));
assertArrayEquals(new String[]{null, null, "1", "2", "3", "12", "14"}, array);
logger.info(Arrays.toString(array)); // [null, null, 1, 2, 3, 12, 14]

异常处理

好像也没有什么可说的

集合

不可变集合

不可变对象有以下优点:

  1. 被第三方库使用时是安全的(不会被修改了还不知道 )
  2. 线程安全:多线程使用时没有条件竞争风险
  3. 无需支持变化,能节约时间与空间。所有的不可变集合比可变集合的实现在内存上效率更好
  4. 可用作常量

构造对象的不可变拷贝是一个好的防御性编程技巧。
JDK也提供了Collections.unmodifiableXXX方法把集合包装为不可变形式,但这些方法有以下问题:

  1. 使用不便
  2. 不安全,不一定是真正的不可变
  3. 低效,数据结构仍是在可变集合基础上实现的

注意:所有的Guava的不可变集合都不支持null值元素,如果发现null值会快速失败

使用方式

1
2
3
4
5
6
7
8
ImmutableSet.copyOf(set);
ImmutableSet.of("a", "b", "c");
ImmutableMap.of("a", 1, "b", 2);
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();

新的集合类型

multisets, multimaps, tables, bidirectional maps

Multiset

继承于collection,这里的set是数学中集合的意思,并不是java不是Set接口。(把Multiset当成List一样用就可以了)
用于计数

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);
}
}

使用Multset

1
2
3
4
5
6
List<String> list = Lists.newArrayList("hello", "guava", "hello", "world");
HashMultiset<String> multiset = HashMultiset.create();
multiset.addAll(list);
assertEquals(2, multiset.count("hello"));
assertEquals(1, multiset.count("guava"));
assertEquals(0, multiset.count("guave"));

Multiset的实现有

Map Corresponding Multiset Supports null elements
HashMap HashMultiset Yes
TreeMap TreeMultiset Yes
LinkedHashMap LinkedHashMultiset Yes
ConcurrentHashMap ConcurrentHashMultiset No
ImmutableMap ImmutableMultiset No

SortedMultiset

它是Multiset的一个变种,能按给定的范围高效的获取子集

Multimap

每个有经验的Java程序员都在某处实现过Map<K, List>或Map<K, Set>,比如,我要做一个归类操作,把复合某种特征的数据放归到一起,就可以使用这个接口,Multimap是把键映射到任意多个值的一般方式。
不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。
很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。

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
List<String> list = Lists.newArrayList("hello", "guava", "hello", "world");
HashMultiset<String> multiset = HashMultiset.create();
multiset.addAll(list);
assertEquals(2, multiset.count("hello"));
assertEquals(1, multiset.count("guava"));
assertEquals(0, multiset.count("guave"));

Multimap<Integer, Integer> listMultimap = MultimapBuilder.treeKeys().arrayListValues().build();
Multimap<Integer, Integer> setMultimap = MultimapBuilder.hashKeys().hashSetValues().build();

listMultimap.put(1, 2);
listMultimap.put(1, 2);
listMultimap.put(1, 3);
listMultimap.put(1, 4);
listMultimap.put(2, 3);
listMultimap.put(3, 3);
listMultimap.put(4, 3);
listMultimap.put(5, 3);

setMultimap.put(1, 2);
setMultimap.put(1, 2);
setMultimap.put(1, 3);
setMultimap.put(1, 4);
setMultimap.put(2, 3);
setMultimap.put(3, 3);
setMultimap.put(4, 3);
setMultimap.put(5, 3);

assertEquals(4, listMultimap.get(1).size());
assertEquals(0, listMultimap.get(6).size());
assertNull(listMultimap.asMap().get(6));

assertEquals(3, setMultimap.get(1).size());
// 对值视图集合进行的修改最终都会反映到底层的Multimap
setMultimap.get(1).remove(2);
assertEquals(2, setMultimap.get(1).size());

assertTrue(listMultimap.containsKey(1));
assertFalse(listMultimap.containsKey(6));

BiMap

传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:

1
2
3
4
5
6
7
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();

nameToId.put("Bob", 42);
idToName.put(42, "Bob");
// what happens if "Bob" or 42 are already present?
// weird bugs can arise if we forget to keep these in sync...

BiMap<K, V>是特殊的Map:

  • 允许你使用inverse()方法去反转BiMap<V, K>的键值
  • 保证值是唯一的,values()返回一个Set

使用BiMap.put(key, value)方法去添加一个已存在的key将会引起IllegalArgumentException异常,如果你想强制替换就要使用BiMap.forcePut(key, value)

Table

通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, Map<LastName, Person>>的实现,这种方式很丑陋,使用上也不友好。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<R, C, V>的Set表现Table<R, C, V>。Cell类似于Map.Entry,但它是用行和列两个键区分的。

给出一个学生-课程-成绩表:

a javase 80
b javaee 90
c javame 100
d guava 60
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Table<String, String, Integer> tables = HashBasedTable.create();
tables.put("a", "javase", 80);
tables.put("b", "javaee", 90);
tables.put("c", "javame", 100);
tables.put("c", "javaee", 80);
tables.put("d", "guava", 70);

// 所有学生
Set<String> students = tables.rowKeySet();
assertEquals(Sets.newHashSet("a", "b", "c", "d"), students);

// 找到对应学生和科目的成绩
assertEquals(100, tables.get("c", "javame").intValue());
assertEquals(80, tables.get("c", "javaee").intValue());

// 所有课程
Set<String> courses = tables.columnKeySet();
assertEquals(Sets.newHashSet("javase", "javaee", "javame", "guava"), courses);

// 所有分数
Collection<Integer> scores = tables.values();
assertArrayEquals(Lists.newArrayList(80, 90, 100, 80, 70).toArray(new Integer[]{}), scores.toArray(new Integer[]{}));

RangeSet

RangeSet描述了一组不相连的、非空的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并,空区间会被忽略。例如:

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

RangeMap

RangeMap描述了”不相交的、非空的区间”到特定值的映射。和RangeSet不同,RangeMap不会合并相邻的映射,即便相邻的区间映射到相同的值。例如:

1
2
3
4
5
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
rangeMap.put(Range.open(3, 6), "bar"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo"}
rangeMap.put(Range.open(10, 20), "foo"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo", (10,20) => "foo"}
rangeMap.remove(Range.closed(5, 11)); //{[1,3] => "foo", (3,5) => "bar", (11,20) => "foo"}

字符工具

  • Joiner
  • Splitter
  • CharMatcher
  • Charsets
  • CaseFormat

CaseFormat在做代码生成器的时候是非常有用的,比如:

1
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "CONSTANT_NAME"));
0%