【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)

为什么要使用不可变集合

不可变集合包括元组和冻结集合,其特点是不能被修改。元组有序、不可变、可包含不同类型元素,不能进行修改、删除操作,可通过索引访问元素。冻结集合是一种无序的集合类型,内部元素不能修改、添加或删除,因此常用于

不可变对象有很多优点

不可变对象有以下优点:

  1. 对于不可信的库调用,不可变对象是安全的;

  2. 在多线程环境下,由于不可变对象不会发生变化,因此不存在竞态条件问题;

  3. 不可变集合不需要考虑变化,因此可以节省时间和,所有不可变的集合都比可变集合有更好的内性,可以作为常量来安全使用。

Java原生的不可变集合

JDK提供的Collections.unmodifiableXXX方法可以将集合包装为不可变形式,但是我们认为这种方法并不理想。

  • 笨重而繁琐:不能适用于所有需要防御性拷贝的情况;
  • 不安全:必须确保没有人能够通过原始集合的引用进行修改,才能得到实际上不可变的集合;
  • 低效:包装成的集合仍然带有可变集合的负担,例如并发修改检查、散列表需要的额外空间等。

如果不需要修改某个集合,或者希望保持集合不变,将其防御性地拷贝到不可变集合是一个很好的实践。

重要提示:Guava不可变集合的实现不支持null值。通过对Google内部的代码库的详细研究,发现只有5%的情况需要在集合中允许null元素,而其余95%的场景都会快速失败处理null值。如果您需要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法

Java原生的不可变集合的案例

这段 Java 代码定义了一个名为 LUCKY_NUMBERS 的静态常量 Set(即不可变集合)。

 

java

复制代码

public static final Set<Integer> LUCKY_NUMBERS; static { Set<Integer> set = new LinkedHashSet<Integer>(); set.add(4); set.add(8); set.add(15); set.add(16); set.add(23); set.add(42); LUCKY_NUMBERS = Collections.unmodifiableSet(set); }

使用 static 修饰符来表示这个 Set 常量是与类相关联,而不是与类的实例相关联的。static 代码块是一个静态初始化块,用于在类加载时初始化静态成员变量。在这里,用 LinkedHashSet 创建了一个 Set 对象,并将其设置为不可变集合。LinkedHashSet 可以保 持插入元素的顺序。

这个 Set 常量包含 6 个自然数,即 4,8,15,16,23 和 42。由于 Set 对象被设置为不可变集合,因此无法将新元素添加到集合中,以在代码运行时保持其中元素的不变性。

 

java

复制代码

public static final Set<Integer> LUCKY_NUMBERS = Collections.unmodifiableSet( new LinkedHashSet<Integer>( Arrays.asList(4, 8, 15, 16, 23, 42)));

Java原生定义了一个名为 LUCKY_NUMBERS 的静态常量 Set(即不可变集合)。这个 Set 常量包含 6 个自然数,即 4,8,15,16,23 和 42。与之前的示例代码不同的是,这里使用了更简单的方式实现了相同的功能——使用 Collections 类的 unmodifiableSet() 方法。在这里,使用 Arrays 类的 asList() 方法将一些整数转换成一个 List,然后再使用 LinkedHashSet 构造函数创建一个 LinkedHashSet。最后,将创建的 LinkedHashSet 转换为不可变 Set,并将其赋值为 LUCKY_NUMBERS 静态常量。由于 Set 对象被设置为不可变集合,因此无法将新元素添加到集合中,以在代码运行时保持其中元素的不变性。

 

java

复制代码

public static final ImmutableSet<Integer> LUCKY_NUMBERS = ImmutableSet.of(4, 8, 15, 16, 23, 42);

Guava定义了一个名为 LUCKY_NUMBERS 的静态常量集合,其元素为 4,8,15,16,23 和 42。与之前的示例不同的是,这里使用了 Guava 提供的 ImmutableSet,其是一个不可变集合。通过调用 of() 静态方法并传入要添加到集合中的元素,会返回一个不可变的 ImmutableSet 集合对象。ImmutableSet 允许我们创建一个在运行时不可更改的集合,从而使多线程代码的编写更加轻松,并且可以更好地保证代码安全性。由于 LUCKY_NUMBERS 集合使用了 ImmutableSet,因此在其对象的生命周期内,它的内容不会被修改,这有助于确保代码的稳定性和正确性。

怎么使用不可变集合

可用的方法来创建不可变集合有:

  1. 使用copyOf方法,例如ImmutableSet.copyOf(set);
 

java

复制代码

class Foo { Set<Bar> bars; Foo(Set<Bar> bars) { this.bars = ImmutableSet.copyOf(bars); // defensive copy! } }

  1. 使用of方法,例如ImmutableSet.of("a","b","c")或ImmutableMap.of("a",1,"b",2);
 

java

复制代码

public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of( "red", "orange", "yellow", "green", "blue", "purple");

此外,对有序不可变集合来说,排序是在构造集合的时候完成的,如:

 

java

复制代码

ImmutableSortedSet.of("a", "b", "c", "a", "d", "b")

会在构造时就把元素排序为 a, b, c, d。

  1. 使用构建器工具,例如使用ImmutableList.builder()返回一个builder对象,之后可使用该对象来添加或删除元素,并使用它们来创建不可变的集合。
 

java

复制代码

public static final ImmutableSet<Color> GOOGLE_COLORS = ImmutableSet.<Color>builder() .addAll(WEBSAFE_COLORS) .add(new Color(0, 191, 255)) .build();

比想象中更智能的copyOf

值得注意的是,ImmutableXXX.copyOf() 方法在安全的情况下会尝试避免进行拷贝。实现细节不详,但通常是非常智能的,例如:

 

java

复制代码

ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz"); thingamajig(foobar); void thingamajig(Collection<String> collection) { ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection); ... }

在这段代码中,调用ImmutableList.copyOf(foobar)会智能地直接返回 foobar.asList(),这是一个常量时间复杂度的 List 视图,其存储的元素是不可变的。

ImmutableXXX.copyOf(ImmutableCollection) 是一种探索性实现,旨在避免对于一些可能的情况进行线性时间复杂度的拷贝操作,例如:

  • 可以在常量时间内使用底层数据结构,但是ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成。
  • 如使用ImmutableList.copyOf(hugeList.sublist(0,10)),可以避免不必要地持有 hugeList 的引用,从而防止内存泄漏问题的发生。
  • 如果我们想使用 ImmutableSet.copyOf(myImmutableSortedSet) 操作来获取不改变语义的 ImmutableSet,需要注意不同语义对 hashCode() 和 equals 的影响。为避免此类问题,需要显式地拷贝一份新的 ImmutableSet。

为了减少防御性编程风格带来的性能开销,在合适的情况下我们可以尽量避免进行线性拷贝操作。这样可以最大限度地提高代码的性能表现。

asList视图

所有不可变集合都提供 asList() 方法,可以方便地将集合元素以列表形式进行读取。例如,可以使用 sortedSet.asList().get(k) 在 ImmutableSortedSet 中读取第k个最小元素。

注意,asList() 返回的 ImmutableList 视图实现通常是稳定的且开销较小,而非简单地将元素拷贝进列表中。因此,asList() 返回的列表视图通常比一般列表的平均性能更好。此外,在底层集合的支持下,asList() 总是使用高效的 contains() 方法。

可变集合和不可变集合的关系

可变集合接口JDK不可变版本
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet/
MapJDKImmutableMap
SortedMap/NavigableMapJDKImmutableSortedMap/
MultisetGuavaImmutableMultiset
SortedMultiset/NavigableMultisetGuavaImmutableSortedMultiset/
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable
不可变的Map

定义了一个名为 ENGLISH_TO_INT 的静态不可变 Map,用于将字符串类型的英文单词转换为对应的整数值。

 

java

复制代码

public static final Map<String, Integer> ENGLISH_TO_INT; static { Map<String, Integer> map = new LinkedHashMap<String, Integer>(); map.put("four", 4); map.put("eight", 8); map.put("fifteen", 15); map.put("sixteen", 16); map.put("twenty-three", 23); map.put("forty-two", 42); ENGLISH_TO_INT = Collections.unmodifiableMap(map); }

使用 LinkedHashMap 存储字符串-整数的映射关系,其中字符串代表英文单词,整数为对应的数字。在静态代码块中,程序使用 Collections.unmodifiableMap() 将这个 Map 转换为不可修改的 Map,并将其赋值给 ENGLISH_TO_INT 常量。这个过程将会确保 ENGLISH_TO_INT 对象在其生命周期内不会被修改,从而防止了可能的错误或安全问题。

 

java

复制代码

public static final ImmutableMap<String, Integer> ENGLISH_TO_INT = ImmutableMap .with("four", 4) .with("eight", 8) .with("fifteen", 15) .with("sixteen", 16) .with("twenty-three", 23) .with("forty-two", 42) .build();

这段代码使用Guava定义了一个名为 ENGLISH_TO_INT 的静态不可变 Map,用于将字符串类型的英文单词转换为对应的整数值。这个代码的实现方式是使用 Guava 开源库中的 ImmutableMap 实现。静态常量 ENGLISH_TO_INT 的值是通过使用 with() 方法连续地向 ImmutableMap 对象中添加字符串-整数的映射关系,然后调用 build() 方法建立不可变的 Map 对象实现的。

与第一个代码示例相比,这个实现方式更加简洁且语法更加直观。此外,Guava 中的 ImmutableMap 实现具有更好的性能和可读性,并能够避免并发修改和不必要的内存使用,从而更容易实现代码的可靠性和稳定性。

ImmutableMap.of
 

java

复制代码

static final ImmutableMap<Integer, String> MAP = ImmutableMap.of(1, "one", 2, "two");

这段 Java 代码定义了一个名为 MAP 的静态不可变 Map 对象,它的键是整数类型的 1 和 2,分别对应的值是字符串类型的 "one" 和 "two"。

ImmutableMap.of() 方法是 Guava 开源库中用于创建不可变 Map 对象的快捷方式。这个方法支持传递 1 到 5 个键值对作为参数,从而只需一行代码就能创建一个简单的不可变 Map 对象。由于这个 Map 对象是不可变的,因此它的键值对不能被增加、删除或修改。这种不可变性使得这个 Map 对象更加稳定和安全,并且在多线程环境中更加可靠。

总的来说,这种代码编写风格更加简洁和直观,同时使用了 Guava 提供的不可变集合工具类库,从而可以提高代码的可读性和稳定性。同时这种实现方式还可以避免程序的并发修改和不必要的内存使用,具有更好的性能表现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值