java 集合类 之 Set

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的元素当成MapKey,是否就保证了元素的不重复?!答案是肯定的。但是Map存储键值对,Key有了,那么Value呢?这正是第二个属性PERSENT的意义。看到PERSENT属性时一个Object对象,且是staticfinal的,它的用途就是当做Value存进map中。

     总结一下,HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMapKey中,而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();
 }


     很清楚了,返回的是HashMapKeySet的迭代器。

      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不存在,mapremove方法返回为null,则对应的结果是HashSetremove操作应该放回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;
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值