HashSet/LinkedHashSet/TreeSet

Collection 系列文章的总目录:

HashSet

HashSet 内部是直接使用 HashMap 来实现的

是一种变体的装饰器模式

实现原理:

添加元素:

add() :

  • 元素放到 HashMap 的 keyvalue 放入一个常量 PRESENT

删除元素:

remove():

  • 从 HashMap 中 remove,并判断 value 是否等于 PRESENT
private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

问题:

Q:突然发现,很多集合的方法,入参都是使用 Object,而不是泛型,这是为什么?

  1. 集合内部依赖 Object.equals() 方法,而 Object.equals() 方法接收的参数类型是 Object 类型。Object子类如果重写了equals()方法的话,并没有要求参数类型和当前类类型一定要相同才返回true。
  2. 使用类似 Set<? extends Foo> set 这样的变量,除非传 null,否则编译器都会报错。因为 ? extends Foo 不是一个确定的类型,任何确定的类型,即使是Foo的子类,该方法都不能接收,因为编译期不能确定这个Foo的子类就一定是创建set时指定的那个类型。

Q:那为什么 add() 的时候需要使用泛型?

  1. 要保证集合内保存的元素类型
  2. 那些使用 Object 类型的方法,都是使用外部传入的对象去和集合内的元素做比较,而这并不强制要求传入的对象类型要求一样,只需要能和集合中的元素进行比较即可。

参考:为什么Map.containsKey()方法的参数类型是Object?

LinkedHashSet

LinkedHashSet 直接继承 HashSet

实现原理:

  • HashSet提供了一个构造,可以指定使用LinkedHashMap 作为实现
  • 所以 LinkedHashSet 只需要调用这个构造即可
// dummy只是用来区别不同的构造,本身并没用
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

TreeSet:

TreeSet 也是直接使用 TreeMap

实现原理:

  • 和 HashSet 类似,就不再赘述了
  • 内部持有的是 NavigableMap 类型,就不是 TreeMap 类型
private transient NavigableMap<E,Object> m;

private static final Object PRESENT = new Object();

public TreeSet() {
    this(new TreeMap<E,Object>());
}
posted @ 2020-03-30 00:57  demo杰  阅读(261)  评论(0编辑  收藏  举报