Guava是一组来自谷歌的核心Java库,其中包括新的集合类型(比如multimap射和multiset)、不可变集合、并发、I/O、散列、缓存、字符串等的实用工具。它在谷歌中的大多数Java项目中被广泛使用,也被许多其他公司广泛使用
今天我们就来看一下Guava的不可变集合(Immutable Collections)
首先我们思考一个问题,什么是不可变,我们知道在Java里面String就是不可变的,为什么要设计不可变的类和集合呢?
你可以先想想这个问题,再继续往下看
对象创建后,所有的状态和属性在整个生命周期内不能被修改
同理,不可变集合就是集合创建后,不能对集合中的对象进行修改
好处1:让并发处理变得更简单了
在并发编程的世界里,最麻烦的问题就是处理多个线程间的资源共享问题,稍不注意就会出现线程间相互影响的问题
而且这种问题排查起来非常困难,不是每次都会出现。
一般我们处理并发问题,都是在共享资源上加锁,比如synchronize、Lock等,让线程串行的来进行处理
其实仔细想一下,之所有会有并发的问题,是因为线程之间会进行资源争抢,对共享资源进行修改才会影响到其他线程
那假如共享资源不能被修改,每个线程获取到的都是一样的,就不存在并发的问题了
想想是不是?每个线程获取到的数据都是一样的,而且共享资源不能被任何线程修改,那线程之间根本就不会相互影响,天然就是线程安全的
所以不可变对象的好处之一是不用处理并发情况下的资源竞争问题
好处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:不可变对象可以减少集合出错的概率
大家在使用Map的时候,是不是经常以String作为Key,那假如String是可变的对象,想想会有什么问题?
Map<String,String> map = new HashMap<String,String>();
String key = "hello";
map.put(key,"world");
假如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);
我们可以验证一下上面JDK不可变集合的问题,在原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。`
所有不可变集合都有一个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 |
SortedSet | 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