Guava集合--Immutable(不可变)集合

  所谓不可变集合,顾名思义就是定义了以后不可修改的集合。html

一.为何要使用不可变集合

不可变对象有不少优势,包括:java

  • 当对象被不可信的库调用时,不可变形式是安全的;
  • 不可变对象被多个线程调用时,不存在竞态条件问题
  • 不可变集合不须要考虑变化,所以能够节省时间和空间。全部不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
  • 不可变对象由于有固定不变,能够做为常量来安全使用。

建立对象的不可变拷贝是一项很好的防护性编程技巧。Guava为全部JDK标准集合类型和Guava新集合类型都提供了简单易用的不可变版本。
 JDK也提供了Collections.unmodifiableXXX方法把集合包装为不可变形式,但咱们认为不够好:git

  • 笨重并且累赘:不能温馨地用在全部想作防护性拷贝的场景;
  • 不安全:要保证没人经过原集合的引用进行修改,返回的集合才是事实上不可变的;
  • 低效:包装过的集合仍然保有可变集合的开销,好比并发修改的检查、散列表的额外空间,等等。

若是你没有修改某个集合的需求,或者但愿某个集合保持不变时,把它防护性地拷贝到不可变集合是个很好的实践。编程

重要提示:全部Guava不可变集合的实现都不接受null值。咱们对Google内部的代码库作过详细研究,发现只有5%的状况须要在集合中容许null元素,剩下的95%场景都是遇到null值就快速失败。若是你须要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法。更多细节建议请参考“使用和避免null”安全

 

二.JDK中实现immutable集合

package collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class JDKImmutable {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        System.out.println("list:" + list);

        List<String> unmodifiableList = Collections.unmodifiableList(list);
        System.out.println("unmodifiableList:" + unmodifiableList);

        list.add("d");
        System.out.println("list add a item after list:"+list);
        System.out.println("list add a item after unmodifiableList:"+unmodifiableList);

        List<String> unmodifiableList1 = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));
        System.out.println("unmodifiableList1:" + unmodifiableList1);
        unmodifiableList1.add("bb");
        System.out.println("unmodifiableList add a item after list:"+unmodifiableList1);
    }
}

运行结果:数据结构

 

 

说明:Collections.unmodifiableList实现的不是真正的不可变集合,当原始集合修改后,不可变集合也发生变化。不可变集合不能够修改集合数据,当强制修改时会报错,实例中的最后两个add会直接抛出不可修改的错误。并发

  总结一下JDK的Collections.unmodifiableXXX方法实现不可变集合的一些问题:性能

  1.它用起来笨拙繁琐你不得不在每一个防护性编程拷贝的地方用这个方法
  2.它不安全:若是有对象reference原始的被封装的集合类,这些方法返回的集合也就不是正真的不可改变。
  3.效率低:由于它返回的数据结构本质仍旧是原来的集合类,因此它的操做开销,包括并发下修改检查,hash table里的额外数据空间都和原来的集合是同样的。测试

 

三.如何使用Guava中的不可变集合

  Guava提供了对JDK里标准集合类里的immutable版本的简单方便的实现,以及Guava本身的一些专门集合类的immutable实现。当你不但愿修改一个集合类,或者想作一个常量集合类的时候,使用immutable集合类就是一个最佳的编程实践。ui

  Immutable集合使用方法:
    一个immutable集合能够有如下几种方式来建立:
    1.用copyOf方法, 譬如, ImmutableSet.copyOf(set)
    2.使用of方法,譬如,ImmutableSet.of("a", "b", "c")或者ImmutableMap.of("a", 1, "b", 2)
    3.使用Builder类

  例子:

package collections;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class GuavaImmutable  {

    public static void main(String[] args) {
        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);
    }
}

  运行结果:

 

 

比想象中更智能的copyOf

请注意,ImmutableXXX.copyOf方法会尝试在安全的时候避免作拷贝——实际的实现细节不详,但一般来讲是很智能的,好比:

package collections;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;

import java.util.ArrayList;
import java.util.List;

public class GuavaImmutableCopyOf {

    public static void main(String[] args) {
        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);
    }
}

运行结果:

 

 

ImmutableXXX.copyOf(ImmutableCollection)会试图对以下状况避免线性时间拷贝:

  • 在常量时间内使用底层数据结构是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成。
  • 不会形成内存泄露——例如,你有个很大的不可变集合ImmutableList<String>
    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方法。

package collections;

import com.google.common.collect.ImmutableSortedSet;

public class GuavaImmutableAsList {

    public static void main(String[] args) {
        ImmutableSortedSet<String> imSortSet=ImmutableSortedSet.of("aa","bb","cc","dd","ee");
        System.out.println(imSortSet.asList().get(2));
    }
}

运行结果:

 

 

Guava集合和不可变对应关系

可变集合接口 属于JDK仍是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