sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

Collections(九)Guava Collections

Open Sayi opened this issue 7 years ago • 0 comments

Java Collections Framework基本介绍完了,当我们遇到性能或者有需需要定义自己的集合时,比如希望数据不是保存在内存中而是从数据库迭代,可以扩展JDK提供的明确设计的抽象实现AbstractXXX接口。

Guava提供了一些特别的集合实现和工具类,本文旨在入门Guava Collections,分为三个部分:

  1. 更多的集合类型
  2. 不可变集合
  3. 工具

详细文档请参阅官方文档:https://github.com/google/guava/wiki。

更多的集合类型

Multiset

Multiset是一个元素可以重复的集合,Multiset可以被看做一个无需关心顺序ArrayList,或者一个元素和元素个数的Map。如果没有这个类,我们通常的实现会是这样的:

new ArrayList<E>()
或者
new HashMap<E, Integer>

Multiset 继承了JDK Collection接口,并且提供了一些额外的方法。

方法 作用
int count(@Nullable @CompatibleWith("E") Object element) 获取元素的个数
int setCount(E element, int count); 设置元素的个数
int add(@Nullable E element, int occurrences); 新增元素
Set<E> elementSet(); 获取不同的元素集合

根据不同特性和实现,Guava提供了以下几种Multiset:

  • HashMultiset
  • LinkedHashMultiset
  • TreeMultiset
  • ConcurrentHashMultiset
  • ImmutableMultiset

因为泛型书写的原因,Guava中创建集合大多数采用了工厂方法,不建议用new的方式,所以我们创建一个Multiset的代码如下:

Multiset<String> set = HashMultiset.<String>create();

Multimap

Multimap是一个key值允许重复的集合,可以看作为Map<K, List<V>>。如果没有这个类,我们的实现可能是这样的:

new HashMap<K, Collection<V>>

Multimap是一个独立的接口,它不是一个Ma,提供了一些基础方法:

方法 作用
Collection<V> get(@Nullable K key); 获取key对应的值集合
Multiset<K> keys(); 获取所有可重复的keys
Map<K, Collection<V>> asMap();转换为Map

如果当你希望使用key-collection这样的结构时,就可以考虑使用Multimap,根据valus的实现方式不同,我们更多用的是接口ListMultimap和接口SetMultimap,前者返回的值是一个列表List<V> get(@Nullable K key);,后者返回的是一个set:Set<V> get(@Nullable K key);

我们通过构建器MultimapBuilder进行初始化:

ListMultimap<String, String> map = MultimapBuilder.hashKeys().arrayListValues().build();

BiMap

BiMap是一个双向Map,既可以key-value映射,也可以value-key映射(调用inverse()方法),所以要求value必须是唯一的,实现原理是内部维护了两张哈希表。具体实有以下几个:

  • HashBiMap
  • EnumBiMap
  • EnumHashBiMap
  • ImmutableBiMap 同样,当我们初始化这些类时,是通过静态方法而不是new:
BiMap<String, Integer> userId = HashBiMap.create();

ClassToInstanceMap

ClassToInstanceMap一个专用的实现,表示类型与实例的映射,继承Map<Class<? extends B>, B>,我们可以看看接口定义:

public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> {
  <T extends B> T getInstance(Class<T> type);
  <T extends B> T putInstance(Class<T> type, @Nullable T value);
}

Guava提供了两个实现MutableClassToInstanceMap和ImmutableClassToInstanceMap,同样通过静态方法初始化:

MutableClassToInstanceMap.create();

不可变集合

顾名思义,该集合不支持修改操作,比如:

  • ImmutableList
  • ImmutableSet
  • ImmutableMap
  • ImmutableMultiset
  • ImmutableMultimap
  • ImmutableBiMap

通过前面文了解过,JDK提供了一个包装方法返回一个不可修改的集合:Collections.unmodifiableXXX,那么为什么Guava提供了新的实现方式呢?主要基于以下几点来考虑:

  1. 需要一个集合的常量的场景是广泛的,如果都要通过包装方法,显得冗余且厌烦
  2. 包装方法通常需要先实例化一个集合,而这个集合的引用如果继续被操作将影响到不可变的集合
  3. 包装方法是低效的,背后调用了一个具体集合的方法,里面可能包含了很多在不可变集合中无用的代码,所以我们需要针对不可变写一个高效的代码。

注意:所有不可变集合不支持null值,因为google觉得大多数场景下都不会使用null值。 构造不可变集合的方式也是通过工厂方法或者构建器:

Builder<String> builder = ImmutableList.<String>builder();
builder.add(str);
ImmutableList<String> list = builder.build();

// of
ImmutableList<String> list2 = ImmutableList.of("hi");

// copy of
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add("hi");
ImmutableList<Object> copyOf = ImmutableList.copyOf(arrayList);

工具

正如JDK提供了Collections工具类一样,Guava也提供了一系列工具类。

  • Collections2 区别于JDK工具类
  • Lists
  • Sets
  • Maps

这里不作介绍,可以自行查看官方文档,值得注意的是,这些工具类提供的静态构造方式Lists.newArrayList()已经不建议使用了,官方建议jdk1.7以后使用<>语法来代替这些工厂方法。

装饰器模式

Guava提供了一系列ForwardingXXX类为我们对集合进行装饰提供了便捷,我们只需要继承这些类,然后实现抽象方:

protected abstract List<E> delegate();

接下来,你只需要重写对应的方法进行装饰了。

抽象迭代器

我们知道,实现Iterator接口需要实现几个方法,guava为我们封装了一个实现Iterator接口的抽象类AbstractIterator方便我们继承,我们只需要方法computeNext即可,下面这段代码实现了一个迭代字符串的列表。

package com.deepoove.datastructure;

import java.util.AbstractList;
import java.util.Iterator;
import java.util.Objects;

import com.google.common.collect.AbstractIterator;

public class CharacterList extends AbstractList<Character> {

  private transient String str;

  public CharacterList(String str) {
      Objects.requireNonNull(str);
      this.str = str;
  }

  @Override
  public Character get(int index) {
      return str.charAt(index);
  }

  @Override
  public int size() {
      return str.length();
  }

  @Override
  public Iterator<Character> iterator() {
      return new AbstractIterator<Character>() {

          int i = 0;

          @Override
          protected Character computeNext() {
              if (i == CharacterList.this.size()) return endOfData();
              return CharacterList.this.str.charAt(i++);
          }
      };

//        return new Iterator<Character>() {
//
//            int i = 0;
//
//            @Override
//            public boolean hasNext() {
//                return i < CharacterList.this.size();
//            }
//
//            @Override
//            public Character next() {
//                return CharacterList.this.str.charAt(i++);
//            }
//        };
  }

}

总结

虽然JDK也能完某些任务,但是Guava Collections提供了很大的便利性。

这是这个系列文的最后一篇,熟读并不是需要你记得这些内容,而是更好的使用它。

Sayi avatar Sep 04 '18 10:09 Sayi