Java集合框架-宏观总结

对 Java 集合框架部分的知识进行宏观的总结,主要涉及集合类的设计思想,以及集合类之间相互转换方法。

集合接口概况与设计思想

集合框架中最基础的是各种接口,主要分为两大类:

  • java.util.Collection 接口
    • 子接口包括 Set, SortedSet, List, Deque …
  • java.util.Map 接口
    • 不是真正的集合,但通过 collection-view 操作,可以像操作集合一样操作它;
    • 子接口包括 SortedMap, NavigableMap, ConcurrentMap, ConcurrentNavigableMap.

1. 可修改性功能的实现

集合操作的核心是增删和查询。根据集合的可修改属性可以进行分类:

  • 是否支持修改;unmodifiable与immutable的区别
  • 集合大小是否可以改变(List);
  • 是否支持根据index快速获取元素(List);
  • 是否支持随机获取元素(RandomAccess);

可以对一些集合类进行包装,来做到 unmodifiable,例如 Collections.unmodifiableInterface, 返回被包装集合的一个不可修改的 view。

使用 Set, List 等的静态工厂方法 of() 也可以返回一个不可修改(immutable)的集合,如果尝试修改,会抛出一个 UnsupportedOperationException。下面为Java Set 的of方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 注意:ImmutableCollections 是 Container class for immutable collections. Not part of the public API.
* Mainly for namespace management and shared infrastructure.
*
* Ref: https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java
*
* AbstractImmutableSet 是一个内部类。
* static final class SetN<E> extends AbstractImmutableSet<E> implements Serializable {
* static final Set<?> EMPTY_SET = new SetN<>();
* ....}
*
*/
// since Java 9
// Set 中的 of 方法
static <E> Set<E> of() {
return ImmutableCollections.emptySet();
}
// ImmutableCollections 类中的 emptySet() 方法。
@SuppressWarnings("unchecked")
static <E> Set<E> emptySet() {
return (Set<E>) SetN.EMPTY_SET;
}

可以看到,实现 ImmutableCollection 的方法是在公开的 API 中,隐藏了他们的实现。

不在核心接口中直接设置集合是否可以修改,而是通过包装类或者工厂方法等生成不可修改的类,目的是保证核心接口、方法的简洁(类似网络的端到端原则)。

2. 互操作性

所有的集合表示之间都能够很好的进行互操作(interoperate)- 各种集合类型(包括数组)之间可以进行转换,从而可以更灵活的复用方法。

Collection接口 中,规定了大多数通用的集合间互相转换的方法。

  • addAll()方法,addAll(Collection<? extends E> c)

    • 需要关注的是顺序问题,如果List,则插在尾部;Deque, 插在尾部,= addLast()。
    • List - addAll(int index, Collection<? extends E> c:在特定的index 处插入 Collection 类型的元素。
  • containsAll() 方法,传入参数为 集合类型,containsAll(Collection<?> c)

  • removeAll(Collection<?> c), retainAll(Collection<?> c) 方法,删除所有集合中重复的值或者保留集合中重复的值(note: 可以快速的找出两个集合中重复的部分)。

  • toArray()方法:如果不传入参数,返回 Object[] 类型;也可以如下传入参数,直接指定变为 array 后的类型:String[] y = x.toArray(new String[0]);

Note: Java 在 Collection 接口中定义了互操作的方法,在具体的子接口中,如果有需要,会对这些接口进行重写(override)及重载(overloading, e.g. list 的 addAll() 方法)。

Arrays类 中也提供了一些将数组与集合类型对象进行转换的静态方法。

  • asList(), 返回 List<T>, 即将数组转为list, 元素类型通过 List 泛型进行保留。

但是需要注意的是,asList() 方法不能将基本数据类型的数组转为List,e.g.

1
2
3
int[] tmp = {1,2,3,4,5};
List<Integer> list1 = Arrays.asList(tmp); // 报错!!
List<int[]> list2 = Arrays.asList(tmp); // 不会报错,但是会把整个数组当作一个整体赋值给这个list。

Stackoverflow 上提供了几种将基本数据类型数组转为 List 的方法, ref

1
2
3
4
5
6
7
8
9
10
11
12
// method 1- 循环遍历添加
int[] ints = {1, 2, 3};
List<Integer> intList = new ArrayList<Integer>(ints.length);
for (int i : ints)
intList.add(i);

// mehtod 2- 使用 stream 来实现- @since 8
int[] ints = {1,2,3};
List<Integer> intlist = Arrays.stream(ints).boxed().collect(Collectors.toList());

// method 3- with google's guava library, 可以直接转换..
List<Integer> Ints.asList(int...)

Map 作为一种特殊的集合类型,与 Collection 接口之间没有继承关系。但可以通过一些方法转换为 集合类型 进行处理。

  • keySet() 方法,返回所有的 key 构成的集合,类型为 Set<K>
  • values() 方法,返回所有的 values 构成的集合,类型为 Collection<V>
  • entrySet() 方法,返回所有的条目构成的集合,类型为 Set<Map.Entry<K,V>>

注意的是,上述方法返回的都称为 view,返回的结果背后的存储内容为 Map 中的内容。也就是说,上述方法使得我们可以使用 集合类(Collection, Set 接口)中的方法,查询、修改 Map 中的元素。(类似的返回 view 的还有 List 的 subList 等方法,通过修改返回的view,达到修改原对象的目的。)

需要注意的是,删除掉 Map 中的 entry、key、value 任何一项,对应的一条记录会被整个的删除。

1
2
3
4
5
6
7
8
Map<Integer, String> map = new HashMap<>();
Collection<String> c = new ArrayList<>();
map.put(1,"Java");
map.put(2, "Python");
map.put(3, "JavaScript");
c = map.values();
c.remove("Java");
System.out.println(map.containsKey(1)); // 返回 false

Tip: Map接口(包括其他的 Collection接口)都是从Java1.2开始出现的,Map接口用于取代之前版本中的抽象类 Dictionary。

Reference:

  1. Java official document on Java Collection framework
  2. Java 11 source code