java对set对象进行去重,java中set如何实现去重?

在java当中我们经常会遇到set对象去重的问题,那么这应该如何实现呢?有很多人表示不大清楚实现方式,下面一起来看看吧。

set集合是没有重复数据的特性应该都很清楚吧,那么,对于元素为对象的情况是不是也是一样有效果呢?

来看一下下面的例子:

SetTest.java:
class VO
{
    private String name;
    private String addr;
    public VO(String name, String addr)
    {
        this.name = name;
        this.addr = addr;
    }
    @Override
    public String toString()
    {
        return "name: " + name + " addr:" + addr;
    }
}
@Test
public void testSet()
{
    Set < VO > vos = new HashSet < > ();
    VO vo = new VO("wahaha", "sh");
    VO vo1 = new VO("wahaha", "bj");
    VO vo2 = new VO("wahaha", "sh");
    vos.add(vo);
    vos.add(vo1);
    vos.add(vo2);
    for (VO item: vos)
    {
        System.out.println(item.toString());
    }
}

下面是结果:

java对set对象进行去重

可以看出,对于各个字段值都相同的对象,并没有做去重操作,那么这又是什么原因呢?

我们再来看看JDK1.8当中,HashSet的数据结构,HashSet.java,实例化对象:

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet()
{
    map = new HashMap < > ();
}

new HashSet()操作实际上是new HashMap<>(),所以说,底层是以HashMap来实现的。

HashSet.add方法

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

HashMap.add方法

public V put(K key, V value)
{
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent
    , boolean evict)
{
    Node < K, V > [] tab;
    Node < K, V > p;
    int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize())
        .length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else
    {
        Node < K, V > e;
        K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode < K, V > ) p)
            .putTreeVal(this, tab, hash, key, value);
        else
        {
            for (int binCount = 0;; ++binCount)
            {
                if ((e = p.next) == null)
                {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null)
        { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

判断插入的key是不是存在,我们要判断两点,一个是hash值是不是相同,第二个就是对应的值是不是相同。

前者看hashCode()方法,后者看equal()方法。

接下来,一起来了解一下基本的数据类型和自定义类类型在计算hashCode和equal的区别。

代码:

class VO
{
    private String name;
    private String addr;
    public VO(String name, String addr)
    {
        this.name = name;
        this.addr = addr;
    }
    @Override
    public String toString()
    {
        return "name: " + name + " addr:" + addr;
    }
}
@Test
public void testSet()
{
    Set < VO > vos = new HashSet < > ();
    VO vo = new VO("wahaha", "sh");
    VO vo1 = new VO("wahaha", "bj");
    VO vo2 = new VO("wahaha", "sh");
    Integer a = 2;
    Integer b = 2;
    String str1 = new String("abc");
    String str2 = new String("abc");
    System.out.println(a.equals(b));
    System.out.println(str1.equals(str2));
    System.out.println(vo.equals(vo2));
    System.out.println(a.hashCode() == b.hashCode());
    System.out.println(str1.hashCode() == str2.hashCode());
    System.out.println(vo.hashCode() == vo2.hashCode());
    vos.add(vo);
    vos.add(vo1);
    vos.add(vo2);
    for (VO item: vos)
    {
        System.out.println(item.toString());
    }
}

下面是结果:

java对set对象进行去重

java.lang.Integer.equals():两个对象对应的值一致则返回true。

public boolean equals(Object obj)
{
    if (obj instanceof Integer)
    {
        return value == ((Integer) obj)
            .intValue();
    }
    return false;
}

java.lang.String.equals():两个字符串对应的值一致,那么返回true:

public boolean equals(Object anObject)
{
    if (this == anObject)
    { //同一个对象,必定是一致的
        return true;
    }
    if (anObject instanceof String)
    {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length)
        { //对比每一个字符
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0)
            {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false; //anObject不是String实例,那么返回false
}

java.lang.Object.equals():两个对象的引用是否一致,也就是两个的对象是不是同一个。

public boolean equals(Object obj)
{
    return (this == obj);
}

对于java.lang.Object.equals()来说,两个new出来的对象铁定是不一致的,所以,在HashMap数据结构中不会被判定成相同的对象。

再来看一下hashCode源码:

java.lang.Integer.hashCode():
    @Override
public int hashCode()
{
    return Integer.hashCode(value);
}
public static int hashCode(int value)
{
    return value;
}
java.lang.String.hashCode():
    public int hashCode()
    {
        int h = hash;
        if (h == 0 && value.length > 0)
        {
            char val[] = value;
            for (int i = 0; i < value.length; i++)
            {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
java.lang.Object.hashCode():
    public native int hashCode();

JDK8的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift schema随机数算法得到的一个随机数。

所以的话,能够看到Integer以及String也是依据具体的value值来计算hashCode。

所以尽管两个引用不同但是值相同的对象,依然是想等的,可是Object则不同了。

重载VO类的equals和hashCode方法:

SetTest.java:
class VO
{
    private String name;
    private String addr;
    public VO(String name, String addr)
    {
        this.name = name;
        this.addr = addr;
    }
    @Override
    public String toString()
    {
        return "name: " + name + " addr:" + addr;
    }
    /**
     * 如果对象类型是User,先比较hashcode,一致的场合再比较每个属性的值
     */
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
            return false;
        if (this == obj)
            return true;
        if (obj instanceof VO)
        {
            VO vo = (VO) obj;
            // 比较每个属性的值 一致时才返回true
            if (vo.name.equals(this.name) && vo.addr.equals(this.addr))
                return true;
        }
        return false;
    }
    /**
     * 重写hashcode 方法,返回的hashCode不一样才再去比较每个属性的值
     */
    @Override
    public int hashCode()
    {
        return name.hashCode() * addr.hashCode();
    }
} // class
@Test
public void testSet()
{
    Set < VO > vos = new HashSet < > ();
    VO vo = new VO("wahaha", "sh");
    VO vo1 = new VO("wahaha", "bj");
    VO vo2 = new VO("wahaha", "sh");
    vos.add(vo);
    vos.add(vo1);
    vos.add(vo2);
    for (VO item: vos)
    {
        System.out.println(item.toString());
    }
}

下面是结果:

java对set对象进行去重

java set对象去重的方法你都了解了吗?对于java set你还有什么想要了解的吗?更多java常见问题及解决方法请继续关注奇Q工具网来进行了解吧。

推荐阅读:

java中set方法实例,java中set用法

java List和Set的区别是什么?有什么区别?

hashmap的实现原理和hashset的实现原理是什么?