工作中遇到一个问题,我在javaeye中也提问了,但是还是自己搞定了。这里和大家分享下!
假如有一个类,Stu代表学生类,有编号和姓名(sid,sname,sage)。
假如需求是查找姓名为'corleone'或者年龄为23的学生的信息。
按照两个条件查到的集合累加的话则同时满足两个条件的学生就被算了两次。显然是重复的。
写了如下的例子来模拟下
public class Stu {
private Integer sage;
private String sname;
private Integer sid;
public Stu(Integer sage, String sname, Integer sid) {
super();
this.sage = sage;
this.sname = sname;
this.sid = sid;
}
public Stu() {
super();
}
public Integer getSage() {
return sage;
}
public void setSage(Integer sage) {
this.sage = sage;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
}
这个是测试类:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Test {
public static void main(String[] args) {
List stus1 = createStus(null);
List stus2 = createStus(1);
Set stus = new HashSet();
stus.addAll(stus1);
stus.addAll(stus2);
for (Stu stu : stus) {
System.out.println(stu.getSage() + ".." + stu.getSid() + ".."
+ stu.getSname());
}
}
public static List createStus(Integer type) {
if (null == type) {
List stus = new ArrayList();
stus.add(new Stu(23, "corleone", 1));
stus.add(new Stu(14, "corleone", 3));
return stus;
} else {
List stus = new ArrayList();
stus.add(new Stu(23, "corleone", 1));
stus.add(new Stu(23, "OX", 2));
return stus;
}
}
}
显然得到的结果是有重复的。
于是想,Set可以有这样的功能,肯定是有判断重复的方法的,这里大家都想到了contains(),对不?
就看JDK源码:
JDK1.5源码如下:
public boolean contains(Object o) {
return map.containsKey(o);
}
发现Set接口下的HashSet实现类的contains()取决于map.containsKey(o);
containsKey()是不是眼熟,估计是Map的实现类。那map到底是个啥?
果然,查看结果如下
private transient HashMap map;
是HashMap,那控制唯一的手段应该就是通过HashMap的key-value控制key唯一来实现对象唯一的。
接着就想,如果改变了这个key,并且指定key是上述例子中Stu的sid的值的话,岂不是就可以达到想要的效果了?
接着发现:这个HashSet中的HashMap的key是HashSet的泛型入参E
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
这样的话,不再无从下手了。
那这个E是指什么呢?因为是唯一的,而且相同的对象是被判断为重复(无论属性是不是相同)所以我首先想到的是toString()。
这样我再次查看源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这时才明白,默认的toString是类名和hashCode()的拼接。
结论就是这个key就是hashCode()的返回结果。
尝试修改代码……Stu类重写hashCode(),使返回逻辑唯一的sid属性值
@Override
public int hashCode() {
return this.getSid();
}
testing
[list]
23..1..corleone
14..3..corleone
23..1..corleone
23..2..OX
[/list]
再次尝试,failed!重复的sid依然存在,我感觉到它在嘲笑我,还不止一个!!
于是继续振作起来,到底是哪里出问题了?
思前想后,出现这个问题的原因有两个
1、到底以上修改有没有让sid成为key?
2、如果1实现了,那又是什么原因导致依然重复的呢?
于是修改代码:
再写个测试类:
import java.util.LinkedHashSet;
import java.util.Set;
public class T {
public static void main(String[] args) {
Stu stu = new Stu(23, "corleone", 1);
Set stus = new LinkedHashSet();
stus.add(stu);
stu.setSid(20);
stus.add(stu);
System.out.println(stus.size());
for (Stu s : stus) {
System.out.println(s.getSage() + ".." + s.getSid() + ".."
+ s.getSname()+".. hashCode(): "+s.hashCode());
}
}
}
运行结果如下:
[list]
2
23..20..corleone.. hashCode(): 20
23..20..corleone.. hashCode(): 20
[/list]
结论是:显然易见,唯一判断属性值改变,即使是同个对象,只要hashCode()不一样就可以做为两个不同的对象。但是两个对象的所有属性全部一致,这个大家很容易想到答案,因为对象是引用类型的。
那么第一个疑点被排除,偶们是成功让sid成为key的。
那么我们再来研究疑问2
再看源码:
HashSet
的add(E o)
public boolean add(E o) {
return map.put(o, PRESENT)==null;
}
在看调用的HashMap的put(K key, V value)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry 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;
}
我是细细研究了一番,是不是大家也有和我一样的想法了?既然是已经成功修改了key的取值,那很大的可能就是比较的时候出问题了。于是偶就把焦点放在了
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
仔细看可以发现如果if成立,就返回oldValue,那下面的操作都不错了。大家可以根据方法名顾名思义,那么偶又把焦点方法了这个判断的条件上了。
前面的都是没什么问题的,最后,最大嫌疑犯这个称号落在了
key.equals(k)上,大家都知道,每个类都有个从Object继承下来的equals()。
于是查看源码:Object类的equals()
public boolean equals(Object obj) {
return (this == obj);
}
大家都知道==比较的都是内存地址,也许有人会有疑问那String类型呢?
不妨跑下题:String的equals()
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
偶就不多说了。
于是偶就把Stu修改了下,重写了父类的equals():
@Override
public boolean equals(Object obj) {
return obj == null ? this == null ? true : false
: obj instanceof Stu ? ((Stu) obj).getSid() == null ? this
.getSid() == null ? true : false : ((Stu) obj).getSid()
.equals(this.getSid()) : false;
}
再次尝试……success!!!同个sid的被过滤在外了。
[list]
23..1..corleone
14..3..corleone
23..2..OX
[/list]
PS:有个问题,这里的hashCode()返回的是int类型的,但是如果是String类型的话就没办法了。我的项目上是一个32位加密的String。呵呵……依然解决不了问题。
不过Set的问题已经解决了,希望对大家有用,希望各位多动动手。
如果有别的好方法,或者疑问,欢迎回复。偶天天要逛下javaeye的。
祝大家圣诞快乐!!!!!!!!!!!