Java——集合的排序
<一>接口comparable与接口comparator的比较
1> Comparable
Comparable是在集合内部定义方法实现的排序,位于java.lang包下。是一个对象本身就已经支持自然比较所需要实现的接口。例如String,Interger自己就实现了Comparable接口,并且重写了compareTo方法,可以完成比较大小操作。自定义类要在加入list容器后能够排序,也可以实现Comparable接口,在使用Collections类的sort方法排序时若不指定Comparator,那就以自然顺序排序。所谓自然顺序就是实现Comparable接口设定的排序方式
2> Comparator
Comparator是在集合外部实现的排序,位于java.util包下。是一个专用的比较器,当这个对象不支持自比较或者自比较函数(compareTo())不能满足要求时,可以写一个比较器来完成两个对象之间大小的比较。使用时大多是先构造一个比较器,再通过调用某些sort()方法作参数传入之后进行排序
<二>接口comparable与接口comparator的使用
1> Comparable
- List:由于一些包装类型(如String,Interger)已经实现了Comparable接口,并且重写了comepareTo()方法,所以写出如下代码
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("bbb");
list.add("cba");
list.add("acd");
list.add("zhang");
list.add("shi");
list.sort(null);
System.out.println(list);
}
}
这里显示运行结果是排好序的。我们可以注意到list.sort()方法里写的是null,这里其实是需要一个自己构造的比较器,但是这里传入null,说明没有比较器,以自然顺序(String类内的compareTo方法)进行排序
- 我么们也可以这么排序:
Collections.sort(list);
结果是一样的,下面我们来实现一个自定义类
class Person implements Comparable<Person>{
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public int compareTo(Person o) {
return this.age - o.getAge();
}
}
- 这个类重写了comepareTo方法,我们规定以年龄为准排序
public class Test {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person(99,"bbb"));
list.add(new Person(56,"cba"));
list.add(new Person(34,"acd"));
list.add(new Person(78,"zhang"));
list.add(new Person(43,"shi"));
list.sort(null);
//Collections.sort(list);
Iterator<Person> iter= list.iterator();
while (iter.hasNext()){
Person p = iter.next();
System.out.println("age=" + p.getAge() + "name=" + p.getName());
}
}
}
运行结果:
同样的 ,使用Collections.sort(list)也会得到相同的结果。
- 另外还有一个地方涉及到了Comparable接口,那就是TreeSet,前面说过,TreeSet的底层实现是红黑树,并且每add一次,就会自动把其放到正确的位置上,保证集合有序。对于包装类,因为已经实现了Comparable接口并重写了compareTo方法,所以不需要进行其他操作。但当Set内元素类型为自定义类时,我们就要注意了:
public class Test {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
set.add(new Person(99,"bbb"));
set.add(new Person(56,"cba"));
set.add(new Person(34,"acd"));
set.add(new Person(78,"zhang"));
set.add(new Person(43,"shi"));
Iterator<Person> iter = set.iterator();
while (iter.hasNext()){
Person p = iter.next();
System.out.println("age=" + p.getAge() + "name=" + p.getName());
}
}
}
首先,自定义类必须实现Comparable接口并重写了compareTo方法,这样在add之后迭代Set时会发现是有序的,但如果Person类没有进行以上操作,编译时就会报错,因为TreeSet始终是要保持有序的。
2> Comparator
- 首先考虑一个问题,如果我不想根据字典顺序对元素为字符串的容器排序怎么办?学习了Comparable接口后,如果是个自定义类,我们可以通过重写compareTo()方法来改变排序规则。可对于String这种不可能修改的类呢? 然后就引出了我们的比较器。
下面是一个例子
public class Test_02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("shi");
list.add("zhang");
list.add("hao");
list.add("du");
list.add("abv");
list.sort(null);
//Collections.sort(list); 也可以
System.out.println(list);
}
}
结果可以想象:
- 这时我们可以通过构造我们自己的比较器,来修改排序规则
加入类:
class lengthComparator implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
再将原代码中的list.sort()改为
list.sort(new lengthComparator());
运行结果:
- 当然这里可以使用匿名内部类+lambda表达式来简化代码,如下图:
public class Test_02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("shi");
list.add("zhang");
list.add("hao");
list.add("du");
list.add("abv");
list.sort((o1, o2) -> o1.length() - o2.length());
//也可以这样
//Collections.sort(list,(o1, o2) -> o1.length() - o2.length())
System.out.println(list);
}
}
那对于自定义类,我们也可以通过构造比较器进行排序。
- 例如上面的Person类,我们取消实现Comparable接口:
class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
- 然后利用匿名内部类和lambda表达式实现比较器
public class Test {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person(99,"bbb"));
list.add(new Person(56,"cba"));
list.add(new Person(34,"acd"));
list.add(new Person(78,"zhang"));
list.add(new Person(43,"shi"));
list.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
//这样写也可以
// Collections.sort(list,(o1, o2) -> o1.getName().compareTo(o2.getName()));
Iterator<Person> iter = list.iterator();
while (iter.hasNext()){
Person p = iter.next();
System.out.println("age=" + p.getAge() + "name=" + p.getName());
}
}
}
运行结果:
- 这样就成功修改了排序顺序。
再将视线回归到TreeSet上,难道TreeSet对象就一定要实现Comparable接口吗?我们刚才看到,如果不实现,add的时候编译报错。其实还有一种解决方案:
- 我们进入TreeSet的jdk源码,发现除了无参构造器还隐藏着这么一个构造器:
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
根据这段代码我们得到了信息:
- 1.Set其实是通过Map实现的(这个之前一篇文章里说过)
- 2.可以在构造器里以一个比较器作为参数
那一起开动我们的小脑筋,既然要求TreeSet必须是有序的,但TreeSet元素的类并没有实现Comparable接口,可不可以在创建TreeSet的时候就传入一个比较器,用来规定排序标准呢?
答案当然是可以的 我们修改代码如下:
public class Test {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
set.add(new Person(23,"shi"));
set.add(new Person(10,"zhang"));
set.add(new Person(57,"adv"));
set.add(new Person(99,"hao"));
set.add(new Person(36,"meng"));
Iterator<Person> iter = set.iterator();
while (iter.hasNext()){
Person p = iter.next();
System.out.println("age=" + p.getAge() + "name=" + p.getName());
}
}
}