Set类
Set接口继承自Collection,不按特定的方式排序,并且不能重复,主要有两个实现类:HashSet和TreeSet,HashSet类按照哈希算法来存取集合中对象,存取速度比较快,HashSet类还有一个子类LinkedHashSet类,不仅实现了哈希算法,而且实现了链表数据结构,提高数据结构的插入和删除元素的性能,TreeSet类实现了SortedSet接口,具有排序功能。
看下这个程序:
package com.bin.proxy;
import java.util.HashSet;
import java.util.Set;
public class Test4 {
public static void main(String[] args){
Set<String> set = new HashSet<String>();
String s1="hello";
String s2=s1;
String s3=new String("world");
set.add(s1);
set.add(s2);
set.add(s3);
System.out.print(s2.equals(s1));
System.out.print(set.size());
}
}
把s2 的对象引用赋值给s1,则2个位同一个引用,值是相同的。Set采用equals()比较对象是否相等。而不是“==”。
HastSet类
我们先从HashSet源码看起:
下面是HashSet的定义:
1 public classHashSet<E>
2 extendsAbstractSet<E>
3 implementsSet<E>, Cloneable, java.io.Serializable
HashSet继承了AbstractSet,实现了Set接口。其实AbstractSet已经实现Set接口了。AbstractSet继承自AbstractCollection,而AbstractCollection实现了Collection接口的部分方法,而Set接口和Collection接口完全一致,所以AbstractSet只是实现了AbstractCollection没有实现的Set接口的方法和重写了部分AbstractCollection已经实现的方法。AbstractSet实现了equql()、hashCode()、removeAll()功能的重写。
再看HashSet
下面是HashSet定义的属性:
1 private transient HashMap<E,Object> map;
2 private static final Object PRESENT = new Object();
想一下HashMap有什么特点:基于哈希表,存储键值对,Key不能相同等等。Key不能相同!这个特点是不是和Set的元素不能相同和类似?如果将Set的元素当成Map的Key,是否就保证了元素的不重复?!答案是肯定的。但是Map存储键值对,Key有了,那么Value呢?这正是第二个属性PERSENT的意义。看到PERSENT属性时一个Object对象,且是static和final的,它的用途就是当做Value存进map中。
总结一下,HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。
这样看来HashSet应该很简单,应该只是使用了HashMap的部分内容来实现。
看下add()方法源码 ,即是通过HashMap实现,
public boolean add(E e) {
returnmap.put(e,PRESENT)==null;
}
addAll方式是在抽象类AbstractCollection中实现,HashSet通过继承拥有该方法,采用循环插入实现。
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
iterator()
public Iterator<E> iterator() {
returnmap.keySet().iterator();
}
很清楚了,返回的是HashMap中KeySet的迭代器。
size()
public int size() {
return map.size();
}
size()方法同样返回的是map的大小,所以HashSet根本就没定义size属性。
public boolean isEmpty() {
return map.isEmpty();
}
既然size()用的是map的大小,那么isEmpty()自然也是判断map。
public boolean contains(Object o) {
return map.containsKey(o);
}
public voidclear() {
map.clear();
}
public Objectclone() {
try {
HashSet<E> newSet =(HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
这几个方法就不解释了。
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
remove(Object o)为什么还要判断结果呢?因为通过HashSet存入的元素,所对应的Value值都是PERSENT,如果传入的o不存在,map的remove方法返回为null,则对应的结果是HashSet的remove操作应该放回false,所以这里根据返回的结果判断是否移除成功。
在Object类中定义了hashCode()和equals()方法,equals()方法按照内存地址比较对象是否相等,因此如果object1.equals(object2)为true,则表明两个变量引用同一个对象,其哈希码也肯定相等。如果用户定义一个类覆盖了Object类的equals()方法,但没有覆盖Object类的hashCode()方法,就会会导致,当object1.equals(object2)为true,而哈希码不一定一样,导致HashSet或HashMap无法正常工作。
package com.bin.proxy;
class Customer {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean equals(Object o){
if(this ==o)returntrue;
if((o instanceof Test5))returnfalse;
final Customer test=(Customer)o;
if(this.name.equals(test.getName())&&this.age==test.getAge())
return true;
else
return false;
}
public int hashCode(){
int result;
result=(name ==null?0:name.hashCode());
result = 29*result+age;
return result;
}
}
public class Test5{
public static void main(String[] args){
Customer t1= new Customer();
Customer t2= new Customer();
t1.setAge(4);
t1.setName("song");
t2.setAge(4);
t2.setName("song");
System.out.print(t1.equals(t2));
}
}
如上程序,若把hashCode()去掉,则在HashSet和HashMap代表不同的对象。
四、TreeSet类
其构造方法为:
构造方法摘要 | |
TreeSet() 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。 |
|
TreeSet(Collection<? extendsE> c) 构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。 |
|
TreeSet(Comparator<? superE> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。 |
|
TreeSet(SortedSet<E> s) 构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。 |
|
例程:
Set<Integer>set =new TreeSet<Integer>();
set.add(newInteger(8));
set.add(new Integer(7));
set.add(new Integer(9));
则程序打印set时显示 6 7 8 9。
TreeSet支持两种排序方式:自然排序和客户化排序,默认采用自然排序
1、 自然排序
在JDK中有个Comparable接口,部分类实现了他,如Integer,Double和String等,其有个接口compareTo(Object o)方法,x.compareTo(y),返回0,表示相等,若大于0表示x大于y,反之小于0,表示x小于y。
TreeSet调用compareTo()方法比较集合对象的大小,然后进行升序排序,成为自然排序,使用自然排序的时候,只能想TreeSetJ集合加入同类型的对象,这些对象必须实现了Comparable接口,
2、 客户化排序
除了自然排序,TreeSet还支持客户化排序,java.util.Comparator<Type>接口提供具体排序方式,Comparator有个compare(Type x,Type y)方法,用于比较对象大小,当返回大于0时,表示x大于y,当放回小于0时,表示x小于y,当返回等于0时,表示x等于y
package com.bin.jihe;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class CustomerComparator implements Comparator<Customer>{
public static void main(String[] args) {
Set<Customer> set=new TreeSet<Customer>(newCustomerComparator());
set.add(new Customer(11,"tom"));
set.add(new Customer(12,"jim"));
set.add(new Customer(10,"ljcy"));
Iterator <Customer>it=set.iterator();
while(it.hasNext()){
Customer customer=it.next();
System.out.println(customer.getAge()+"-"+customer.getName());
}
}
@Override
public int compare(Customer o1, Customer o2) {
if(o1.getName().compareTo(o2.getName())>0)return 1;
if(o1.getName().compareTo(o2.getName())<0)return -1;
return 0;
}
}