Guava的不可变集合(Immutable Collections)
对象创建后,所有的状态和属性在整个生命周期内不能被修改;同理,不可变集合就是集合创建后,不能对集合中的对象进行修改为什么需要不可变对象?或者说不可变对象有什么好处?
好处1:让并发处理变得更简单了,对象是线程安全的
好处2:消除了副作用
下面我们看一个例子
public class DemoTest {
public static void main(String[] args){
Student student = new Student();
student.setName("tom");
student.setAge(30);
DemoTest demoTest = new DemoTest();
demoTest.validateAge(student);
//如果后续要使用Age,很可能不知道Age被改了,容易产生BUG
}
public boolean validateAge(Student student){
if(student.getAge() > 20){
student.setAge(student.getAge() - 3);//此处对年龄进行了副作用处理
return false;
}
return true;
}
}
class Student{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
那假如Student是不可变对象,创建后属性和状态都不能被改变,就不会出现这种问题了
好处3:不可变对象可以减少集合出错的概率
经常以String作为Key,那假如String是可变的对象,想想会有什么问题?假如key是可变的对象,可能出现的问题是,当你去通过String对象get数据的时候,有可能Map中Key已经变了,会导致你取不到对象了。
上面我们说不可变对象有很多好处, 那不可变对象是完全不可变的吗?
看看下面这段代码:
String str = "Hello";
System.out.println("str:" + str);
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] arr = (char[]) value.get(str);
arr[4] = '_';
System.out.println("str:" + str);
输出结果:
str:Hello
str:Hell_
可以看出来,String对象创建后,可以利用反射来进行修改;既然能改变,为何还叫不可变对象?
这里面大家不要误会不可变的本意,从不可变对象的意义分析能看出来对象的不可变性只是用来辅助帮助大家更简单地去编写代码,减少程序编写过程中出错的概率,这是不可变对象的初衷。
如果真要靠通过反射来改变一个对象的状态,此时编写代码的人也应该会意识到此类在设计的时候就不希望其状态被更改,从而引起编写代码的人的注意
以上我们介绍了不可变对象,下面我们就看一下Guava中的不可变集合
为什么要使用不可变集合?
- 不可变对象提供给别人使用时是安全的
- 不可变集合节省内存空间,因为不可变,集合空间在创建时就已经确定好了,不用考虑扩容等问题,内存利用率高
- 不可变集合可用于常量
不可变集合的使用方法
其实JDK中也提供了不可变集合,如下:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
List<String> unList = Collections.unmodifiableList(list);
unList.add("d");//往不可变List中添加元素会报错
表面上看,也实现了不可变集合,但是我修改原list呢
list.add(d);
此时,可以修改成功,并且不可变unList中的元素也被修改了,没有达到不可变的特性
Guava中不可变集合的使用方法
1、copyOf方法
基于已有的集合创建不可变集合
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
ImmutableList<String> immutList = ImmutableList.copyOf(list);
在原list中增加元素,不可变集合不受影响
list.add("d");
System.out.println(immutList);
输出如下:
[a,b,c]
2、of方法
ImmutableList<String> immutableList = ImmutableList.of("a","b","c");
3、Builder方法
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
ImmutableList<String> immutableList = ImmutableList.<String>builder().addAll(list).add("d").build();
可以看到,Builder方法更像是组合了copyOf和of方法;此处,对于有序的不可变集合来说,是在集合构造完成时就已经排序完成
ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
会在构造时把元素排序为a,b,c,d。`
- 在常量时间内使用底层数据结构是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成
- 不会造成内存泄露——例如,你有个很大的不可变集合ImmutableList hugeList, ImmutableList.copyOf(hugeList.subList(0, 10))就会显式地拷贝,以免不必要地持有hugeList的引用
- 不改变语义——所以ImmutableSet.copyOf(myImmutableSortedSet)会显式地拷贝,因为和基于比较器的ImmutableSortedSet相比,ImmutableSet对hashCode()和equals有不同语义
所有不可变集合都有一个asList()方法提供ImmutableList视图,来帮助你用列表形式方便地读取集合元素。例如,你可以使用sortedSet.asList().get(k)从ImmutableSortedSet中读取第k个最小元素。
asList()返回的ImmutableList通常是——并不总是——开销稳定的视图实现,而不是简单地把元素拷贝进List。也就是说,asList返回的列表视图通常比一般的列表平均性能更好,比如,在底层集合支持的情况下,它总是使用高效的contains方法。
可变集合类型 | 可变集合源:JDK or Guava? | Guava不可变集合 |
---|---|---|
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
ImmutableSortedSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
参考:
https://www.cnblogs.com/dolphin0520/p/10693891.html
https://blog.51cto.com/kaolaa/1794793
如果感觉对你有些帮忙,想跟我一起学习,坚信技术改变世界,请关注Java天堂公众号,我会定期分享自己的学习成果,第一时间推送给你