《疯狂Java讲义》(二十四)---- Set集合

  • HashSet类

  HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode方法返回值也相等。当向HashSet集合中存入一个元素时,HashSet会调用对象的hashCode方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals方法返回true,但是hashCode方法返回不同的值,HashSet会把这两个对象存入不同的位置,不会把它们当作同一个对象。

package com.ivy.collection;

import java.util.HashSet;

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

class B {
    public int hashCode() {
        return 1;
    }
}

class C {
    public boolean equals(Object obj) {
        return true;
    }
    
    public int hashCode() {
        return 2;
    }
}
public class HashSetDemo {

    public static void main(String[] args) {
        HashSet set = new HashSet();
        set.add(new A());
        set.add(new A());
        set.add(new B());
        set.add(new B());
        set.add(new C());
        set.add(new C());
        System.out.println(set);

    }

}

输出结果

[com.ivy.collection.B@1, com.ivy.collection.B@1, com.ivy.collection.C@2, com.ivy.collection.A@e3f6d, com.ivy.collection.A@a4b78b]

两个A,两个B是不同的,但是两个C是同一个对象。

所以,当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals方法,也应该重写其hashCode方法。其规则是:如果两个对象通过equals方法比较返回true,那这两个对象的hashCode值也应该相同。对象中用作equals方法比较标准的Field,都应该用来计算hashCode值。

当向HashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确访问该对象。

  • LinkedHashSet类

  LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet。

  • TreeSet类

  TreeSet可以确保机荷元素处于排序状态。TreeSet支持两种排序方法:自然排序和定制排序。

  1. 自然排序

    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。Java提供了一个Comparable接口,该接口中定义了一个compareTo(Object obj)方法,实现了该接口的类的对象就可以比较大小。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。

    对于TreeSet而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo方法比较是否返回0,如果返回0,TreeSet会认为它们相等,不会被添加进TreeSet。

    例如:

    向TreeSet添加两个SomeClass对象A和B,添加A时,因为TreeSet中没有任何元素,所以可以添加进去,但是当添加B时,TreeSet会调用B.compareTo(Object obj)与TreeSet中其他元素比较,如果SomeClass没有实现Comparable接口,则会引发ClassCastException。

    当需要把一个对象放入TreeSet中,重写该对象对应类的equals方法时,应保证该方法与compareTo方法有一致的结果,其规则是:如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo方法比较应该返回0。

    推荐HashSet和TreeSet中只放入不可变对象,因为如果向TreeSet中添加一个可变对象,并且后面程序修改了该对象的Field,这将导致它与其他对象的大小发生改变,但TreeSet不会再次调整它们的顺序,甚至可能导致TreeSet中保存的这两个对象通过compareTo方法比较返回0。

  2. 定制排序

    如果需要实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。Comparator接口包含一个int compare(Object o1, Object o2)方法,用于比较o1和o2的大小。

import java.util.Comparator;
import java.util.TreeSet;

class M {
    int age;
    public M(int age) {
        this.age = age;
    }
    public String toString() {
        return "M[age:" + age + "]";
    }
}
public class TreeSetDemo {

    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new Comparator() {
            public int compare(Object o1, Object 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);
    }

}

输出结果:

[M[age:9], M[age:5], M[age:-3]]

  • EnumSet类

  EnumSet没有提供构造器来创建EnumSet,程序应该通过它提供的static方法来创建。例如:

import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;

enum Season {
    SPRING, SUMMER, FALL, WINTER
}
public class EnumSetDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        EnumSet es1 = EnumSet.allOf(Season.class);
        System.out.println(es1);
        
        EnumSet es2 = EnumSet.noneOf(Season.class);
        System.out.println(es2);
        
        es2.add(Season.WINTER);
        es2.add(Season.SPRING);
        System.out.println(es2);
        
        EnumSet es3 = EnumSet.of(Season.SUMMER, Season.WINTER);
        System.out.println(es3);
        
        EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
        System.out.println(es4);
        
        EnumSet es5 = EnumSet.complementOf(es4);
        System.out.println(es5);
        
        Collection c = new HashSet();
        c.clear();
        c.add(Season.FALL);
        c.add(Season.SPRING);
        EnumSet es6 = EnumSet.copyOf(c);
        System.out.println(es6);
        c.add("Java");
        c.add("C#");
        es6 = EnumSet.copyOf(c);
    }

}

输出结果:

[SPRING, SUMMER, FALL, WINTER]
[]
[SPRING, WINTER]
[SUMMER, WINTER]
[SUMMER, FALL, WINTER]
[SPRING]
[SPRING, FALL]
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum
at java.util.EnumSet.copyOf(EnumSet.java:168)
at com.ivy.collection.EnumSetDemo.main(EnumSetDemo.java:41)

最后一行抛出异常,原因是当复制Collection集合中的所有元素来创建EnumSet时,要求Collection集合中的所有元素必须是同一个枚举类的枚举值。

  • 各Set实现类的性能分析

   HashSet vs TreeSet

  HashSet的性能总是比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合次序。

  只有当需要一个保持排序的Set时,才应该使用TreeSet.

  HashSet vs LinkedHashSet

  对于普通的插入删除操作,LinkedHashSet比HashSet要慢,这是由维护链表所带来的额外开销造成的。

  但因为有了链表,遍历LinkedHashSet会更快。

  EnumSet是所有set中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。

  Set的三个实现类HashSet/TreeSet/EnumSet都是线程不安全的。通常可以通过如下方法来包装Set集合,手动保证该Set集合的同步性:

  SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));

原文地址:https://www.cnblogs.com/IvySue/p/6376653.html