黑马程序员-JAVA基础-Java 集合之Set 接口

时间:2021-02-13 17:54:37

------- android培训java培训、期待与您交流! ----------

  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 的元素组成。