转载:http://www.cnblogs.com/peida/p/Guava_ImmutableCollections.html
Table:
当我们需要多个索引的数据结构的时候,通常情况下,我们只能用这种丑陋的Map<FirstName, Map<LastName, Person>>来实现。为此Guava提供了一个新的集合类型-Table集合类型,来支持这种数据结构的使用场景。Table支持“row”和“column”,而且提供多种视图。
Table<String, Integer, String> aTable = HashBasedTable.create();
for (char a = 'A'; a <= 'C'; ++a) {
for (Integer b = 1; b <= 3; ++b) {
aTable.put(Character.toString(a), b, String.format("%c%d", a, b));
}
}
System.out.println(aTable.column(2));
System.out.println(aTable.row("B"));
System.out.println(aTable.get("B", 2));
System.out.println(aTable.contains("D", 1));
System.out.println(aTable.containsColumn(3));
System.out.println(aTable.containsRow("C"));
System.out.println(aTable.columnMap());
System.out.println(aTable.rowMap());
System.out.println(aTable.remove("B", 3));
System.out.println(aTable.rowMap());
输出:
{A=A2, B=B2, C=C2}
{1=B1, 2=B2, 3=B3}
B2
false
true
true
{1={A=A1, B=B1, C=C1}, 2={A=A2, B=B2, C=C2}, 3={A=A3, B=B3, C=C3}}
{A={1=A1, 2=A2, 3=A3}, B={1=B1, 2=B2, 3=B3}, C={1=C1, 2=C2, 3=C3}}
B3
{A={1=A1, 2=A2, 3=A3}, B={1=B1, 2=B2}, C={1=C1, 2=C2, 3=C3}}
Table视图:
rowMap()返回一个Map<R, Map<C, V>>的视图。rowKeySet()类似地返回一个Set<R>。
row(r)返回一个非null的Map<C, V>。修改这个视图Map也会导致原表格的修改。
和列相关的方法有columnMap(), columnKeySet()和column(c)。(基于列的操作会比基于行的操作效率差些)
cellSet()返回的是以Table.Cell<R, C, V>为元素的Set。这里的Cell就类似Map.Entry,但是它是通过行和列来区分的。
Table有以下实现:
HashBasedTable:基于HashMap<R, HashMap<C, V>>的实现。
TreeBasedTable:基于TreeMap<R, TreeMap<C, V>>的实现。
ImmutableTable:基于ImmutableMap<R, ImmutableMap<C, V>>的实现。(注意,ImmutableTable已对稀疏和密集集合做了优化)
ArrayTable:ArrayTable是一个需要在构建的时候就需要定下行列的表格。这种表格由二维数组实现,这样可以在密集数据的表格的场合,提高时间和空间的效率。
Range:
在Guava中新增了一个新的类型Range,从名字就可以了解到,这个是和区间有关的数据结构。从Google官方文档可以得到定义:Range定义了连续跨度的范围边界,这个连续跨度是一个可以比较的类型(Comparable type)。比如1到100之间的整型数据。
在数学里面的范围是有边界和无边界之分的;同样,在Guava中也有这个说法。如果这个范围是有边界的,那么这个范围又可以分为包括开集(不包括端点)和闭集(包括端点);如果是无解的可以用+∞表示。如果枚举的话,一共有九种范围表示:
概念 | 表示范围 | guava对应功能方法 |
(a..b) | {x | a < x < b} | open(C, C) |
[a..b] | {x | a <= x <= b} | closed(C, C) |
[a..b) | {x | a <= x < b} | closedOpen(C, C) |
(a..b] | {x | a < x <= b} | openClosed(C, C) |
(a..+∞) | {x | x > a} | greaterThan(C) |
[a..+∞) | {x | x >= a} | atLeast(C) |
(-∞..b) | {x | x < b} | lessThan(C) |
(-∞..b] | {x | x <= b} | atMost(C) |
(-∞..+∞) | all values | all() |
上表中的guava对应功能方法那一栏表示Range类提供的方法,分别来表示九种可能出现的范围区间。如果区间两边都存在范围,在这种情况下,区间右边的数不可能比区间左边的数小。在极端情况下,区间两边的数是相等的,但前提条件是最少有一个边界是闭集的,否则是不成立的。比如:
[a..a] : 里面只有一个数a;
[a..a); (a..a] : 空的区间范围,但是是有效的;
(a..a) : 这种情况是无效的,构造这样的Range将会抛出异常。
在使用Range时需要注意:在构造区间时,尽量使用不可改变的类型。如果你需要使用可变的类型,在区间类型构造完成的情况下,请不要改变区间两边的数。
实例:
public class TestBaseRange {
@Test public void testRange(){ System.out.println("open:"+Range.open(1, 10)); System.out.println("closed:"+ Range.closed(1, 10)); System.out.println("closedOpen:"+ Range.closedOpen(1, 10)); System.out.println("openClosed:"+ Range.openClosed(1, 10)); System.out.println("greaterThan:"+ Range.greaterThan(10)); System.out.println("atLeast:"+ Range.atLeast(10)); System.out.println("lessThan:"+ Range.lessThan(10)); System.out.println("atMost:"+ Range.atMost(10)); System.out.println("all:"+ Range.all()); System.out.println("closed:"+Range.closed(10, 10)); System.out.println("closedOpen:"+Range.closedOpen(10, 10)); //会抛出异常 System.out.println("open:"+Range.open(10, 10)); } }
此外,范围可以构造实例通过绑定类型显式,例如:
public class TestBaseRange { @Test public void testRange(){ System.out.println("downTo:"+Range.downTo(4, BoundType.OPEN)); System.out.println("upTo:"+Range.upTo(4, BoundType.CLOSED)); System.out.println("range:"+Range.range(1, BoundType.CLOSED, 4, BoundType.OPEN)); } }
输出:
downTo:(4‥+∞) upTo:(-∞‥4] range:[1‥4)
操作方法
1.contains:判断值是否在当前Range内
@Test public void testContains(){ System.out.println(Range.closed(1, 3).contains(2)); System.out.println(Range.closed(1, 3).contains(4)); System.out.println(Range.lessThan(5).contains(5)); System.out.println(Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3))); } //=====输出===== true false false true
2.Endpoint相关查询方法:
@Test public void testQuery(){ System.out.println("hasLowerBound:"+Range.closedOpen(4, 4).hasLowerBound()); System.out.println("hasUpperBound:"+Range.closedOpen(4, 4).hasUpperBound()); System.out.println(Range.closedOpen(4, 4).isEmpty()); System.out.println(Range.openClosed(4, 4).isEmpty()); System.out.println(Range.closed(4, 4).isEmpty()); // Range.open throws IllegalArgumentException //System.out.println(Range.open(4, 4).isEmpty()); System.out.println(Range.closed(3, 10).lowerEndpoint()); System.out.println(Range.open(3, 10).lowerEndpoint()); System.out.println(Range.closed(3, 10).upperEndpoint()); System.out.println(Range.open(3, 10).upperEndpoint()); System.out.println(Range.closed(3, 10).lowerBoundType()); System.out.println(Range.open(3, 10).upperBoundType()); } //======输出======= hasLowerBound:true hasUpperBound:true true true false 3 3 10 10 CLOSED OPEN
3.encloses方法:encloses(Range range)中的range是否包含在需要比较的range中
@Test public void testEncloses(){ Range<Integer> rangeBase=Range.open(1, 4); Range<Integer> rangeClose=Range.closed(2, 3); Range<Integer> rangeCloseOpen=Range.closedOpen(2, 4); Range<Integer> rangeCloseOther=Range.closedOpen(2, 5); System.out.println("rangeBase: "+rangeBase+" Enclose:"+rangeBase.encloses(rangeClose)+" rangeClose:"+rangeClose); System.out.println("rangeBase: "+rangeBase+" Enclose:"+rangeBase.encloses(rangeCloseOpen)+" rangeClose:"+rangeCloseOpen); System.out.println("rangeBase: "+rangeBase+" Enclose:"+rangeBase.encloses(rangeCloseOther)+" rangeClose:"+rangeCloseOther); } //=======输出======== rangeBase: (1‥4) Enclose:true rangeClose:[2‥3] rangeBase: (1‥4) Enclose:true rangeClose:[2‥4) rangeBase: (1‥4) Enclose:false rangeClose:[2‥5)
4.isConnected:range是否可连接上
@Test public void testConnected(){ System.out.println(Range.closed(3, 5).isConnected(Range.open(5, 10))); System.out.println(Range.closed(0, 9).isConnected(Range.closed(3, 4))); System.out.println(Range.closed(0, 5).isConnected(Range.closed(3, 9))); System.out.println(Range.open(3, 5).isConnected(Range.open(5, 10))); System.out.println(Range.closed(1, 5).isConnected(Range.closed(6, 10))); } //======输出========= true true true false false
4.intersection:如果两个range相连时,返回最大交集,如果不相连时,直接抛出异常
@Test public void testIntersection(){ System.out.println(Range.closed(3, 5).intersection(Range.open(5, 10))); System.out.println(Range.closed(0, 9).intersection(Range.closed(3, 4))); System.out.println(Range.closed(0, 5).intersection(Range.closed(3, 9))); System.out.println(Range.open(3, 5).intersection(Range.open(5, 10))); System.out.println(Range.closed(1, 5).intersection(Range.closed(6, 10))); } //=======输出========= (5‥5] [3‥4] [3‥5] 注意:第四和第五行代码,当集合不相连时,会直接报错
5.span:获取两个range的并集,如果两个range是两连的,则是其最小range
@Test public void testSpan(){ System.out.println(Range.closed(3, 5).span(Range.open(5, 10))); System.out.println(Range.closed(0, 9).span(Range.closed(3, 4))); System.out.println(Range.closed(0, 5).span(Range.closed(3, 9))); System.out.println(Range.open(3, 5).span(Range.open(5, 10))); System.out.println(Range.closed(1, 5).span(Range.closed(6, 10))); System.out.println(Range.closed(1, 5).span(Range.closed(7, 10))); } //=====输出======= true true true false false
Range使用:
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Range;
/**
* Range:为一个可比较类型连续的值定义连续边界
* User: Realfighter
* Date: 2014/10/16
* Time: 22:18
*/
public class RangeTest {
public static void main(String[] args) {
//定义一个返回Person年龄的函数
Function<Person, Integer> ageFunction = new Function<Person, Integer>() {
@Override
public Integer apply(Person input) {
return input.getAge();
}
};
//定义一个年龄大于等于25小于30岁的区间
Range<Integer> ageRange = Range.closedOpen(25, 30);
//定义一个对于指定区间年龄Person的过滤
// 注:Range实现了Predicate接口,所以compose方法可以接口ageRange
Predicate<Person> personPredicate = Predicates.compose(ageRange, ageFunction);
Person person = new Person("张三", 30);
System.out.println(personPredicate.apply(person));//false,不在区间内
}
}
//Person实现了Comparable接口,当我们需要选择一定年龄区间的Person时
// 通过Comparable比较显然麻烦许多,这时候可以使用Range
class Person implements Comparable<Person> {
private Integer age;//年龄
private String name;//名称
@Override
public int compareTo(Person o) {
//使用Guava提供的ComparisonChain进行对象的链式比较
return ComparisonChain.start()
.compare(this.name, o.getName())
.compare(this.age, o.getAge()).result();
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
Immutable:
不可变集合,顾名思义就是说集合是不可被修改的。集合的数据项是在创建的时候提供,并且在整个生命周期中都不可改变。
为什么要用immutable对象?immutable对象有以下的优点:
1.对不可靠的客户代码库来说,它使用安全,可以在未受信任的类库中安全的使用这些对象
2.线程安全的:immutable对象在多线程下安全,没有竞态条件
3.不需要支持可变性, 可以尽量节省空间和时间的开销. 所有的不可变集合实现都比可变集合更加有效的利用内存 (analysis)
4.可以被使用为一个常量,并且期望在未来也是保持不变的
immutable对象可以很自然地用作常量,因为它们天生就是不可变的对于immutable对象的运用来说,它是一个很好的防御编程(defensive programming)的技术实践。
JDK中实现immutable集合
在JDK中提供了Collections.unmodifiableXXX系列方法来实现不可变集合, 但是存在一些问题,下面我们先看一个具体实例:
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; public class ImmutableTest { @Test public void testJDKImmutable(){ List<String> list=new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); System.out.println(list); List<String> unmodifiableList=Collections.unmodifiableList(list); System.out.println(unmodifiableList); List<String> unmodifiableList1=Collections.unmodifiableList(Arrays.asList("a","b","c")); System.out.println(unmodifiableList1); String temp=unmodifiableList.get(1); System.out.println("unmodifiableList [0]:"+temp); list.add("baby"); System.out.println("list add a item after list:"+list); System.out.println("list add a item after unmodifiableList:"+unmodifiableList); unmodifiableList1.add("bb"); System.out.println("unmodifiableList add a item after list:"+unmodifiableList1); unmodifiableList.add("cc"); System.out.println("unmodifiableList add a item after list:"+unmodifiableList); } }
输出:
[a, b, c] [a, b, c] [a, b, c] unmodifiableList [0]:b list add a item after list:[a, b, c, baby] list add a item after unmodifiableList1:[a, b, c, baby]
说明:Collections.unmodifiableList实现的不是真正的不可变集合,当原始集合修改后,不可变集合也发生变化。不可变集合不可以修改集合数据,当强制修改时会报错,实例中的最后两个add会直接抛出不可修改的错误。
总结一下JDK的Collections.unmodifiableXXX方法实现不可变集合的一些问题:
1.它用起来笨拙繁琐你不得不在每个防御性编程拷贝的地方用这个方法
2.它不安全:如果有对象reference原始的被封装的集合类,这些方法返回的集合也就不是正真的不可改变。
3.效率低:因为它返回的数据结构本质仍旧是原来的集合类,所以它的操作开销,包括并发下修改检查,hash table里的额外数据空间都和原来的集合是一样的。
Guava的immutable集合
Guava提供了对JDK里标准集合类里的immutable版本的简单方便的实现,以及Guava自己的一些专门集合类的immutable实现。当你不希望修改一个集合类,或者想做一个常量集合类的时候,使用immutable集合类就是一个最佳的编程实践。
注意:每个Guava immutable集合类的实现都拒绝null值。我们做过对Google内部代码的全面的调查,并且发现只有5%的情况下集合类允许null值,而95%的情况下都拒绝null值。万一你真的需要能接受null值的集合类,你可以考虑用Collections.unmodifiableXXX。
Immutable集合使用方法:
一个immutable集合可以有以下几种方式来创建:
1.用copyOf方法, 譬如, ImmutableSet.copyOf(set)
2.使用of方法,譬如,ImmutableSet.of("a", "b", "c")或者ImmutableMap.of("a", 1, "b", 2)
3.使用Builder类
实例:
@Test public void testGuavaImmutable(){ List<String> list=new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); System.out.println("list:"+list); ImmutableList<String> imlist=ImmutableList.copyOf(list); System.out.println("imlist:"+imlist); ImmutableList<String> imOflist=ImmutableList.of("peida","jerry","harry"); System.out.println("imOflist:"+imOflist); ImmutableSortedSet<String> imSortList=ImmutableSortedSet.of("a", "b", "c", "a", "d", "b"); System.out.println("imSortList:"+imSortList); list.add("baby"); System.out.println("list add a item after list:"+list); System.out.println("list add a item after imlist:"+imlist); ImmutableSet<Color> imColorSet = ImmutableSet.<Color>builder() .add(new Color(0, 255, 255)) .add(new Color(0, 191, 255)) .build(); System.out.println("imColorSet:"+imColorSet); }
输出:
list:[a, b, c] imlist:[a, b, c] imOflist:[peida, jerry, harry] imSortList:[a, b, c, d] list add a item after list:[a, b, c, baby] list add a item after imlist:[a, b, c] imColorSet:[java.awt.Color[r=0,g=255,b=255], java.awt.Color[r=0,g=191,b=255]]
对于排序的集合来说有例外,因为元素的顺序在构建集合的时候就被固定下来了。譬如,ImmutableSet.of("a", "b", "c", "a", "d", "b"),对于这个集合的遍历顺序来说就是"a", "b", "c", "d"。
更智能的copyOf
copyOf方法比你想象的要智能,ImmutableXXX.copyOf会在合适的情况下避免拷贝元素的操作-先忽略具体的细节,但是它的实现一般都是很“智能”的。譬如:
@Test public void testCotyOf(){ ImmutableSet<String> imSet=ImmutableSet.of("peida","jerry","harry","lisa"); System.out.println("imSet:"+imSet); ImmutableList<String> imlist=ImmutableList.copyOf(imSet); System.out.println("imlist:"+imlist); ImmutableSortedSet<String> imSortSet=ImmutableSortedSet.copyOf(imSet); System.out.println("imSortSet:"+imSortSet); List<String> list=new ArrayList<String>(); for(int i=0;i<20;i++){ list.add(i+"x"); } System.out.println("list:"+list); ImmutableList<String> imInfolist=ImmutableList.copyOf(list.subList(2, 18)); System.out.println("imInfolist:"+imInfolist); int imInfolistSize=imInfolist.size(); System.out.println("imInfolistSize:"+imInfolistSize); ImmutableSet<String> imInfoSet=ImmutableSet.copyOf(imInfolist.subList(2, imInfolistSize-3)); System.out.println("imInfoSet:"+imInfoSet); }
输出:
imSet:[peida, jerry, harry, lisa] imlist:[peida, jerry, harry, lisa] imSortSet:[harry, jerry, lisa, peida] list:[0x, 1x, 2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x, 15x, 16x, 17x, 18x, 19x] imInfolist:[2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x, 15x, 16x, 17x] imInfolistSize:16 imInfoSet:[4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x]
在这段代码中,ImmutableList.copyOf(imSet)会智能地返回时间复杂度为常数的ImmutableSet的imSet.asList()。
一般来说,ImmutableXXX.copyOf(ImmutableCollection)会避免线性复杂度的拷贝操作。如在以下情况:
这个操作有可能就利用了被封装数据结构的常数复杂度的操作。但例如ImmutableSet.copyOf(list)不能在常数复杂度下实现。
这样不会导致内存泄漏-例如,你有个ImmutableList<String> imInfolist,然后你显式操作ImmutableList.copyOf(imInfolist.subList(0, 10))。这样的操作可以避免意外持有不再需要的在hugeList里元素的reference。
它不会改变集合的语意-像ImmutableSet.copyOf(myImmutableSortedSet)这样的显式拷贝操作,因为在ImmutableSet里的hashCode()和equals()的含义和基于comparator的ImmutableSortedSet是不同的。
这些特性有助于最优化防御性编程的性能开销。
asList方法
所有的immutable集合都以asList()的形式提供了ImmutableList视图(view)。譬如,你把数据放在ImmutableSortedSet,你就可以调用sortedSet.asList().get(k)来取得前k个元素的集合。
返回的ImmutableList常常是个常数复杂度的视图,而不是一个真的拷贝。也就是说,这个返回集合比一般的List更智能-譬如,它会更高效地实现contains这样的方法。
实例:
@Test public void testAsList(){ ImmutableList<String> imList=ImmutableList.of("peida","jerry","harry","lisa","jerry"); System.out.println("imList:"+imList); ImmutableSortedSet<String> imSortList=ImmutableSortedSet.copyOf(imList); System.out.println("imSortList:"+imSortList); System.out.println("imSortList as list:"+imSortList.asList()); }
输出:
imList:[peida, jerry, harry, lisa, jerry] imSortList:[harry, jerry, lisa, peida] imSortList as list:[harry, jerry, lisa, peida]
Guava集合和不可变对应关系
可变集合类型 | 可变集合源:JDK or Guava? | Guava不可变集合 |
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |