Set 集合的功能和Collection 是一致的,它没有提供任何额外的方法。实际上就是Collection ,只是行为不同(Set 不允许包含重复元素)。
Set 集合的特点:存入的元素的是无序的,即存入和取出的顺序不一定不一致,且元素不可以重复。
1 public class SetText { 2 public static void main(String[] args) 3 { 4 Set books = new HashSet() ; 5 // 添加一个字符串对象 6 books.add(new String("笑傲江湖")) ; 7 // 添加另一的字符串对象,打印返回值 8 System.out.println(books.add(new String("笑傲江湖"))); 9 // 打印Set集合中的元素。 10 System.out.println(books); 11 } 12 }
Set 不允许包含相同的元素,所以在添加元素时,会用equals 方法进行判断,如果比较返回true则表示两对象相等,所以add 方法会返回false,表示添加失败。
一:HashSet 类
HashSet 类是 Set 接口的实现类。其底层数据结构是哈希表,所以具有很好的存取和查找性能。
1.1、HashSet 是如何保证元素唯一性的?
当向HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的HashCode值,然后根据该HashCode值来决定该对象在HashSet 中存储的位置。如果两个HashCode值不相等,则存入元素。(不会调用equals() 方法 。)
如果两个HashCode值相等,则调用equals 方法进行判断,如果两个元素通过equals 方法比较返回true ,则添加失败,否则添加成功。
所以:HashSet 集合判断两个元素相等的标准是两个对象通过equals 方法比较,并且两个对象的hashCode() 方法返回值也相等。在定义类时,如果要用到集合,则要重写hashCode() 和 equals() 两个方法。
注意:重写hashCode() 方法的基本规则:
1、当两个对象通过equals 方法比较返回true 时,这两个对象的HashCode 应该相等。
2、对象中用作equals 比较的标准属性,都应该用来计算HashCode值。
不同类型属性获取HashCode值的方式:f表示对象中每一个有意义的属性。
属性类型 | 计算方式 |
boolean | HashCode = (f?0:1); |
整数型 | hashCode = (int)f |
long | hashCode = (int)(f^(f>>>32)) |
float | hashCode = Float.floatToIntBits(f) |
double |
long l = Double.doubleToLongBits(f) hashCode = (int)(l^(l>>>32)) |
普通引用型 | hashCode = f.hashCode() |
1 class Person 2 { 3 private String name ; 4 private String id ; 5 public Person(String name , String id) 6 { 7 this.name = name ; 8 this.id = id ; 9 } 10 // get方法 11 public String getName() 12 { 13 return name ; 14 } 15 public String getId() 16 { 17 return id ; 18 } 19 // 重写equals 方法 20 public boolean equals(Object obj) 21 { 22 // 23 if (!(obj instanceof Person)) 24 return false ; 25 Person p = (Person) obj ; 26 return this.name.equals(p.name) && this.id.equals(p.id) ; 27 } 28 // 重写 hashCode 方法 29 public int hashCode() 30 { 31 return name.hashCode()+id.hashCode() ; 32 } 33 } 34 public class HashSetDemo { 35 public static void main(String[] args) 36 { 37 HashSet hs = new HashSet() ; 38 hs.add(new Person("张三" , "110110")) ; 39 hs.add(new Person("李四" , "120110")) ; 40 hs.add(new Person("张三" , "123110")) ; 41 hs.add(new Person("张三" , "110110")) ; 42 Iterator it = hs.iterator() ; 43 while(it.hasNext()) 44 { 45 Person p = (Person) it.next() ; 46 System.out.println("name:"+p.getName() + "," + "id:"+p.getId() ); 47 } 48 } 49 }
上面代码的判断两个Person 对象是否相等的条件是: name 和 id 都相等。
1.2、关于hashCode() 方法对于HashSet 的作用:
hash 算法功能:它能保证通过一个对象快速查找到另一个对象。其价值在于速度,它可以保证查询得到快速执行。当需要集合中的某个集合中某个元素时,hash算法可以直接根据元素的值得到该元素保存在何处,从而可以让程序快速找到该元素。简单来来说HashCode值 类似数组中的索引。
而不直接使用数组的原因:因为数组的索引是连续的,长度是固定的,无法自由增加数组的长度,而HashSet 采用每个元素的HashCode 值作为索引,可以自由增加HashSet 长度。而且因为长度不是固定的,索引不是连续的,所以HashSet 存储和删除都是比较高效的。
1.3、HashSet 判断和删除的依据:
调用hashCode() 方法判断对象的hashCode值是否在集合中存在,如果不存在则返回false ;否则再调用equals () 方法进行判断。
二.TreeSet 类
2.1 TreeSet 是如何保证元素唯一性的?
当把一个对象添加到TreeSet时,如果是第一个,不会进行任何判断;当添加第二对象时,TreeSet 就会调用对象的compareTo(Object o) 方法与集合其他元素进行比较,通过comparaTo 方法返回的值来进行判断,如果compareTo 方法返回的是值是0, 这表示集合中存在该元素;如果返回的是1这表示该对象大于比较的对象;如果返回的是-1则表示该对象小于比较的对象。
所以,如果试图把一个对象添加进TreeSet,则该对象必须实现Comparable 接口否则程序将会抛出异常---ClassCastException。而且向TreeSet 中添加的应该是同一类对象。
TreeSet 类可以确保元素集合处于排序状态,这排序状态不是根据元素的插入顺序进行排序的,而是根据元素的实际值来排序的。
1 public class TreeSetText { 2 public static void main(String[] args) 3 { 4 TreeSet ts = new TreeSet() ; 5 ts.add("adsfdf") ; 6 ts.add("refdas") ; 7 ts.add("vceqw") ; 8 ts.add("asdfe") ; 9 System.out.println(ts); 10 } 11 }
输入结果:
[adsfdf, asdfe, refdas, vceqw]
与HashSet 集合采用的hash 算法来确定元素的存储的位置不同,TreeSet 采用二叉树的数据结构对元素进行排序。
TreeSet排序方式用两种:
1、自然顺序排序。
2、定义比较器排序,即制定排序。
2.1、自然排序:
让元素自身具备比较性,元素需要实现Comparable 接口,覆盖compareTo 方法。
> int compareTo(Object o) :比较对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分为负整数、0或正整数。
1 class Student implements Comparable 2 { 3 // 当两个对象的name 和 id 都相等时,表示是同一个人。 4 private String name ; 5 private long id ; 6 public Student(String name , long id) 7 { 8 this.name = name; 9 this.id = id ; 10 } 11 // get方法 12 public String getName() 13 { 14 return name ; 15 } 16 public long getId() 17 { 18 return id ; 19 } 20 // 覆盖comparaTo 方法 21 // 按找id来排序。 22 public int compareTo(Object obj) 23 { 24 if (obj instanceof Person) 25 throw new RuntimeException() ; 26 Student p = (Student) obj ; 27 if(this.id > p.id ) 28 return 1 ; 29 if(this.id == p.id ) 30 { 31 return this.name.compareTo(p.name) ; 32 } 33 return -1 ; 34 } 35 public boolean equals(Object obj) 36 { 37 Student p = (Student) obj ; 38 return this.name.equals(p.name)&& p.id == this.id ; 39 } 40 }
当Student 对象添加进TreeSet 时, 会调用Student 类的compareTo(Object o) 方法来进行判断要添加的对象是否在集合中已经存在,如果存在则不再添加;如果不存在,则根据compareTo 方法的返回值来进行排序。
2.3 定义比较器(定制排序)
当对象自身不具备比较性时,或者具备的比较性不是所需要的,这时候就需要让集合自身具备比较性。如:TreeSet 的自然排序是根据集合元素的大小,TreeSet 将它们以升序排序的,如果要定制需要的排序,例如以降序排序,则可以通过定义比较器的方法让集合自身具备比较性。
如何定义比较器:
定义一个类,实现Comparator 接口,并且覆盖compare() 方法。然后将比较器对象作为参数传递给TreeSet 集合的构造器。
> int compare(Object o1 , Object o2) 比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
注意:其实Comparator 接口除了compare 方法外,还有equals(Object o),由于定义的类继承了Object 类, 所以不需要覆盖equals 方法了。
1 class Student implements Comparable 2 { 3 // 当两个对象的name 和 id 都相等时,表示是同一个人。 4 private String name ; 5 private long id ; 6 public Student(String name , long id) 7 { 8 this.name = name; 9 this.id = id ; 10 } 11 // get方法 12 public String getName() 13 { 14 return name ; 15 } 16 public long getId() 17 { 18 return id ; 19 } 20 // 覆盖comparaTo 方法 21 // 按找id来排序。 22 public int compareTo(Object obj) 23 { 24 if (obj instanceof Person) 25 throw new RuntimeException() ; 26 Student p = (Student) obj ; 27 if(this.id > p.id ) 28 return 1 ; 29 if(this.id == p.id ) 30 { 31 return this.name.compareTo(p.name) ; 32 } 33 return -1 ; 34 } 35 public boolean equals(Object obj) 36 { 37 Student p = (Student) obj ; 38 return this.name.equals(p.name)&& p.id == this.id ; 39 } 40 } 41 public class TreeSetTest { 42 public static void main(String[] args) 43 { 44 TreeSet ts = new TreeSet( new Comparator() 45 { 46 // 定义比较器:按id的降序排列 47 public int compare(Object o1 , Object o2) 48 { 49 if ((o1 instanceof Person) &&(o2 instanceof Person)) 50 throw new RuntimeException() ; 51 Student p = (Student) o1 ; 52 Student s = (Student) o2 ; 53 if(p.getId() > s.getId() ) 54 return -1 ; 55 if(p.getId() == s.getId() ) 56 { 57 return p.getName().compareTo(s.getName()) ; 58 } 59 return 1 ; 60 } 61 }) ; 62 63 ts.add(new Student("hezuoan001",12)) ; 64 ts.add(new Student("hezuoan005",11)) ; 65 ts.add(new Student("hezuoan003",1)) ; 66 ts.add(new Student("hezuoan004",4)) ; 67 ts.add(new Student("hezuoan002",11)) ; 68 69 Iterator it = ts.iterator() ; 70 while(it.hasNext()) 71 { 72 Student s = (Student) it.next() ; 73 System.out.println("name:"+s.getName()+" id:"+s.getId()); 74 } 75 } 76 }
当没有定义比较器时(第44行没有向TreeSet 集合构造器传递参数 )打印的结果如下:
name:hezuoan003 id:1 name:hezuoan004 id:4 name:hezuoan002 id:11 name:hezuoan005 id:11 name:hezuoan001 id:12
定义比较器时,且在Student 类实现了Comparable 接口并覆盖了compareTo(Object o) 方法的情况下,打印的结果如下:
name:hezuoan001 id:12 name:hezuoan002 id:11 name:hezuoan005 id:11 name:hezuoan004 id:4 name:hezuoan003 id:1
所以,当两种排序都存在时,以比较器为主。
注意:当通过Comparator 对象来实现TreeSet 定制排序时,依然不可以向TreeSet 中添加类型不同的对象。
2.4 练习:按字符串的长度排序:
字符串本身具备比较性:字符串实现了Comparable 接口,并且覆盖了CompareTo(Object obj) 方法,其比较是按自然循序排序的。而我们想要的排序是按字符串的长度来排序,所以这时候只能使用比较器。代码如下:
1 public class ComparatorTest { 2 public static void main(String[] args) 3 { 4 // 定义比较器:按字符串的长度排序 5 TreeSet ts = new TreeSet(new Comparator() 6 { 7 public int compare(Object o1 , Object o2) 8 { 9 String s1 = (String) o1 ; 10 String s2 = (String) o2 ; 11 // 定义 Integer 对象来进行 s1 和 s2 长度的比较。 12 int num = new Integer(s1.length()).compareTo(new Integer(s2.length())); 13 if (num == 0) 14 { 15 return s1.compareTo(s2) ; 16 } 17 return num ; 18 } 19 }) ; 20 ts.add("adfwefad"); 21 ts.add("adfadsfwefawefad"); 22 ts.add("adfwfadfaefad"); 23 ts.add("adfwedfafad"); 24 ts.add("adfwedfasfad"); 25 for (Object obj : ts) 26 { 27 System.out.println(obj); 28 } 29 } 30 }
总结:
Java 的一些常用类已经实现了 Comparable 接口,并提供了比较大小的标准。下面是实现了Comparable 接口的常用类:
> 所有数值类型对应的包装类:Integer、Double等,按他们的数值进行大小比较。
> Character : 按字符的UNICODE 值进行比较。
> Boolean : true 对应的包装类实例大于false 对应的包装实例。
> String : 按字符串中字符的UNICODE 值进行比较。
> Date、Time : 后面的时间、日期比前面的时间、日期大。
2.5 TreeSet 提供的额外方法:
因为TreeSet 中的元素是有序的,所以增加了访问第一个、前一个、后一个、最后一个元素的方法,并且提供了三个从TreeSet 中截取子TreeSet 的方法。如下:
> Object first() : 返回第一个元素。
> Object last() : 返回最后一个元素。
> Object lower(Object o) : 返回集合中位于指定元素之前的元素。
> Object higher(Object o) : 返回集合中位于指定元素之后的元素。
> SortedSet subSet(fromElement , toElement) : 返回此Set 的子集合 ,范围从fromElement(包含)到toElement(不包含)。
> SortedSet headSet(toElement) : 返回此Set 子集合,由小于toElement 的元素组成。
> SortedSet tailSet(fromElement) : 返回次Set 子集合,有大于fromElement 的元素组成。