龙空技术网

Guava系列之不可变集合

开心果子爸 140

前言:

现时兄弟们对“java天堂”大致比较讲究,看官们都需要了解一些“java天堂”的相关知识。那么小编也在网摘上汇集了一些有关“java天堂””的相关资讯,希望朋友们能喜欢,同学们一起来了解一下吧!

13/100

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中的不可变集合

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。`

智能的Copyof

在常量时间内使用底层数据结构是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成不会造成内存泄露——例如,你有个很大的不可变集合ImmutableList hugeList, ImmutableList.copyOf(hugeList.subList(0, 10))就会显式地拷贝,以免不必要地持有hugeList的引用不改变语义——所以ImmutableSet.copyOf(myImmutableSortedSet)会显式地拷贝,因为和基于比较器的ImmutableSortedSet相比,ImmutableSet对hashCode()和equals有不同语义

asList视图

所有不可变集合都有一个asList()方法提供ImmutableList视图,来帮助你用列表形式方便地读取集合元素。例如,你可以使用sortedSet.asList().get(k)从ImmutableSortedSet中读取第k个最小元素。

asList()返回的ImmutableList通常是——并不总是——开销稳定的视图实现,而不是简单地把元素拷贝进List。也就是说,asList返回的列表视图通常比一般的列表平均性能更好,比如,在底层集合支持的情况下,它总是使用高效的contains方法。

可变集合与不可变集合对照表

参考:

如果感觉对你有些帮忙,想跟我一起学习,坚信技术改变世界,请关注【Java天堂】公众号,我会定期分享自己的学习成果,第一时间推送给你

标签: #java天堂