在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  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());
}
}

下面是结果:

redis set java Redis set Java 对象去重_java

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

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

* Constructs a new, empty set; the backing HashMap
* 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  [] tab;
Node  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  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  ) 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  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());
}
}

下面是结果:

redis set java Redis set Java 对象去重_java set 对象去重_02

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 
{
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  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());
}
}

下面是结果:

redis set java Redis set Java 对象去重_java set 对象去重_03