List 集合

List 集合中元素有序、可重复,集合中每个元素都有其对应的索引顺序。

看个例子:

public class A {
    public boolean equals(Object obj) {
        return true;
    }
}

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

public class ListTest2 {
    public static void main(String[] args) {
        List books = new ArrayList();
        books.add(new String("a"));
        books.add(new String("b"));
        books.add(new String("c"));
        System.out.println(books);
        books.remove(new A());
        System.out.println(books);
        books.remove(new A());
        System.out.println(books);
    }
}

当试图删除一个 A 对象时,List 会调用 A 对象的 equals 方法依次与集合元素进行比较。如果 equals 方法以某个集合元素作为参数时返回 true,List 将会删除该元素。这里 A 重写了 equals 方法,总是返回 true,所以每次都会从 List 集合中删除一个元素。

ArrayList 类

ArrayList 类是基于数组实现的 List 类,完全支持前面介绍的 List 接口的全部功能。

ArrayList 封装了一个动态的、允许再分配的 Object[] 数组。

Set 集合

HashSet 类

  • 元素没有顺序,集合元素的值可以是 null

  • HashSet 不是同步的,假设有多个线程同时修改了 HashSet 集合时,必须通过代码来保证其同步

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来获得该对象的 hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置。

HashSet 中每个能存储元素的槽位称为桶(bucket)。如果多个元素的 hashCode 值相同,但它们通过 equals 方法比较返回 false,就需要在一个桶里放多个元素,这会导致性能下降。所以,建议在需要把某个类的对象保存到 HashSet 集合时,重写该类的 equals 和 hashCode 方法,尽量保证两个对象通过 equals 方法比较返回 true 时,他们的 hashCode 方法返回值也相等。

当把可变对象添加到 HashSet 中后,需要特别小心,尽量不要去修改可变对象中参与计算 hashCode() 、equals() 方法的实例变量,否则会导致 HashSet 无法正确访问这些集合元素。

看个例子:

public class R {
    int count;
    public R(int count) {
        this.count = count;
    }
    public String toString() {
        return "R[count:" + count + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj != null && obj.getClass() == R.class) {
            R r = (R)obj;
            return this.count == r.count;
        }
        return false;
    }
    public int hashCode() {
        return this.count;
    }
}

import java.util.HashSet;
import java.util.Iterator;

public class HashSetTest2 {
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        hs.add(new R(5));
        hs.add(new R(-3));
        hs.add(new R(9));
        hs.add(new R(-2));
        System.out.println(hs);
        Iterator it = hs.iterator();
        R first = (R)it.next();
        first.count = -3;
        System.out.println(hs);
        hs.remove(new R(-3));
        System.out.println(hs);
        System.out.println("hs 是否包含 count 为 -3 的 R 对象" + hs.contains(new R(-3)));
        System.out.println("hs 是否包含 count 为 -2 的 R 对象" + hs.contains(new R(-2)));
    }
}

/*
[R[count:-2], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:5], R[count:9]]
hs 是否包含 count 为 -3 的 R 对象false
hs 是否包含 count 为 -2 的 R 对象false
*/

LinkedHashSet 类

LinkedHashSet 是 HashSet 的子类,同样根据 hashCode 值来决定元素的存储位置。但是使用链表维护元素的次序,使得当遍历 LinkedHashSet 集合里的元素时,LinkedHashSet 会按元素的添加顺序访问集合里的元素。

LinkedHashSet 需要维护元素的插入顺序,因此性能略低于 HashSet,但在迭代访问 Set 里的全部元素时会有很好的性能,因为它以链表维护内部的顺序。

TreeSet 类

TreeSet 是 SortedSet 接口的实现类,顾名思义这是一种排序的 Set 集合。

TreeSet 底层使用 TreeMap 实现,采用红黑树的数据结构来存储集合元素。TreeSet 支持两种排序方法:自然排序和定制排序。默认情况下,使用自然排序。

自然排序

Java 提供了 Comparable 接口,接口定义了一个 compareTo(Object obj) 方法。实现该接口的类必须实现该抽象方法。

compareTo(Object obj) 比较规则如下:

  • obj1.compareTo(obj2) 返回值为 0,表明相等
  • obj1.compareTo(obj2) 返回值大于 0,表明 obj1 > obj2
  • obj1.compareTo(obj2) 返回值小于 0,表明 obj1 < obj2

TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素大小关系,再将集合元素按升序排序,这就是自然排序。所以自然排序中的元素对象都必须实现了 Comparable 接口。

定制排序

如果需要实现定制排序,需要在创建 TreeSet 集合对象时,提供一个 Comparator 对象与该 TreeSet 集合关联。Comparator 是一个函数式接口,可以使用 Lambda 表达式代替。

通过定制排序方式时,依然不可以向 TreeSet 中添加不同类型的对象,否则引发 ClassCastException 异常。此时集合判断两个元素相等的标准是:通过 Comparator 比较两个元素返回了 0, 这样 TreeSet 也不会把第二个元素添加到集合中。

import java.util.TreeSet;

public class TreeSettest4 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet((o1, o2) -> {
            M m1 = (M) o1;
            M m2 = (M) o2;
            return m1.age > m2.age ? -1 : m1.age < m2.age ? 1: 0;
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}

上面使用目标类型为 Comparator 的 Lambda 表达式,它负责 ts 集合的排序。所有 M 类无需实现 Comparable 接口,而是由 TreeSet 关联的 Lambda 表达式负责元素的排序。

在实现 compareTo 方法时,强烈推荐与 equals 结果一致,否则可能会出现一些奇怪的错误。因为有些类是根据 equals 来判断重复性,有些是利用自然排序 x.compareTo(y) == 0 来判断。compareTo 是判断元素在排序中的位置是否相等,equals 是判断元素是否相等,既然一个决定排序位置,一个决定相等,所以我们非常有必要确保当排序位置相同时,其equals也应该相等。

EnumSet 类

EnumSet 是专为枚举类设计的集合类,EnumSet 中的所有元素都必须是指定枚举类型的枚举类,该枚举类型在创建 EnumSet 时显式或隐式的的指定。

EnumSet 的集合元素是有序的,以枚举值在 Enum 类内的定义顺序来决定集合元素的顺序。EnumSet 集合不允许插入 null 元素。

EnumSet 内部以位向量的形式存储,这种存储形式紧凑高效,占用内存很小,运行效率很高。尤其是在进行批量操作时,比如调用 containsAll 和 retainAll 方法时。

Map 集合

定义:Map 用于保存具有映射关系的数据,key 和 value 之间存在单向的一对一关系,key 不允许重复。

HashMap 实现类

HashMap 中用作 key 的对象必须实现 hashCode() 方法和 equals() 方法。

与 HashSet 类似,当使用自定义类作为 HashMap 的 key 时,如果重写该类的 equals() 方法 和 hashCode() 方法,则应该保证两个方法的判断标准一致,即当两个 key 通过 equals() 方法比较返回 true 时,两个 key 的 hashCode() 方法返回值也应该相同。

与 HashSet 类似,尽量不要使用可变对象作为 HashMap 的 key,如果使用了,则尽量不要在程序中修改作为 key 的可变对象。

LinkedHashMap 实现类

LinkedHashMap 也使用双向链表来维护 key-value 对的次序(其实只需要考虑 key 的次序),该链表负责维护 Map 的迭代顺序,迭代顺序与 key-value 对的插入顺序保持一致。

import java.util.LinkedHashMap;

public class LinkedHashMapTest {
    public static void main(String[] args) {
        LinkedHashMap scores = new LinkedHashMap();
        scores.put("Chinses", 80);
        scores.put("English", 82);
        scores.put("Math", 76);
        scores.forEach((key ,value) -> System.out.println(key + "--->" + value));
    }
}

TreeMap 实现类

TreeMap 是一个红黑树数据结构,每个 key-value 对即作为红黑树的一个节点。TreeMap 存储 key-value 对节点时,需要根据 key 对节点进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态。

两种排序方式:

  • 自然排序:TreeMap 的所有 key 必须实现 Comparable 接口,而且所有的 key 应该是同一个类的对象,否则会抛出 ClassCastException 异常
  • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。采用定制排序时不要求 Map 的 key 实现 Comparable 接口

类似于 TreeSet,如果使用自定义类作为 TreeMap 的 key,为了让 TreeMap 良好的工作,则重写该类的 equals() 方法和 compareTo() 方法时应该保持一致的结果:两个 key 通过 equals 方法比较返回 true 时,它们通过 compareTo 方法比较应该返回 0。

在实现 compareTo 方法时,强烈推荐与 equals 结果一致,否则可能会出现一些奇怪的错误。因为有些类是根据 equals 来判断重复性,有些是利用自然排序 x.compareTo(y) == 0 来判断。compareTo 是判断元素在排序中的位置是否相等,equals 是判断元素是否相等,既然一个决定排序位置,一个决定相等,所以我们非常有必要确保当排序位置相同时,其equals也应该相等。

官方文档的说明:

EnumMap 实现类

EnumMap 的 key 必须是单个枚举类的枚举值。

EnumMap 具有以下特征:

  • EnumMap 在内部以数组形式保存

  • EnumMap 根据 key 的自然顺序(即枚举值在枚举类中的定义顺序)来维护 key-value 对的顺序

  • EnumMap 不能使用 null 作为 key 值

创建 EnumMap 时必须指定一个枚举类,从而将该 EnumMap 和指定枚举类相关联。

欢迎关注我的公众号

02-13 23:38