Java—集合排序

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);
    }
}

image.png

这里显示运行结果是排好序的。我们可以注意到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());
        }
    }
}

运行结果:
image.png
同样的 ,使用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);
    }
}

结果可以想象:
image.png

  • 这时我们可以通过构造我们自己的比较器,来修改排序规则

加入类:

class lengthComparator implements Comparator<String>{
    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
} 

再将原代码中的list.sort()改为

list.sort(new lengthComparator());

运行结果:

image.png

  • 当然这里可以使用匿名内部类+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());
        }
    }
}
运行结果:

image.png

  • 这样就成功修改了排序顺序。

再将视线回归到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());
        }
    }
}
运行结果:

image.png

通过以上的例子,要清晰的将Comparable和Comparator两个接口区分开来

猜你喜欢

转载自blog.csdn.net/wintershii/article/details/81253526