工作中遇到一个问题,我在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<Stu> stus1 = createStus(null);
List<Stu> stus2 = createStus(1);
Set<Stu> stus = new HashSet<Stu>();
stus.addAll(stus1);
stus.addAll(stus2);
for (Stu stu : stus) {
System.out.println(stu.getSage() + ".." + stu.getSid() + ".."
+ stu.getSname());
}
}
public static List<Stu> createStus(Integer type) {
if (null == type) {
List<Stu> stus = new ArrayList<Stu>();
stus.add(new Stu(23, "corleone", 1));
stus.add(new Stu(14, "corleone", 3));
return stus;
} else {
List<Stu> stus = new ArrayList<Stu>();
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<E,Object> map;
是HashMap,那控制唯一的手段应该就是通过HashMap的key-value控制key唯一来实现对象唯一的。
接着就想,如果改变了这个key,并且指定key是上述例子中Stu的sid的值的话,岂不是就可以达到想要的效果了?
接着发现:这个HashSet中的HashMap的key是HashSet的泛型入参E
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, 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
23..1..corleone
14..3..corleone
23..1..corleone
23..2..OX
再次尝试,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<Stu> stus = new LinkedHashSet<Stu>();
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());
}
}
}
运行结果如下:
2
23..20..corleone.. hashCode(): 20
23..20..corleone.. hashCode(): 20
结论是:显然易见,唯一判断属性值改变,即使是同个对象,只要hashCode()不一样就可以做为两个不同的对象。但是两个对象的所有属性全部一致,这个大家很容易想到答案,因为对象是引用类型的。
那么第一个疑点被排除,偶们是成功让sid成为key的。
那么我们再来研究疑问2
再看源码:
HashSet<E> 的add(E o)
public boolean add(E o) {
return map.put(o, PRESENT)==null;
}
在看调用的HashMap<K,V>的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<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;
}
我是细细研究了一番,是不是大家也有和我一样的想法了?既然是已经成功修改了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的被过滤在外了。
23..1..corleone
14..3..corleone
23..2..OX
PS:有个问题,这里的hashCode()返回的是int类型的,但是如果是String类型的话就没办法了。我的项目上是一个32位加密的String。呵呵……依然解决不了问题。
不过Set的问题已经解决了,希望对大家有用,希望各位多动动手。
如果有别的好方法,或者疑问,欢迎回复。偶天天要逛下javaeye的。
祝大家圣诞快乐!!!!!!!!!!!
分享到:
相关推荐
Set接口的方法与Collection接口相同,但由于它不允许重复元素,因此没有专门的方法来处理重复性。 ##### 1.5.3 实现原理 - **HashSet**:基于HashMap实现,提供不重复元素的快速访问。 - **TreeSet**:基于TreeMap...
- **AbstractList**和**AbstractSet**等抽象类进一步细化了对List和Set接口的支持,提供了部分实现,方便开发人员构建自定义列表和集合。 - **ArrayList**和**LinkedList**是List接口的实现,分别使用数组和链表...
在Go语言中,虽然没有内置的集合(Set)类型,但是可以通过自定义的方式来实现这一功能。通常,集合是一个不包含重复元素的数据结构,允许进行成员关系测试和删除操作。Go语言中的Set通常基于Map实现,因为Map提供了...
Java中的Set接口是基于集合概念实现的,它不包含重复元素。Set接口继承自Collection接口,并提供了多种实现类,如HashSet、LinkedHashSet、TreeSet和CopyOnWriteArraySet等。这些实现类各自有不同的特性和使用场景。...
Set接口的主要实现类有HashSet、LinkedHashSet和TreeSet。每个实现类都有其特性,例如: 1. HashSet:不保证元素的顺序,允许null元素,但不允许重复元素。 2. LinkedHashSet:保持元素插入的顺序,允许null元素,...
HashSet类实现了Set接口,它内部基于哈希表(HashMap)实现。HashSet在存储元素时,会调用对象的hashCode()方法计算哈希值,然后将元素存储在对应哈希桶中。由于HashSet不允许重复元素,所以当尝试添加一个已存在的...
Set接口继承自Collection,但不允许元素重复。Set接口使用内部的排序机制,确保元素的唯一性。常见的实现类有HashSet和TreeSet,其中HashSet基于哈希表实现,而TreeSet基于红黑树,保证了元素的排序特性。 3. List...
1. Set 集合:Set 接口代表不允许有重复元素的集合。Set 接口的实现类包括 HashSet、LinkedHashSet 和 TreeSet。HashSet 提供了基于哈希表的存储,插入和查找速度较快;LinkedHashSet 保持元素的插入顺序;TreeSet ...
- **`Set`接口**:继承自`Collection`,但不允许重复元素的存在。它提供了一种确保集合内所有元素都是唯一的机制。 - **`List`接口**:也继承自`Collection`,但允许重复元素并且元素是有序的。这意味着可以通过索...
5. **Set接口与SortedSet接口** - `Set`接口不保证元素的顺序,不允许元素重复。`HashSet`是基于散列的`Set`实现,`TreeSet`则是有序的,基于`Tree`实现,它要求元素实现`Comparable`接口。 - `SortedSet`接口是`...
- set和multiset:红黑树实现的集合,自动排序且不允许重复元素,前者键值唯一,后者允许重复。 - map和multimap:红黑树实现的映射,自动排序,前者键值对唯一,后者键可以重复。 - unordered_set、unordered_...
Set接口同样继承自Collection接口,但Set不允许包含重复元素,它通常用于实现数学上的集合概念。Set的两个重要实现类是HashSet和TreeSet。HashSet是基于HashMap实现的,提供了快速的查找功能;TreeSet则是基于...
- **Set接口**:继承自`Collection`接口,不允许有重复元素。每个元素都是唯一的。 - **实现类** - `HashSet`:实现了`Set`接口,使用哈希表来存储元素。提供了良好的性能,通常情况下,添加、删除和查找操作的时间...
- **不可重复性**:通过`equals()`方法判断元素是否重复。 - `Set`有两个主要实现类:`HashSet`和`TreeSet`。 - **`HashSet`**:基于哈希表的实现,元素的存储和查找速度非常快。 - **`TreeSet`**:基于红黑树的...
`Set`接口则是另一种不包含重复元素的`Collection`。每个元素在`Set`中都是唯一的,这意味着对于任意两个元素e1和e2,`e1.equals(e2)`总是返回`false`,并且最多只能有一个`null`元素。`Set`的实现类如`HashSet`、`...
`Set`接口继承自`Collection`,并且不允许元素重复。它有自己的内部排序机制,如`HashSet`和`TreeSet`。`List`接口也继承自`Collection`,但允许元素重复,并且保持元素插入时的顺序,如`ArrayList`和`LinkedList`。...
Set接口与实现 `Set`接口也是继承自`Collection`接口,但它代表一个不允许重复元素的集合。主要实现类有`HashSet`和`TreeSet`。 - **HashSet**:提供了高效的元素添加、删除和查找操作,底层使用哈希表实现。 - **...
2. **Set接口**:继承自Collection接口,它是一个不允许重复元素的集合。Set接口的主要实现类有HashSet和TreeSet,它们各自有不同的特性,如HashSet基于哈希表实现,而TreeSet则维护元素的排序。 3. **List接口**:...