Set接口概述
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
Set实现类之一:HashSet
1 HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
2 HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet 具有以下特点:
不能保证元素的排列顺序
HashSet 不是线程安全的
集合元素可以是 null
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
Set实现类之一:HashSet
向HashSet中添加元素的过程:
1 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法
来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
2 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
重写 hashCode() 方法的基本原则
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
- 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
重写 equals() 方法的基本原则
以自定义的Customer类为例,何时需要重写equals()? 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是
要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
结论:复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
Set实现类之二:LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
- LinkedHashSet 不允许集合元素重复。
Set实现类之三:TreeSet
- TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
- TreeSet底层使用树结构存储数据
- 新增的方法如下: (了解)
1 Comparator comparator()
2 Object first()
3 Object last()
4 Object lower(Object e)
5 Object higher(Object e)
6 SortedSet subSet(fromElement, toElement) 7SortedSet headSet(toElement)
8SortedSettailSet(fromElement)
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序
排 序—自然排序
自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。
向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添
加的所有元素都会调用compareTo()方法进行比较。
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值。
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与compareTo(Object obj) 方法有一致的结果:如果两个对象通过equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解。
排 序—定制排序
TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没
有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通Comparator接口来实现。需要重写compare(T o1,T o2)方法。
利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。
使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
package Java1;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
* 向TreeSet中添加数据,要求是相同类的对象
* 两种排序方式:自然排序(实现comparable接口)和定制排序(Comparator)
*
* 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals().
*/
public class TreeTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
// set.add(123);
// set.add(345);
// set.add("afafda");
// 添加失败:不能添加不同类的对象
//举例一
// set.add(123);
// set.add(0);
// set.add(-8);
// set.add(34);
// set.add(76);
// set.add(353);
//举例二
set.add(new User("tom",12));
set.add(new User("tom",34));
set.add(new User("eom",89));
set.add(new User("adm",92));
set.add(new User("ghm",16));
Comparator com = new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User&&o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(), u2.getAge());
}else{
throw new RuntimeException("类型不匹配");
}
}
};
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
User中的comareTo方法
//按照姓名从小到大排序
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
//return this.name.compareTo(user.name);
int compare = this.name.compareTo(user.name);
if(compare!=0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("类型不匹配!!");
}
}