TreeSet这个类的特点是:使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的Comparator
进行排序,具体取决于使用的构造方法。
一、按自然顺序排列
我们定义一个employee类,重写equal和hashcoed方法,并重写compareto方法,hsahcode和equal方法是用来让set比较两个元素是否相同,进而决定是否把他添加进集合;而这个类的重写的compareto方法则是决定了这个类的自然顺序,如果没有指定compartor,在add的时候,就会按照这个自然添加进set
在第一个例子中我们用public TreeSet()无参构造方法,先看一下API(jdk1.6)介绍的它的使用特点:
public TreeSet()
-
构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。插入该 set 的所有元素都必须实现
Comparable
接口。另外,所有这些元素都必须是 可互相比较的:对于 set 中的任意两个元素e1
和e2
,执行e1.compareTo(e2)
都不得抛出ClassCastException
。如果用户试图将违反此约束的元素添加到 set(例如,用户试图将字符串元素添加到其元素为整数的 set 中),则add
调用将抛出ClassCastException
。 -
-
-
- 例1:
-
public class Employee implements Comparable<Object>{ private int age; private String name; private int salary; protected Employee(){ } protected Employee(String name,int age,int salary){ this.age=age; this.name=name; this.salary=salary; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + salary; return result; } @Override public boolean equals(Object obj) { //这是一个自定义的 重写equals方法,认为名字相同的两个对象就是同一个元素,set接口不会接受两个相同的元素 Employee e1=(Employee) obj; if(e1.name==this.name){ return true; }else return false; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override
-
//自定义的compareTo方法,因为TreeSet只能接受实现了compareTo方法的类的对象(必须是可比较的对象),这个compareto会规定了对象的序列,我们称之为类自然顺序; public int compareTo(Object j) { Employee o=(Employee) j; if(name!=null){ if (!(this.name.equals(o))){ return this.name.compareTo(o.name); }else return Integer.signum(this.age-o.age); } else return 0; }
-
import static org.junit.Assert.*; import java.util.TreeSet; import org.junit.Test; public class TreeSetOfEmployee { @Test public void testName() throws Exception { TreeSet<Object> set = new TreeSet<Object>(); set.add(new Employee("Tom",54,5000) ); set.add(new Employee("Tom",28,4000)); //因为我们重写了Employee中的equal和hashcode方法,同名的Tom将不被列入 set.add(new Employee("Amy",24,3000)); set.add(new Employee("Mark",49,4000)); for(Object o: set){ Employee e1=(Employee) o; System.out.println(e1.getName()+"---------"+e1.getAge()+"---------"+e1.getSalary()); } } }
-
-
输出结果:
-
Amy---------24---------3000
Mark---------49---------4000
Tom---------54---------5000
-
-
可以看出,我们输入的两个Tom,有一个没有进入集合,这是因为我们规定了,名字相同的对象为同一种对象,Set的实现类不允许有相同的元素
-
再一个,我们可以看出,我们第三个输入的Amy,确实第一个输出的,这其实是因为我们规定了我们的自然序列是以name调的String类里的compareTo方法来比较名字,我们可以看一下API怎么阐述的这个方法的:
-
public int compareTo(String anotherString)
-
按字典顺序比较两个字符串。该比较基于字符串中各个字符的 Unicode 值。按字典顺序将此
String
对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此String
对象位于参数字符串之前,则比较结果为一个负整数。如果按字典顺序此String
对象位于参数字符串之后,则比较结果为一个正整数。如果这两个字符串相等,则结果为 0;compareTo
只在方法equals(Object)
返回true
时才返回0
。
-
按字典顺序比较两个字符串。该比较基于字符串中各个字符的 Unicode 值。按字典顺序将此
-
可以看到他是按字典顺序排列,我们观察我们的输出结果,Amy因为首字母是A,跑到了最先面。
-
-
二、comparator比较器
-
comparator是一个接口,我们先看看API对它的解释:
-
public interface Comparator<T>
强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如
Collections.sort
或Arrays.sort
),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set
或有序映射
)的顺序,或者为那些没有自然顺序
的对象 collection 提供排序。 -
对于我们此处的应用,我们只需要关注最后一句话,为collection提供排序;
-
我们再来看一下TreeSet的传comparator的一个构造器:
-
TreeSet
public TreeSet(Comparator<? super E> comparator)
-
构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行
相互比较:对于 set 中的任意两个元素
e1
和e2
,执行comparator.compare(e1, e2)
都不得抛出ClassCastException
。如果用户试图将违反此约束的元素添加到 set 中,则add
调用将抛出ClassCastException
。 - 可以看出,采用这个方法我们需要往里传一个经过实现的comparator对象,我们知道comparator是一个接口,只有通过它的实现类才能构建它的对象,我们需要专门写一个类来实现它,写到这里你想到了什么,没错,这是一个只需要在此处实现一次的类,目的就是为了让我们构建一个对象,我们会想到通过写一个匿名内部类来简化操作;
- 那我们在这个类里面实现comparator时需要实现它里面的抽象方法,我们来看一下他有那些抽象方法:
-
-
int
-
构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行
相互比较:对于 set 中的任意两个元素
-
-
-
我们知道所有的类和接口都是继承自Object这个类的,Object这个类里有equals方法,所以我们实际上只需要实现compare方法就可以了,我们再来看一下API对我们实现时推荐的规范:
-
compare
int compare(T o1,T o2)
- 比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
- 我们把上面的TreeSetOfEmployee类里的测试代码稍微修改一下
- 例2:
-
import static org.junit.Assert.*; import java.util.Comparator; import java.util.TreeSet; import org.junit.Test; public class TreeSetOfEmployee_comparator { @Test public void testName() throws Exception { Comparator salarycomparator=new Comparator(){ //这个地方是一个匿名内部类,直接在创建的接口对象后面加花括号写 @Override
-
//重写compare方法,按薪水的来排序 public int compare(Object o1, Object o2) { Employee e1=(Employee) o1; Employee e2=(Employee) o2; if(e1.getSalary()>e2.getSalary()){ return 1; }else if(e1.getSalary()<e2.getSalary()){ return -1; }else return 0; } //方法结束的花括号结尾 }; //匿名类结束的花括号结尾,因为这个是创建的语句,所以最后要加分号来结束这个语句 TreeSet<Object> set = new TreeSet<Object>(salarycomparator); set.add(new Employee("Tom",44,5000) ); set.add(new Employee("Amy",24,9000)); set.add(new Employee("Mark",23,4000)); for(Object o: set){ Employee e1=(Employee) o; System.out.println(e1.getName()+"---------"+e1.getAge()+"---------"+e1.getSalary()); } } }
- 输出结果:
-
Mark---------23---------4000
Tom---------44---------5000
Amy---------24---------9000 - 可以看出,我们的元素通过薪水的从小到大的顺序打印出来,这也告诉我们了一个新的知识点,如果按照推荐规范重写compare方法时,调用比较器比较时,会以自然数的序列为准
-
-
-
-
-
-