HashSet与TreeSet中的等同性比较

大家好,今天和大家分享一些日常使用,但是又颇为容易采坑的代码知识,这次要和大家分享的就是HashSet和TreeSet的等同性比较,闲话少说,开始正题。
首先我们知道Set接口使我们常用的集合接口之一,在对Set进行存储时,Set会自动将重复值去除,也就是如下:

Set<String> set = new HashSet<>();
        set.add("111");
        set.add("111");

在这段代码中set中只会有一个字符串”111”,而重复元素的个数不会随着添加而变多。
但是我们来看下面的代码:

         HashSet<BigDecimal> hashSet = new HashSet<>();
         TreeSet<BigDecimal> treeSet = new TreeSet<>();
         //注意,这里传入的两个字符串
         BigDecimal decimal1 = new BigDecimal("1.0");
         BigDecimal decimal2 = new BigDecimal("1.00");

         hashSet.add(decimal1);
         hashSet.add(decimal2);
         System.out.println(hashSet.size());

         treeSet.add(decimal1);
         treeSet.add(decimal2);
         System.out.println(treeSet.size());

输出的结果是什么呢?经过输出后,hashSet的容量为2,但是TreeSet的容量为1。
我们知道,set在存值时,是依赖来等同性比较的,这么说吧,每个类都会有一个或者多个比较规则,然后set会根据自己选中的比较规则,来进行比较了,那么大家可能疑惑,我们平常的代码中并没有定义多余的比较规则啊?那么我们定义的类的默认比较规则是什么呢?答案就是equels()方法,因为Object是所有类的父类啊。
但是我们常常忽略了一种比较规则,就是实现compareable接口后需要实现的campareTo()方法,上面代码的问题就是出现在这里,简单说明一下,其实set的实现内部是依赖于Map的,HashSet的add()方法,实际上是调用了内部HashMap的put()方法,我们来看一下HashMap的put()方法:

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

在这段代码中,我们注意到put()方法对key(由hashSet传入的add()方法中的参数)值进行了hash处理,然后进行比较,首先是比较内存地址,然后进行equels比较,在这个过程中,由于我们的代码传值为BigDecimal对象,所以地址比较肯定失败,所以开始进行equels比较。相同时,就会去掉旧值,设为新值,然后回传旧值。
我们再来看一下TreeSet中的add()方法,首先TreeSet默认内部依赖的Map为TreeMap:

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

那么TreeMap的put()方法是怎么进行等同性比较的呢?

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

我们可以看到,首先是使用了内聚的caoparator的实现类进行compare比较,如果caoparator为空的话就将key转换为compareable接口的类型,进行比较,也就是说key必须实现compareable接口,所以在TreeSet中,对key的比较是依赖于camparable接口的。
通过以上分析,我们不难得到结论,如果一个类实现的equels方法和compare方法的等同性比较结果不一样,那么在两个Set中存储的内容自然也就不一样,而BigDecemal就是这样的一个情况,所以我们就看到了第一段代码的结果。
但是在《Effective JAVA》中提到:“强烈建议compareTo()方法和equels()方法实现同样的比较原则”,什么意思呢,就是说希望equels方法和compareTo方法在调用时,如果调用对象和比较对象不变的话,结果也是一样的,这样的话就可以避免在依赖于等同性比较的类中出现不同的结果,当然好处并不只有这一点,不过这也只是建议,而不是规则,如果说两者的比较结果不同,则需要明确进行说明。
好了,今天就和大家先分享这些,如有不足,请多多批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值