guava是来自Google的Java核心类库。包含了新的集合类型(例如:复合map、复合set)、不可变集合,以及一些对于并发、I/O、hashing、缓存、原型、字符串等的通用功能。guava被广泛使用在Google的项目中,也被广泛的使用在其他公司里。
null 是一个模棱两可的概念,能引起让人困惑的错误。很多Guava的通用功能在遇到null的时候会拒绝并快速失败。而不是盲目的接受它们。
####(1)使用和避免null:Optional
粗心使用null可能造成令人震惊的各种错误。
另外,null是一个令人不愉快的模糊概念,很少能从返回的null值明确的推测出其代表的含义。例如Map.get(key),返回null,既可以代表在map中的值是null,也可以代表value不再map中。null即可以代表失败,也是代表成功,它能代表几乎任何东西。使用null以外的东西,可以让意思更加明确。
Guava提供了很多特征,不仅帮助你让在需要使用null的时候轻松使用null,也让你避免使用null。
Optional:
创建Optionnal,静态方法
Optional.of(T t)
当遇到null值当时候,就会快速失败;Optional.obsent()
创建某一个缺少某个值当类型当对象;Optional.fromNullable(T t)
将一个可能为非空的引用转换为Optional;查询方法,非静态方法
optional.isPresent()
判断是否存在optional.get()
如果不存在将抛出异常optional.or(T t)
如果不存在返回定义的默认值optional.orNull()
如果不存在返回nulloptional.asSet()
返回一个不可变的集合,有一值,或者一个空集合重点是什么?
除了通过null值一个名称增加了可读性,最大的优势的它的操作是白痴操作。如果你想让你的程序在任何时候都能编译通过,它强迫你去考虑值不存在的情况,因为你不得不积极的展开Optional并去处理这种情况。
它是极其相关的,当返回的值既可能是存在也可能不存在的时候。比起你要实现other.method方法的时候忘记a可能为null值,你更容器忘了other.method(a, b)
的返回值是null。让返回值是Optional类型,就会避免这种情况的发生。
便利的方法
MoreObjects.firstNonNull(T t, T t2)
无论什么时候你想让一个null值被一个默认值代替,使用MoreObjects.firstNonNull
。如果两个值都为空,将报空指针异常。
如果使用Optional,则有一个更好的代替方案,or(T t)
一些用于处理String类型null的方法,是在Strings中提供,具体来说,我们提供了如下的名称:
Strings.nullToEmpty(String)
Strings.isNullOrEmpty(String)
Strings.emptyToNull(String)
我们想要强调这些方法主要是为了面对令人不愉快的APIs交互,它们等同于null 字符串或空字符串。每次你要写代码合并null 字符串和空字符串的时候,Guava团队都在哭泣。(如果将null字符串和空字符串作为不同的东西对待,那是好多,如果将它们作为相同的东西对待就是会有令人不安的代码味道。)
让方法更容易测试先决条件。
Guava提供了很多先决条件的检查工具在Preconditions类中。强烈推荐通过静态导入的方式使用它们。
每一个方法都有三个变种:
没有额外的参数,任何异常将会抛出,不带任何错误信息;
带有一个额外的Object参数,任何异常将会抛出,带有一个object.toString的错误消息;
带有一个额外的String类型的参数,并带有任意数量的额外的Object参数。这种行为有时候更像printf,但是它为了GWT的兼容性和高效性,仅仅允许%s
标识符。
注意:checkNotNull
、checkArgument
、checkState
又很多的重载方法,获取原始类型和对象参数,而不是一个可变数组。在绝大多数情况下,这种调用可以避免上述调用时进行原始类型的装箱和可变数组的分配。
第三种变体的示例代码:
import static com.google.common.base.Preconditions.*;
public class UsingAndAvoidNull {
public void preconditionTest(int i, int j) {
checkArgument(i>0, "Argument was %s but expected nonnegative", i);
checkArgument(i>j, "Expected i > j, but %s >= %s", i, j);
}
}
方法签名(不包含额外的参数) | 描述 | 在失败的时候抛出异常 |
---|---|---|
checkArgument(boolean) | 检查boolean是true,用于验证方法的参数。 | IllegalArgumentException |
checkNotNull(T) | 检查值不为null,如果在行内使用checkNotNull(value) ,将直接返回value值。 | NullPointerException |
checkState(boolean) | 检查对象的状态,不依赖方法的参数。例如,一个Iterator可以使用这个方法检查next有值能够调用,在调用remove之前 | IllegalStateException |
checkElementIndex(int index, int size) | 检查索引是有效的,对于list、String、或者指定大小的数组。元素的索引从0(包含)到size(不包含)。不能直接传递list、string、或数组。仅传递它的大小,返回索引值。 | IndexOutOfBoundsException |
checkPositionIndex(int index, int size) | 检查是有一个位置,对于list、string、或指定大小的数组。位置的值从0(包含)到size(不包含)。不能直接传递list、string、数组,只需要传递它们的大小。最后返回index。 | IndexOutOfBoundsException |
checkPositionIndexes(int start,int end, int index) | 检查index是否在[start, end]范围内,其中end值要大于等于start。 | IndexOutOfBoundsException |
可变参数“printf-style”风格的异常消息。这个优势是我们推荐使用checkNotNull,而不是Objects.requireNotNull的原因。
建议将先决条件分成不同的行,它能帮助你在调试期间定位出哪个先决条件失败了。此外,你应该提供有用的错误信息。
一个有条件的失败或者运行时检查,是任何一段代码当且仅当一个boolean条件成立的时候抛出异常。在好的软件设计中这种代码很常见。
一个非常容易的处理,可以用相同的方法:if(!condition) {throw new RunTimeException();}
。但是如果你花一点时间去考虑你正在执行的检查性质,然后用更合适的方式处理它,就能让你的代码更容易理解,错误也更容易诊断。
这里有一些重要的运行时检查:
先觉条件的检查。
确认调用的公共方法是服从方法规范要求的。例如,一个sqrt函数,仅接受非负参数。
IllegalArgumentException、IllegalStateException
传统的断言检查。
仅应该在以某种方式类本身被破坏的时候失败。
assert、AssertionError
确认检查。
当你对一个要使用的API严重缺乏信心的时候,要对它进行检查。
VerifyException
测试断言。仅仅在测试类中被发现,确保测试代码是符合规范要求的。
注意,这类断言的代码和生产代码的断言几乎没有共同之处。
assertThat、assertEquals、AssertionError
不可能条件检查。它是不可能失败,除非我们代码被修改了,或者严重违反了底层平台的行为。
AssertionError
一个异常的结果。方法没有提供预期的结果
Guava有强大的Comparator类。
从本质上讲,Ordering 无非是一个Comparator实现。Ordering 依赖于Collector的方法(例如,Collections.max)。此外Ordering通过链式方法来增强和调整现有的比较器。
Ordering.natural()
对可比较类型进行自然排序Ordering.usingToString()
根据toString()返回的字符串表示形式的字典顺序比较对象。Ordering.from(Comparator)
Ordering byLenOrdering = new Ordering<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
通过包装Ordering获取派生的顺序。一些常用的变体包含如下:
reverse()
逆序
nullsFirst()
null值放到最前面,非空值按照Ordering定义的排序进行排序;
nullsLast()
null值放在最后,非空值按照Ordering定义的排序进行排序;
compound()
组合两个比较器,当第一个比较器无法比较大小时,采用第二个比较器进行比较;
// 按照字符串的长度进行比较,如果长度相同按照字母表顺序进行比较
Collections.sort(names, byLenOrdering.compound(Ordering.usingToString()));
lexicographical()
对Iterator列表进行排序,类比Ordering.usingToString()
,这里每个可对比的单元是Iterator中的一个元素。
List<List<String>> groups = new ArrayList<>();
groups.add(new ArrayList<>(Arrays.asList("aa", "ff")));
groups.add(new ArrayList<>(Arrays.asList("aa", "bb", "cc")));
System.out.println(groups); // [[aa, ff], [aa, bb, cc]]
Collections.sort(groups, Ordering.usingToString().lexicographical());
System.out.println(groups); // [[aa, bb, cc], [aa, ff]]
onResultOf(Function)
把比较器的元素使用Function函数转化成一个值result,再对这个值应用Ordering的比较方法。result的排序顺序就是最后的排序顺序。
例如,你要对Foo列表进行排序:
class Foo {
String sortedBy;
int notSortedBy;
}
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);
比如上面的调用器链,先查找Foo的sortedBy字段,然后将所有null元素移动到最前面,再对剩下的顺序进行自然排序。
之所以,出现这个从右往左的调用方式,是因为每个调用链都是将前面一个Ordering包装成一个新的Ordering。
注意:compound()
是调用链里的例外,它是从左往右调用。
调用链太长,不容易理解。推荐把调用链控制在三个以内。
####(6)应用
greatestOf(Iterable iterable, int k)
对元素按照从大到小排序,并返回前k个元素
List<Integer> result = Ordering.natural().greatestOf(new ArrayList<>(Arrays.asList(3, 5, 1)), 2);
leastOf(Iterable iterable, int k)
对元素按照从小到大排序,并返回前k个元素
isOrdered(Iterable iterable)
测试是否为按照递增的顺序排序
boolean ordered = Ordering.natural().isOrdered(result);
isStrictlyOrdered(Iterable iterable)
测试是否按照递减的顺序排序
sortedCopy(Iterable iterable)
返回一个新的已经排序的列表,原来的列表顺序不会变
List<Integer> nums = new ArrayList<>(Arrays.asList(4, 1, 3));
List<Integer> resultCopy = Ordering.natural().sortedCopy(nums);
System.out.println(resultCopy); // [1, 3, 4]
System.out.println(nums); // [4, 1, 3]
immutableSortedCopy(Iterable iterable)
类似sortedCopy,只不过返回一个不可变的副本
min(E e1, E e2)
返回最小的值,如果两个值相等,返回第一个元素。
Integer min = Ordering.natural().min(5, 1);
max(E e1, E e2)
返回最大值,如果两个值相等,返回第一个元素。
min(E, E, E...)
对比多个值,返回最小值,如果有多个最小值,返回第一个
max(E, E, E...)
对比多个值,返回最大值,如果有多个最大值,返回第一个
min(Iterable)
、max(Iterable)
返回列表中的最小值或最大值,如果列表为空,抛出异常。
简化了实现对象的方法,例如hashcode()、toString()。
Objects.equal()
可以避免在比较时由于null值带来麻烦。在JDK7中也引入的Objects,里面的equals() 方法是等价的。
Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true
Objects.hashCode(filed1, filed2,...,filedn)
对一系列顺序的字段进行hash。
在JDK7中引入的Objects中,里面的hash()
方法和上面是等价的。
toString()
:MoreObjects.toStringHelper()
toString() 方法对于调试代码没有用,但很难写,可以使用MoreObjects.toStringHelper()
方法来创建一个有用的toString方法。
// MyObject{x=1, y=cc}
MoreObjects.toStringHelper("MyObject")
.add("x", 1)
.add("y", "cc")
.toString();
ComparisonChain
在实现Comparator 或 Comparable的时候非常痛苦,可以使用ComparisonChain来进化工作:
@Override
public int compareTo(Product o) {
return ComparisonChain.start()
.compare(this.name, o.name)
.compare(this.num, o.name)
.compare(this.typeEnum, o.typeEnum, Ordering.natural())
.result();
}
ComparisonChain 采用懒工作模式:它执行比较,直到找到一个非零的结果,然后忽略进一步的输入。
Guava中的Throwables能够简化异常的处理。
Throwables.propagateIfPossible(Throwable, Class<X extends Throwable>) throws X;
只有当它是RuntimeException、Error、或X的时候,它才会按照原样抛出异常Throwables.throwIfInstanceOf(Throwable, Class<x extends Exeption>) throws X;
当且仅当它是X类型的异常时,按照原样传播可抛出异常Throwables.throwIfUnchecked(Throwable)
仅当它是RuntimeExeption或Error的时候,才按照原样抛出异常Throwables.getRootCause(Throwable)
获取根部异常Throwables.getCausalChain(Throwable)
获取异常链Throwables.getStackTraceAsString(Throwable)
将异常转化成字符串 Throwable throwable = new IllegalArgumentException(new ArrayStoreException());
Throwable rootCause = Throwables.getRootCause(throwable); // java.lang.ArrayStoreException
System.out.println(rootCause);
// [java.lang.IllegalArgumentException: java.lang.ArrayStoreException, java.lang.ArrayStoreException]
System.out.println(Throwables.getCausalChain(throwable));
/*
java.lang.IllegalArgumentException: java.lang.ArrayStoreException
at com.hef.guava.baseutilities.ThrowDemo.main(ThrowDemo.java:16)
Caused by: java.lang.ArrayStoreException
... 1 more
*/
System.out.println(Throwables.getStackTraceAsString(throwable));
Guava扩展了Java的集合生态系统,这是Guava中非常成熟和流程的一部分。
为了集合对象创建不可变的副本是一个好的防御性的编程策略。Guava为每一个Collection类型(包括Guava自己的Collection类)都提供了简单易用的不可变集合版本。
Collections.unmodifiableXXX
产生不可变集合存在的问题当你不期望集合被修改,或者期望集合一致不变,一个好的策略是采用防御性拷贝,让其变成不可变集合。
重要:在Guava的不可变集合中,不允许有null值。如果你想使用带有null的不可变集合,可以使用Collections.unmodifiableXXX
。
通过copyOf
方法,例如:ImmutableSet.copyOf(set)
使用of
方法,例如:ImmutableSet.of
ImmutableMap.of("name", "world", "where", "beijing");
ImmutableSet.of("aa", "bb")
使用builder方法:
ImmutableSet<String> result = ImmutableSet.<String>builder().add("aaa").add("ccc").build();
除了有顺序的集合,顺序是根据存入数据结构的先后顺序。
copyOf
非常的聪明:能在安全的情况下,避免复制数据结构。ImmutableXXX.copyOf
避免线性时间的复制:
ImmutableSet.copyOf(ImutableList)
, 不能再固定时间完成。ImmutableList<String> hugeList
,使用ImmutableList.copyOf(hugeList.subList(0, 10))
,将会执行一个明确的拷贝。从而避免持有对hugeList不必要的引用;ImmutableSet.copyOf(myImmutableSortedSet)
将执行明确的拷贝,因为hashCode和equals在ImmutableSet和ImmutableSorted中的语义是不一样的。这将能为防御性编码风格带来最小的性能负担。
asList
便可变集合通过asList
方法就能得到一个ImmutableList
。
JDK中的接口 | JDK 还是 Guava | Guava中的不可变集合版本 |
---|---|---|
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | Immutable |
MultiSet | Guava | ImmutableMultiSet |
SortedMultiSet | Guava | ImmutableSortedMultiSet |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
Guava中定义的很多新的集合,它们被广泛的使用。这些新的集合遵循JDK的集合接口约定。
一个场景:统计单词在文章中出现的次数。传统的做法如下:
Map<String, Integer> result = new HashMap<>();
for (String word : words) {
if (result.containsKey(word)) {
result.put(word,result.get(word)+1);
}else {
result.put(word, 1);
}
}
这样做容易出错,还不利于统计出一些有用的信息,例如,单词的总数。
使用Multiset就可以很简单的这样写:
Multiset<String> set = HashMultiset.create();
set.addAll(words);
add(E)
方法,添加一个指定的元素;iterator()
方法,遍历每个元素每次出现(元素每出现一次就会被遍历一次);size()
方法,获取所有元素出现的总次数;addAll(Collection)
添加多个元素添加进去;Map<String, Integer>
一样:
count(Object)
返回某个元素出现的总次数。对于HashMultiset,count的时间复杂度为O(1);对于TreeMultiset,count的时间复杂度是O(logn);entrySet()
返回Set<Multiset.entry<E>>
,工作机制类似于Map的entrySet;elementSet()
返回不同元素的集合列表,类似于Map的keySet();setCount(E, int)
设置某个元素,出现的次数remove(E, int)
将某个元素减少指定次数;注意,Multiset并不是一个Map<E,Integer>
,尽管它是Multiset的一部分。Multiset真的是Collection类型。其它值得注意的有:
multiset.size()
返回的是集合的大小。每次执行add操作,size都会加1;multiset.iterator()
迭代的是每个元素的所有次数。也就是迭代的次数等于size;multiset.count(E)
对于不在Multiset中的元素,总是返回0;map | Multiset | 是否支持null |
---|---|---|
HashMap | HashMultiset | YES |
TreeMap | TreeMultiset | YES |
LinkedHashMap | LinkedHashMultiset | YES |
ConcurrentHashMap | ConcurrentHashMultiset | NO |
ImmutableMap | ImmutableMultiset | NO |
SortedMultiset 接口是Multiset的变体。它能有效的获取在特定范围的子集。例如:
SortedMultiset<Comparable<Integer>> result = set.subMultiset(3, BoundType.OPEN, 9, BoundType.CLOSED);
TreeMultiset 实现了 SortedMultiset接口。
Guava的Multimap很容见处理key到多个value的映射。
有两种思路理解Multimap概念:
一帮情况下,我们最好把Multimap接口作为第一视图,也可以用另一种视角,通过调用asMap()
方法得到Map<K, Collection<V>>
。
非常重要的是,在Multimap中没有key会对应一个空集合。也就是说,一个key至少对应一个值,或者key不存在Multimap中。
很少直接使用Multimap接口,经常使用ListMultimap或SetMultimap,它们的key分别对应List和Set。
创建Multimap最简单直接的方式是通过MultimapBuilder来创建:
ListMultimap<String, Integer> listMultimap =
MultimapBuilder.hashKeys().arrayListValues().build();
也可以直接在实现类上使用create() 方法:
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
Multimap.get(key)
通过这个方法返回指定key关联的values;修改Multimap底层的内容:
ListMultimap<String, Integer> listMultimap =
MultimapBuilder.hashKeys().arrayListValues().build();
List<Integer> nums = listMultimap.get("name");
System.out.println(nums); // []
nums.clear();
nums.add(23);
nums.add(21);
System.out.println(listMultimap.get("name")); // [23, 21]
其它修改Multimap底层的方法:
put(K, V)
添加一个K 及相关的V;等价于multimap.get(K).add(V)
putAll(K, Iterable<V>)
添加K到每一个V的联系;等价于Iterables.addAll(multimap.get(K), Iterable<V>)
remove(K, V)
移除一个K到V的关系,如果Multimap改变了,返回true;等价于multimap.get(K).remove(V)
removeAll(K)
移除所有与K有关的关系,并返回合适的集合类型;等价于multimap.get(K).clear()
replaceValues(K, Iterable<V>)
清空现有的key相关的关系,并建立新的关系,返回原来与Key相关联的value集合;等价于multimap.get(K).clear(); Iterables.addAll(multimap.get(K), Iterable<V>)
asMap
将Multimap作为Map<K, Collection<V>>
。返回的Map支持remove,也支持改变写入的集合。但是map不支持put或putAll。entries
返回Collection<Map.Entry<K, V>>
所有Multimap的关系keySet
返回所有的key集合;keys
返回Multiset。可以移除元素,但是不能添加;values
返回所有的values。类似于Iterables.concat(listMultimap.asMap().values())
一个Multimap<K, V>
并不是一个Map<K, Collection<V>>
,尽管这样的Map能够用Multimap来实现。值得注意的不同如下:
Multimap.get(K)
总是返回一个非null,可能为空的集合。返回的集合允许你添加与key有关系的value;Map<K, Collection<V>>
视图。也可以使用Multimaps
的静态方法asMap
;Multimap.containsKey(K)
只有当存在K的关系,才会返回true。Multimap.entries()
返回所有key与value的关系对;如果要获取key-collection,使用Multimap.asMap().entrySet()
Multimap.size()
将返回所有Key的关系对数量,而不是不同key对个数。使用Multimap.asMap().size()
可以获取不同key对个数;Multimap提供了很多实现,首先创建Multimap实现的方式是通过MultimapBuilder。
实现 | Key的行为像 | value的行为像 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
这些实现中,除了不可变集合,其它的key和value都可以为空。
可以自定义自己的实现:Multimaps.newMultimap(Map, Supplier<Collection>)
把values转成keys的传统方式是维护两个独立的map,并同步更新它们。但是这是易于出错的。当map中已经存在value的时候会造成混乱,例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
// 如果 Bob 或 42 已经存在了,会怎么样?
// 诡异的bug将会出现,如果我们忘了同步更新
一个Bimap<K, V>
是一个Map<K, V>
:
Bimap<K, V>
反转,通过调用inverse()
方法;values()
作为一个set集合;如果你试图通过Bimap.put(key, value)
映射一个key到一个已经存在的value值上,会抛出异常:
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("Bob", 42);
biMap.put("Job", 42);
/*
Exception in thread "main" java.lang.IllegalArgumentException: value already present: 42
at com.google.common.collect.HashBiMap.put(HashBiMap.java:310)
at com.google.common.collect.HashBiMap.put(HashBiMap.java:290)
at com.hef.guava.collection.GuavaCollectionDemo.biMapTest(GuavaCollectionDemo.java:32)
at com.hef.guava.collection.GuavaCollectionDemo.main(GuavaCollectionDemo.java:19)
*/
如果你想删除预先已经存在的值,可以通过bimap.forcePut(key, value)
:
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("Bob", 42);
biMap.forcePut("Job", 42);
System.out.println(biMap); // {Job=42}
key-value map | 双向map |
---|---|
HashMap | HashBimap |
ImmutableMap | ImmutableBimap |
EnumMap | EnumBimap |
EnumMap | EnumHashBimap |
类似一个二维表,由“行、列、值”三元素组成。这里的行和列,可以是任意类型。
通常,当你想要一次索引多个keys时,你可能会想起使用Map<FirstName,Map<LastName,Person>>
。Guava提供了一种新的集合类型Table
,它支持使用任何的行列,以展示不同的视图:
rowMap()
对于Table<R, C, V>
,将返回Map<R, Map<C, V>>
。rowKeySet()
将返回Set<R>
;row(r)
返回非null的Map<C, V>
,向这个返回值的Map中写数据,值将写入底层的Table中;columnMap()
、columnKeySet()
、column(c)
cellSet()
将返回Set<Table.Cell<R, C, V>>
, 类似于Map.Entry;HashBasedTable
, 它本质上是基于HashMap<R, HashMap<C, V>>
;TreeBasedTable
, 它本质上是基于TreeMap<R, TreeMap<C, V>>
;ImmutableTable
ArrayTable
需要在创建的时候指定行和列。能够提升速度和内存使用率;有些使用想将不同的类型作为key,该类型的值作为value。就可以使用ClassToInstanceMap。从技术上说ClassToInstanceMap实现了Map<Class<? extends B>, B>
,换句话说,它是一个B类型到B类型值的映射。
ClassToInstanceMap有一个带有一个参数的构造,通过这个参数可以确定ClassToInstanceMap中B的上界类型。
除了扩展Map接口,ClassToInstanceMap还提供了两个方法:
T getInstance(Class<T>)
获取某个类型的值;putInstance(Class<T>, T)
设置某个类型的值;Guava提供了两个有用的ClassToInstanceMap实现:
MutableClassToInstanceMap
ImmutableClassToInstanceMap
RangeSet描述了一个没有关联的非空的范围。当添加一个范围到可变集合RangeSet到时候,任何有联系的范围将合并在一块,空的范围将被忽略。例如:
TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10));
System.out.println(rangeSet); // [[1..10]]
rangeSet.add(Range.closedOpen(11, 15));
System.out.println(rangeSet); // [[1..10], [11..15)]
rangeSet.add(Range.closedOpen(15, 20));
System.out.println(rangeSet);// [[1..10], [11..20)]
rangeSet.add(Range.openClosed(0, 0));
System.out.println(rangeSet); // [[1..10], [11..20)]
rangeSet.remove(Range.open(5, 10));
System.out.println(rangeSet); // [[1..5], [10..10], [11..20)]
complement()
展示rangeSet的补集
TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.openClosed(2, 5));
RangeSet<Integer> complement = rangeSet.complement();
System.out.println(rangeSet); // [(2..5]]
System.out.println(complement); // [(-∞..2], (5..+∞)]
subRangeSet(Range)
获取特定范围内的子集
RangeSet<Integer> subRangeSet = rangeSet.subRangeSet(Range.closed(4, 5));
asRanges()
返回一个Set<Range>
,用于迭代所有子集
asSet()
仅用于不可变集合。
contains(C)
查询一个元素是否在RangSet的范围中rangeContaining(C)
返回包含某一个元素的范围encloses(Range)
测试一个范围是否在RangeSet中span()
返回包含RangeSet所有范围的最小范围范围到值的映射。RangeMap 不会合并两个相同的范围,即便两个相邻的范围映射到同一个值。
TreeRangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo");
System.out.println(rangeMap); // [[1..10]=foo]
rangeMap.put(Range.open(3, 6), "bar");
System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo]
rangeMap.put(Range.open(10, 20), "foo");
System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo, (10..20)=foo]
rangeMap.remove(Range.closed(5, 11));
System.out.println(rangeMap); // [[1..3]=foo, (3..5)=bar, (11..20)=foo]
asMapOfRanges
返回Map<Range, V>
用于遍历RangeMapsubRangeMap(Range)
返回子的RangeMapGuava提供了很多工具。
接口 | JDK还是Guava | 对应Guava的工具类 |
---|---|---|
Collection | JDK | Collections2 |
List | JDK | Lists |
Set | JDK | Sets |
SortedSet | JDK | Sets |
Map | JDK | Maps |
SortedMap | JDK | Maps |
Queue | JDK | Queues |
Multiset | Guava | Multisets |
Multimap | Guava | Multimaps |
BiMap | Guava | Maps |
Table | Guava | Tables |
在JDK7之前,构建一个泛型集合需要写令人不愉快的代码:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
JDK7 的菱形运算符减少了这方面的麻烦:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
Guava 提供了静态方法,通过泛形推断右侧的类型:
ArrayList<TypeThatsTooLongForItsOwnGood> list02 = Lists.newArrayList();
在创建集合的时候,可以非常方便的初始化集合元素:
ArrayList<String> list03 = Lists.newArrayList("aa", "bb", "cc");
通过静态工厂方法,初始化集合的大小:
// 明确值
ArrayList<String> list04 = Lists.newArrayListWithCapacity(10);
// 估计值
ArrayList<String> list05 = Lists.newArrayListWithExpectedSize(10);
HashSet<Integer> set = Sets.newHashSetWithExpectedSize(10);
只要有可能,Guava更喜欢提供接受Iterable而不是Collection的程序。在Google里,一个集合实际上并没有存在主存中的现象是不稀奇的,这个集合来自数据库或来自其他的数据中心。因为没有抓取到所有元素,所以并不支持size() 操作。
因此,你希望看到的所有集合操作都能在Iterables中找到。Iterables的大多方法都支持接受一个Iterator的版本。
在Iterables类中它的绝大多数方法都是懒惰的:只有当绝对需要迭代的时候,迭代操作发生。返回Iterables的方法,返回的是一个懒惰的计算视图,而不是构建一个明确的集合。
concat(Iterable...)
连接多个Iterables,返回一个惰性视图;
frequency
返回元素在集合中出现的频次
对比Collections.frequency(Collection, o)
。Multiset
partition(Iterable, int)
返回一个不可修改的Iterable,里面是分成指定大小的块。
paddedPartition(Iterable, int)
分成指定大小的块,如果某一块的元素个数不足,用null补充。
getFirst(Iterable, T default)
返回第一个元素,没有Iterable为空,返回默认值;
getLast(Iterable)
返回最后一个元素,如果为空抛出异常
getLast(Iterable, T default)
返回最后一个元素,如果没有返回默认值
elementsEqual(Iterable, Iterable)
如果两个Iterable有相同的元素,则返回true
unmodifiableIterable(Iterable)
返回一个不可修改的视图
limit(Iterable, int)
返回集合的前几个元素
getOnlyElement(Iterable)
返回Iterable中仅有的一个元素。如果Iterable中有多个元素,将会报错
getOnlyElement(Iterable, T default)
返回Iterable中仅有的一个元素,如果Iterable为空,返回默认值
一些方法在Iterable中不支持,但在Collection中支持。当向Iterables中下面的这些方法传递Collection的时候,实际的操作会调用Collection接口的方法:
addAll(Collection, Iterable)
contains(Iterable, o)
removeAll(Iterable, Collection)
retainAll(Iterable removeFrom, Collection retain)
size(Iterable)
toArray(Iterable, Class<T>)
isEmpty(Iterable)
get(Iterable, int)
类似List.get(i)
toString(Iterable)
partition(List, int)
将list切分成若干个大小为size的小块。reverse(List)
反转list中的元素静态工厂方法:
Lists.newLinkedList()
Lists.newArrayList()
查找最小值、最大值。一个看似很简单,却因为考虑最小的分配、装箱、API的灵活性而变得复杂。
要对比的元素 | 刚好两个元素 | 超过两个元素 |
---|---|---|
不需要包装箱的基本数据类型 | Math(a, b) | Ints.max(a, b, c); |
Comparable类型的实例 | Comparators.max(a, b) | Collections.max(Arrays.asList(a, b, c)) |
使用自定义的Comparator | Comparators.max(a, b, comparator) | Collections.max(Arrays.asList(a, b, c), comparator) |
Comparators.max("aa", "bb", Comparator.comparingInt(String::length));
Collections.max(Arrays.asList("aa", "bb"),Comparator.comparingInt(String::length));
union(set, set)
获取两个set的并集intersection(set, set)
获取两个set的交集difference(Set, Set)
返回前一个集合与后一个集合不同的元素symmetricDifference(Set, Set)
差集,除去两个集合共有的元素 HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");
HashSet<String> set02 = Sets.newHashSet("ee", "bb", "ff");
Sets.SetView<String> result = Sets.union(set01, set02);
System.out.println(result);
Sets.SetView<String> intersection = Sets.intersection(set01, set02);
System.out.println(intersection);
Sets.SetView<String> difference = Sets.difference(set01, set02);
System.out.println(difference);
Sets.SetView<String> difference1 = Sets.symmetricDifference(set01, set02);
System.out.println(difference1);
这些操作返回的是SetView
:
copyInto(Set)
将SetView中的元素拷贝到一个Set中;immutableCopy()
拷贝成一个不可变集合cartesianProduct(Set...)
多个集合进行组合,形成笛卡尔积Sets.powerSet(Set)
获取元素的子集HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");
Set<Set<String>> sets = Sets.powerSet(set01);
// {{}, {"aa"}, {"bb"}, {"cc"}, {"aa", "bb"}, {"aa", "cc"}, {"bb", "cc"}, {"aa", "bb", "cc"}}
newHashSet()
newTreeSet()
newLinkedHashSet()
Maps中有很多酷的方法,值得分别解释:
Maps.uniqueIndex(Iterable, Function)
解决有一堆对象(Iterable),这一些对象都有唯一的属性。想要根据属性查到对象。
// 假设这些字符串的长度是唯一的,想要根据长度查找相应的字符串
// 一旦这个唯一的属性不唯一就会报错
ArrayList<String> list = Lists.newArrayList("a", "bb", "ccc", "dddd", "eeeee", "ffffff");
ImmutableMap<Integer, String> lenMap = Maps.uniqueIndex(list, String::length);
String item = lenMap.get(5);
System.out.println(item);
Maps.difference(Map, Map)
根据MapDifference
的方法:
entriesInCommon()
key和value都匹配的项目entriesDiffering()
key相同,value不相同的项。返回值是Map<K, MapDifference.ValueDifference<V>>
通过MapDifference.ValueDifference能看到左右两边的值;entriesOnlyOnLeft()
返回key只在左边出现的元素对;entriesOnlyOnRight()
返回key只在右边出现的元素对; Map<String, Integer> map01 = ImmutableMap.of("aa", 1, "bb", 2, "cc", 3);
Map<String, Integer> map02 = ImmutableMap.of("aa", 2, "d", 2, "cc", 3);
MapDifference<String, Integer> difference = Maps.difference(map01, map02);
Map<String, Integer> entriesInCommon = difference.entriesInCommon();
System.out.println(entriesInCommon); // {cc=3}
Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = difference.entriesDiffering();
System.out.println(entriesDiffering); // {aa=(1, 2)}
Map<String, Integer> entriesOnlyOnLeft = difference.entriesOnlyOnLeft();
System.out.println(entriesOnlyOnLeft); // {bb=2}
Map<String, Integer> entriesOnlyOnRight = difference.entriesOnlyOnRight();
System.out.println(entriesOnlyOnRight); // {d=2}
因为BiMap也是一个Map,所以它的工具方法也在Maps中:
Maps.synchronizedBiMap(BiMap)
等价的方法Collections.synchronizedMap(Map);
Maps.unmodifiableBiMap(BiMap)
等价的方法Collections.unmodifiableMap(Map);
Maps.newHashMap()
Maps.newLinkedHashMap()
Maps.newTreeMap()
Maps.newEnumMap()
Maps.newConcurrentMap()
Maps.newIdentityHashMap()
标准的Collection操作,例如containsAll,忽略了元素在Multiset的个数,仅关心元素是否在Multiset中。Multisets提供了很多方法考虑多重性操作:
containsOccurrences(superMultiset, subMultiset)
如果subMultiset中的所有元素都有subMultiset.count(o)<=superMultiset(o)
,则返回true。removeOccurrences(superMultiset, subMultiset)
根据subMultiset中数量移除superMultise的元素。retainOccurrences(superMultiset, subMultiset)
根据subMultiset中的元素个数,保留superMultiset中的元素,其余的都移除intersection(superMultiset, subMultiset)
根据元素个数获取交集 HashMultiset<String> multiset = HashMultiset.create();
HashMultiset<String> multiset02 = HashMultiset.create();
multiset.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "cc"));
multiset02.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "dd"));
Multiset<String> intersection = Multisets.intersection(multiset, multiset02);
System.out.println(intersection); // [aa x 2, bb x 2, cc]
Multisets中的其他工具方法:
copyHighestCountFirst(multiset)
返回一个不可变的Multiset,并且迭代的时候元素频次最高的在前面;unmodifiableMultiset(multiset)
返回一个不可修改的MultisetunmodifiableSortedMultiset(SortedMultiset)
返回一个有序的不可修改的MultisetMultimap提供了很多常用的方法,下面分开解释:
和Maps.uniqueIndex
功能类似。Multimaps.index(Iterable, Function)
是把一批对象,属性相同的分成一组。可以根据属性找到一批对象。
ImmutableListMultimap<Integer, String> multimap1 = Multimaps.index(Lists.newArrayList("aa", "bb", "cc", "dd", "world"), String::length);
System.out.println(multimap1);// {2=[aa, bb, cc, dd], 5=[world]}
因为多个key可以对应一个value,多个value可以对应一个key
ListMultimap<String, Integer> multimap = MultimapBuilder.hashKeys().arrayListValues().build();
multimap.put("name", 23);
multimap.put("name", 21);
multimap.put("value", 21);
System.out.println(multimap); // {name=[23, 21], value=[21]}
ListMultimap<Integer, String> multimap2 = Multimaps.invertFrom(multimap, MultimapBuilder.hashKeys().arrayListValues().build());
System.out.println(multimap2); // {21=[name, value], 23=[name]}
对于ImmutableMultimap,可以直接调用invert() 方法。
Map<String, Integer> map = ImmutableMap.of("aa", 1, "bb", 1, "cc", 3);
System.out.println(map); // {aa=1, bb=1, cc=3}
SetMultimap<String, Integer> setMultimap = Multimaps.forMap(map);
System.out.println(setMultimap); // {aa=[1], bb=[1], cc=[3]}
HashMultimap<Integer, String> multimap3 = Multimaps.invertFrom(setMultimap, HashMultimap.<Integer, String>create());
System.out.println(multimap3);// {1=[aa, bb], 3=[cc]}
Multimap类型 | 不可修改类型 | 同步类型 | 自定义类型 |
---|---|---|---|
Multimap | unmodifiableMultimap | synchronizedMultimap | newMultimap |
ListMultimap | unmodifiableListMultimap | synchronizedListMultimap | newListMultimap |
SetMultimap | unmodifiableSetMultimap | synchronizedSetMultimap | newSetMultimap |
SortedSetMultimap | unmodifiableSortedSetMultimap | synchronizedSortedSetMultimap | newSortedSetMultimap |
自定义类型,允许指定一个实现,能够在返回的Multimap中使用。
自定义类型需要提供一个Supplier方法,用于创建新的集合。
Multimap<String, Integer> multimap4 =
Multimaps.newMultimap(new HashMap<String, Collection<Integer>>(), Lists::newArrayList);
对比newXXXMultimap(Map, Supplier)
。customTable 可以指定row、col的数据结构。
Table<String, String, Integer> table =
Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(),
LinkedHashMap::new);
Table<String, String, Integer> table =
Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(),
LinkedHashMap::new);
table.put("name", "value", 2);
System.out.println(table);
Table<String, String, Integer> transpose =
Tables.transpose(table);
System.out.println(transpose);
unmodifiableTable
转成不可修改的Table
Table<String, String, Integer> unmodifiableTable = Tables.unmodifiableTable(table);
unmodifiableRowSortedTable
转成row排序的Table
RowSortedTable<String, String, Integer> treeBasedTable = TreeBasedTable.create();
treeBasedTable.put("name1", "name2", 23);
RowSortedTable<String, String, Integer> unmodifiableRowSortedTable = Tables.unmodifiableRowSortedTable(treeBasedTable);
有些时候你需要写自己的集合扩展。或者当元素添加到集合汇总时,你想添加特定的行为;或者你想编写一个数据库支持的Iterable。Guava提供了很多工具让你做这些事情更容易。
对于各种集合接口。Guava提供了Forwarding抽象类简化了装饰器模式的使用。
Forwarding类定义了一个抽象方法delegate()
,你应该复写它并返回被装饰的对象。其他的方法就简单的委托给装饰者。例如,ForwardingList.get(i)
是简单的实现了delegate().get(i)
通过Iterators.peekingIterator(Iteraotr) 得到PeekingIterator。其中的peek() 能够得到next() 元素
List<String> result = Lists.newArrayList("a", "b", "c");
PeekingIterator<String> peekingIterator = Iterators.peekingIterator(result.iterator());
// 能够打印5次 a
for (int i = 0; i < 5; i++) {
System.out.println(peekingIterator.peek());
}
通过AbstractIterator
可以很方便的实现自己的Iterator:
private Iterator<String> skipNullIterator(final Iterator<String> in){
return new AbstractIterator<String>() {
@CheckForNull
@Override
protected String computeNext() {
while (in.hasNext()) {
String s = in.next();
if (s!=null) {
return s;
}
}
return endOfData();
}
};
}
private Iterator<Integer> sequentialIterator() {
return new AbstractSequentialIterator<Integer>(2) {
@CheckForNull
@Override
protected Integer computeNext(Integer previous) {
return previous>100?null:previous*3;
}
};
}