Java排序详解

图片 5

本文由码农网 –
小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

很难想象有Java开发人员不曾使用过Collection框架。在Collection框架中,主要使用的类是来自List接口中的ArrayList,以及来自Set接口的HashSet、TreeSet,我们经常处理这些Collections的排序。

集合框架的出现

集合框架,在平时的编程中经常会用到,体系庞大又十分的重要,所以做个总结很有必要,我们知道数组可以用来存放基础数据类型与引用类型,在定义的时候,存放在数组中的类型是已经确定的了,如下面给定的数组只能用来存放String类型的值,并且这时数组的长度已经固定

String[] array = new String[5];

如果你试图在array中赋于String以外的类型,则会给出编译提示,类型转换错误

图片 1

又如果你想给数组添加一个新的值,比如说像这样

array[5] = "s";

那么在运行时,虚拟机会抛出如下异常,数组越界

图片 2

为了弥补数组这种不可变的行为,Java设计者们设计了一系列操作元素的集合类,这一系列的集合类就组成了集合框架,由于这里面涉及到的接口及类较多,所以需要先对整个体系有一个感观的认识,这样才能心中有数。

很难想象有Java开发人员不曾使用过Collection框架。在Collection框架中,主要使用的类是来自List接口中的ArrayList,以及来自Set接口的HashSet、TreeSet,我们经常处理这些Collections的排序。

小编整理了一些java进阶学习资料和面试题,需要资料的请加JAVA高阶学习Q群:664389243
这是小编创建的java高阶学习交流群,加群一起交流学习深造。群里也有小编整理的2019年最新最全的java高阶学习资料!

集合的组成

 图片 3

上面这张图只是简单的列出了集合中最常使用的接口以及实现类,当然远远不止这些,不过掌握了这些就能解决大部分的集合问题。从图中,可以知道集合框架中主要有两大派系,这两个派系被抽象成了两个接口,分别为Collection与Map,这两个接口下面又衍生出子接口以及一些实现类,对于List、Set和Map三种集合,最常用的实现类分别是ArrayList、HashSet和HashMap三个实现类。面对众多繁杂的各种接口以及类,怎么才能比较好的掌握呢,其实可以把集合框架看成是一个小型的数据库,我们都知道,数据库主要的操作就是增删改查,那么集合也是如此,从这四个方面入手,就可以大体掌握集合的用法。

在本文中,我将主要关注排序Collection的ArrayList、HashSet、TreeSet,以及最后但并非最不重要的数组。

在本文中,我将主要关注排序Collection的ArrayList、HashSet、TreeSet,以及最后但并非最不重要的数组。

List

图片 4

我们看看如何对给定的整数集合(5,10,0,-1)进行排序:

ArrayList

ArrayList类为List接口的主要实现类,是一个数组队列,提供了添加、删除、修改、遍历等功能,相当于是一个动态数组,与数组的固定容量相比较,ArrayList集合的容量可以根据一定的规则动态的增长,实现了RandmoAccess接口,即提供了随机访问功能。在ArrayList中,我们可以通过元素的序号快速获取元素对象,这就是快速随机访问

ArrayList中的方法很多都跟位置有关,下面的代码列举了ArrayList一些常用的方法

package collection.list;

import java.util.ArrayList;
import java.util.List;

public class ListDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        // 添加元素
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");

        // 获取集合大小
        System.out.println("初始状态:" + list);
        System.out.println("数组大小:" + list.size());

        // 判断集合是否包含某元素
        boolean contain = list.contains("水浒传");
        System.out.println("集合是否包含水浒传: " + contain);

        // 判断集合是否为空
        boolean isEmpty = list.isEmpty();
        System.out.println("集合是否为空:" + isEmpty);

        // 获取第一个元素
        String string = list.get(0);
        System.out.println("第一个元素是: " + string);

        // 获取第一次出现给定元素的位置
        int index = list.indexOf("水浒传");
        System.out.println("第一次出现水浒传的位置是:" + index);

        // 在1位置插入元素,插入的位置不能超过集合的最大长度,否则报java.lang.IndexOutOfBoundsException异常
        list.add(2, "平凡的世界");
        System.out.println("在位置2处插入元素后的集合: " + list);

        // 删除刚才插入的元素
        string = list.remove(2);
        System.out.println("被删除的元素: " + string);
        System.out.println("删除元素后的集合: " + list);

        // 替换指定位置的元素
        string = list.set(0, "儒林外史");
        System.out.println("被替换的元素是:" + string);
        System.out.println("现在集合中的元素是:" + list);

        // 清空集合
        list.clear();
        System.out.println("清空集合后:" + list);
        System.out.println("清空集合是否为空:" + list.isEmpty());
    }
}

运行结果

初始状态:[红楼梦, 三国演义, 西游记, 水浒传]
数组大小:4
集合是否包含水浒传: true
集合是否为空:false
第一个元素是: 红楼梦
第一次出现水浒传的位置是:3
在位置2处插入元素后的集合: [红楼梦, 三国演义, 平凡的世界, 西游记, 水浒传]
被删除的元素: 平凡的世界
删除元素后的集合: [红楼梦, 三国演义, 西游记, 水浒传]
被替换的元素是:红楼梦
现在集合中的元素是:[儒林外史, 三国演义, 西游记, 水浒传]
清空集合后:[]
清空集合是否为空:true

与数据库一样,集合中最常用的也是查询的操作,所以这个部分单独介绍,因为ArrayList实现了RandmoAccess接口,所以与数组类似,每个元素在ArrayList中都有自己特定的位置,我们可以通过这个角标,也就是索引来获取对应的元素

package collection.list;

import java.util.ArrayList;
import java.util.List;

public class TraverseDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");

        String string = null;

        for (int i = 0; i < list.size(); i++) {
            string = list.get(i);
            System.out.println(string);
        }
    }
}

查看API可知,ArrayList实现了Iterable接口,调用Iterable接口中的iterator()
方法可以返回一个叫做迭代器的接口Iterator,可以把Iterator当做一个游标使用,API很简单,总共就三个方法

boolean hasNext() 
          如果仍有元素可以迭代,则返回 true。 
E next() 
          返回迭代的下一个元素。 
void remove() 
          从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 

下面的代码演示了Iterator的常用用法

package collection.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TraverseDemo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");

        String string = null;
        Iterator<String> it = list.iterator();

        while (it.hasNext()) {
            string = it.next();
            System.out.println(string);
        }
    }
}

上面的while循环,我们可以改成for循环,这种方式效果可能高些,因为for循环结束后,iterator变量便释放了

package collection.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TraverseDemo4 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");

        String string = null;

        for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
            string = iterator.next();
            System.out.println(string);
        }
    }
}

通过增强for循环,也可以遍历ArrayList,增强for循环的优点是代码简洁,但是无法获取特定的元素,因为没有脚标了

package collection.list;

import java.util.ArrayList;
import java.util.List;

public class TraverseDemo3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");

        for (String string : list) {
            System.out.println(string);
        }
    }
}

让我们看看如何对给定的整数集合(5,10,0,-1)进行排序:

数据存储在ArrayList中

LinkedList

LinkedList也是List接口下一个非常重要的实现类,底层数据结构是基于链表,链表相对于数组(ArrayList的底层实现)对数据的增删的速度较快,不过查询速度就稍微逊色了些,来看一下LinkedList中特有的方法

package collection.list;

import java.util.LinkedList;

import org.junit.Before;
import org.junit.Test;

public class LinkedListDemo {
    LinkedList<String> list = null;

    @Before
    public void init() {
        list = new LinkedList<String>();
    }

    @Test
    public void testOfferFirst() { // 在列表开头插入元素
        list.offerFirst("s1");
        list.offerFirst("s2");
        System.out.println(list); // [s2, s1]
    }

    @Test
    public void testOfferLast() { // 在列表末尾插入元素
        list.offerLast("s1");
        list.offerLast("s2");
        System.out.println(list); // [s1, s2]
    }

    @Test
    public void testPeekFirst() { // 获取但不移除列表的第一个元素;如果列表为空,则返回 null
        list.offerLast("s1");
        list.offerLast("s2");
        list.offerLast("s3");
        System.out.println(list.peekFirst()); // s1
        System.out.println(list); // [s1, s2, s3]
    }

    @Test
    public void testPeekLast() { // 获取但不移除列表的最后一个元素;如果列表为空,则返回 null
        list.offerLast("s1");
        list.offerLast("s2");
        list.offerLast("s3");
        System.out.println(list.peekLast()); // s3
        System.out.println(list); // [s1, s2, s3]
    }

    @Test
    public void testPollFirst() { // 获取并移除列表的第一个元素;如果此列表为空,则返回 null
        list.offerLast("s1");
        list.offerLast("s2");
        list.offerLast("s3");
        System.out.println(list.pollFirst()); // s1
        System.out.println(list); // [s2, s3]
    }

    @Test
    public void testPollLast() { // 获取并移除列表的最后一个元素;如果此列表为空,则返回 null
        list.offerLast("s1");
        list.offerLast("s2");
        list.offerLast("s3");
        System.out.println(list.pollLast()); // s3
        System.out.println(list); // [s1, s2]
    }

}

数据(整数)存储在ArrayList中

private void sortNumbersInArrayList() {
        List<Integer> integers = new ArrayList<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original list: " +integers);
        Collections.sort(integers);
        System.out.println("Sorted list: "+integers);
        Collections.sort(integers, Collections.reverseOrder());
        System.out.println("Reversed List: " +integers);
}

输出:

Original list: [5, 10, 0, -1]
Sorted list: [-1, 0, 5, 10]
Reversed List: [10, 5, 0, -1]

private void sortNumbersInArrayList() {

小结

ArrayList:底层数据结构是数组,增删操作慢,查询速度快

LinkedList:底层数据结构是链表,增删操作快,查询速度慢

数据(整数)存储在HashSet中

private void sortNumbersInHashSet() {
        Set<Integer> integers = new HashSet<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original set: " +integers);
       // Collections.sort(integers); This throws error since sort method accepts list not collection
        List list = new ArrayList(integers);
        Collections.sort(list);
        System.out.println("Sorted set: "+list);
        Collections.sort(list, Collections.reverseOrder());
        System.out.println("Reversed set: " +list);
 }

输出:

Original set: [0, -1, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]

在这个例子中(数据(整数)存储在HashSet中),我们看到HashSet被转换为ArrayList进行排序。在不转换为ArrayList的情况下,可以通过使用TreeSet来实现排序。TreeSet是Set的另一个实现,并且在使用默认构造函数创建Set时,使用自然排序进行排序。

List integers = new ArrayList<>();

Set

数据(整数)存储在TreeSet中

private void sortNumbersInTreeSet() {
        Set<Integer> integers = new TreeSet<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original set: " + integers);
        System.out.println("Sorted set: "+ integers);
        Set<Integer> reversedIntegers = new TreeSet(Collections.reverseOrder());
        reversedIntegers.add(5);
        reversedIntegers.add(10);
        reversedIntegers.add(0);
        reversedIntegers.add(-1);
        System.out.println("Reversed set: " + reversedIntegers);
 }

输出:

Original set: [-1, 0, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]

在这种情况下,“Original set:”和“Sorted
set:”两者相同,因为我们已经使用了按排序顺序存储数据的TreeSet,所以在插入后不用排序。

到目前为止,一切都如期工作,排序似乎是一件轻而易举的事。现在让我们尝试在各个Collection中存储自定义对象(比如Student),并查看排序是如何工作的。

integers.add;

HashSet

HashSet是Set接口的一个重要的实现类,主要的特点是:里面不能存放重复元素,而且采用散列的存储方法,所以没有顺序,这里的没有顺序指的是存入和取出的顺序不一定一致,HashSet的底层数据结构是哈希表,内部实现是基于HashMap

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

看个例子(API请自行查阅)

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo1 {
    public static void main(String[] args) {
        HashSet<String> hs = new HashSet<String>();
        hs.add("s1");
        hs.add("s2");
        hs.add("s2");
        hs.add("s3");

        Iterator<String> it = hs.iterator();

        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

运行结果

s2
s1
s3

从运行结果来看,我们往hashset中存入了四个元素,其中存了两次的s2,最终只存入了一个,这说明hashset中元素确实不能重复,元素的取出顺序与存放顺序也不一致。更多时候,我们要存放的不仅仅是像字符串这样简单的对象,而是一些自定义对象

package collection.set;

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

现在往hashset中添加自定义对象Student

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo2 {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<Student>();

        set.add(new Student("s1", 21));
        set.add(new Student("s2", 22));
        set.add(new Student("s2", 22));
        set.add(new Student("s3", 23));

        Iterator<Student> it = set.iterator();

        while (it.hasNext()) {
            Student student = it.next();
            System.out.println(student.getName() + ":" + student.getAge());
        }
    }
}

运行结果

s1:21
s2:22
s3:23
s2:22

出现了一个与预期不同的答案,那就是hashset中添加了两个s2对象,不是说不允许重复元素存在吗,这又是怎么回事,其实hashset在添加元素的时候,首先会调用hashCode方法判断集合中是否有对象的哈希码值与新的对象相等,如果不相等,则添加成功,如果相等,那么再继续调用equals方法将这些对象与插入的对象进行比较,如果为true,则添加失败,如果为false,则添加成功,用下面的流程图表示更直观些

图片 5

那上面的SetDemo1又是怎么回事,别忘了SetDemo1中添加的是String对象,String类中重写了hashCode与equals方法,下面验证一下所说的这个结论,重写Student类的hashCode与equals方法

package collection.set;

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int hashCode() {
        System.out.println(this.getName() + "...hashCode...");
        return this.getName().hashCode() + age * 31;
    }

    public boolean equals(Object object) {
        Student student = (Student) object;
        System.out.println(this.name + "...equals..." + student.getName());
        return this.name.equals(student.getName()) && this.age == student.getAge();
    }
}

主程序也修改下

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo2 {
    public static void main(String[] args) {
        HashSet<Student> hs = new HashSet<Student>();

        boolean isAdd = hs.add(new Student("s1", 21));
        System.out.println(isAdd);
        isAdd = hs.add(new Student("s2", 22));
        System.out.println(isAdd);
        isAdd = hs.add(new Student("s2", 22));
        System.out.println(isAdd);
        isAdd = hs.add(new Student("s3", 23));
        System.out.println(isAdd);

        Iterator<Student> it = hs.iterator();

        while (it.hasNext()) {
            Student student = it.next();
            System.out.println(student.getName() + ":" + student.getAge());
        }
    }
}

运行结果

s1...hashCode...
true
s2...hashCode...
true
s2...hashCode...
s2...equals...s2
false
s3...hashCode...
true
s1:21
s2:22
s3:23

一起来分析一下这个过程,首先添加s1对象,它调用了hashCode方法,因为刚开始hashset中是没有任何元素的,所以这时候添加成功,为true,接着添加s2对象,同样s2对象也调用hashCode方法,由于s2对象与s1对象的哈希值不一样,所以hashset认为集合中没有s2对象,添加成功,接着又添加一个s2对象,根据运行结果,这个s2对象调用hashCode方法之后,发现hashset中已经存在一个与它哈希值一样的对象(哈希值一样只能说明两个对象在内存中的位置一样,对象是否是同一个还得用equals方法进行判断),所以接着调用了equals方法,由Student类的equals方法知道,这两个s2对象相同,返回true,所以这时候s2对象添加失败,最后添加s3对象,根据前面的分析,容易知道,s3对象成功的添加到set集合中了,最终hashset添加了s1,s2,s3三个对象。

通常hashset操作的对象,都需要重写hashCode与equals方法。

数据(Student对象)存储在ArrayList中

private void sortStudentInArrayList() {
        List<Student> students = new ArrayList<>();
        Student student1 = createStudent("Biplab", 3);
        students.add(student1);
        Student student2 = createStudent("John", 1);
        students.add(student2);
        Student student3 =  createStudent("Pal", 5);
        students.add(student3);
        Student student4 = createStudent("Biplab", 2);
        students.add(student4);
        System.out.println("Original students list: " + students);
        Collections.sort(students);// Error here
        System.out.println("Sorted students list: " + students);
        Collections.sort(students, Collections.reverseOrder());
        System.out.println("Reversed students list: " + students);
}
private Student createStudent(String name, int no) {
        Student student = new Student();
        student.setName(name);
        student.setNo(no);
        return student;
}
public class Student {
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", no=" + no +
                '}';
    }
}

这会抛出编译时错误,并显示以下错误消息:

sort(java.util.List<T>)
in Collections cannot be applied
to (java.util.List<com.example.demo.dto.Student>)
reason: no instance(s) of type variable(s) T exist so that Student 
conforms to Comparable<? Super T>

为了解决这个问题,要么Student类需要实现Comparable,要么需要在调用Collections.sort时传递Comparator对象。在整型情况下,排序方法没有错误,因为Integer类实现了Comparable。让我们看看,实现Comparable或传递Comparator如何解决这个问题,以及排序方法如何实现Collection排序。

integers.add;

TreeSet

上面说到的HashSet,当我们获取元素的时候,由于底层数据结构是哈希表,所以输出的顺序是无序的,如果需要对Set集合中的元素进行排序,那就要用到set中另一个重要的实现类TreeSet,TreeSet的底层数据结构是二叉树,可以对Set集合中的元素进行排序,先来看个例子

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo1 {
    public static void main(String[] args) {
        TreeSet<String> ts = new TreeSet<String>();
        ts.add("abc");
        ts.add("aaa");
        ts.add("acd");
        ts.add("abcd");

        Iterator<String> it = ts.iterator();

        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

输出结果

aaa
abc
abcd
acd

输出的结果与存入的顺序不一致,那么TreeSet集合内部是根据什么来排序的呢,查阅API知道,有两种方式可以实现对元素的排序

  1. 使用元素的自然顺序对元素进行排序
  2. 根据创建 set 时提供的 Comparator
    进行排序

这边使用的就是第一种,根据元素的自然顺序进行排序,接下来我们往TreeSet集合中添加自定义对象Student看看

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<Student>();
        ts.add(new Student("s01", 20));
        ts.add(new Student("s02", 22));
        ts.add(new Student("s04", 21));
        ts.add(new Student("s03", 22));

        Iterator<Student> it = ts.iterator();

        while (it.hasNext()) {
            Student student = it.next();
            System.out.println(student.getName() + " : " + student.getAge());
        }
    }
}

这时候,控制台抛出了如下异常信息

Exception in thread "main" java.lang.ClassCastException: collection.set.Student cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(Unknown Source)
    at java.util.TreeMap.put(Unknown Source)
    at java.util.TreeSet.add(Unknown Source)
    at collection.set.TreeSetDemo2.main(TreeSetDemo2.java:9)

根据提示信息,是因为类型转换错误引起的,这里出现了一个Comparable,通过查询API得知Comparable是一个接口,这个接口很简单,只有一个方法,该方法在添加对象时隐式地被调用

int compareTo(T o)

    比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

由于TreeSet会对存入的对象进行排序,所以需要对象之间相互进行比较,也就是具有比较性,那怎样才能让对象具有比较性呢,Java中提供了一个接口Comparable,只要实现了这个接口,对象之间就具有了比较性,说到这里,应该可以猜测出来为什么存入String对象的时候可以成功了,现在我们让Student对象实现Comparable接口,让它具有比较性

package collection.set;

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
        return this.age - s.age; // 按照年龄升序排列
    }
}

代码的运行结果

s01 : 20
s04 : 21
s02 : 22

问题来了,存了四个对象,怎么就输出三个对象了,重新回顾一下compareTo方法,因为compareTo方法中是根据学生的年龄大小进行比较,而s02与s03这两个学生的年龄相同,所以认为他们是同一个对象,这里假定姓名与年龄都相同的时候,认为两个学生才是同一个人,修改一下compareTo方法

@Override
public int compareTo(Student s) {
    int num = this.age - s.age;

    if (num == 0) { // 当年龄相同时,按照姓名排序
        return this.getName().compareTo(s.getName());
    }

    return num; // 按照年龄升序排列
}

这时候的运行结果就符合我们的预期了

s01 : 20
s04 : 21
s02 : 22
s03 : 22

来思考一个问题,如果现在优先根据学生的年龄进行排序,那怎么办,首先想到修改我们的代码,这是可以的,那如果突然有一天又需要根据年龄的长度进行排序,那难道又要修改我们的代码吗,别忘了,还有另一种方法可以实现对对象的排序,那就是

根据创建 set 时提供的 Comparator 进行排序

什么意思呢,看下TreeSet的构造函数就明白了

TreeSet(Comparator<? super E> comparator)
            构造一个新的空 TreeSet,它根据指定比较器进行排序。

这里在初始化TreeSet的时候,传入了一个参数Comparator,Comparator也是一个接口,其中compare方法可以用来比较对象

int compare(T o1, T o2)

    比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。

现在新建一个类实现Comparator接口

package collection.set;

import java.util.Comparator;

public class StudentComparator implements Comparator<Student> {

    @Override
    public int compare(Student s1, Student s2) {
        int num = s1.getName().compareTo(s2.getName());

        if (num == 0) {
            return s1.getAge() - s2.getAge();
        }

        return num;
    }

}

在构造TreeSet的时候,传入StudentComparator

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo3 {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<Student>(new StudentComparator());
        ts.add(new Student("s01", 20));
        ts.add(new Student("s02", 22));
        ts.add(new Student("s04", 21));
        ts.add(new Student("s03", 22));

        Iterator<Student> it = ts.iterator();

        while (it.hasNext()) {
            Student student = it.next();
            System.out.println(student.getName() + " : " + student.getAge());
        }
    }
}

运行结果符合我们的要求

s01 : 20
s02 : 22
s03 : 22
s04 : 21

别忘了,此时Student类依然是实现了Comparable接口的,内部有自己的比较规则,从上面的运行结果可以得出一个结论构造器中传入的比较器规则优于对象本身的比较规则,后期如果需要根据其他方式进行排序,则只需新建一个比较器即可,就不必修改Student类了。

使用Comparable排序

package com.example.demo.dto;
public class Student implements Comparable{
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", no=" + no +
                '}';
    }
    @Override
    public int compareTo(Object o) {
         return this.getName().compareTo(((Student) o).getName());
    }
}

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]

在所有示例中,为了颠倒顺序,我们使用“Collections.sort(students,
Collections.reverseOrder()”,相反的,它可以通过改变compareTo(..)方法的实现而达成目标,且compareTo(…)
的实现看起来像这样 :

@Override
  public int compareTo(Object o) {
     return (((Student) o).getName()).compareTo(this.getName());
  }

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]
Reversed students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]

如果我们观察输出结果,我们可以看到“Sorted students
list:”以颠倒的顺序(按学生name)输出学生信息。

到目前为止,对学生的排序是根据学生的“name”而非“no”来完成的。如果我们想按“no”排序,我们只需要更改Student类的compareTo(Object
o)实现,如下所示:

 @Override
 public int compareTo(Object o) {
    return  (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
}

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='John', no=1}, Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}]

在上面的输出中,我们可以看到“no”2和3的两名学生具有相同的名字“Biplab”。

现在假设我们首先需要按“name”对这些学生进行排序,如果超过1名学生具有相同姓名的话,则这些学生需要按“no”排序。为了实现这一点,我们需要改变compareTo(…)方法的实现,如下所示:

@Override
  public int compareTo(Object o) {
    int result = this.getName().compareTo(((Student) o).getName());
        if(result == 0) {
            result = (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
        }
        return  result;
 }

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]

integers.add;

小结

HashSet:底层数据结构是哈希表,元素无序,往HashSet里面存的自定义元素要复写hashCode和equals方法,以保证元素的唯一性,保证元素唯一性的顺序

  1. 调用元素的hashCode方法,如果不同,则存入元素,如果相同,转2
  2. 调用元素的equals方法,如果不同,则存入元素,如果相同,则不存

注意:hashCode和equals方法在集合操作元素时默认被调用

TreeSet:底层数据结构式二叉树,元素有序,可以对集合中的元素进行排序,有两种方式

  1. 元素实现Compareble接口,覆盖compareTo方法
  2. 自定义一个类,实现Comparator接口,覆盖compare方法

使用Comparator排序

为了按照“name”对Students进行排序,我们将添加一个Comparator并将其传递给排序方法:

public class Sorting {
 private void sortStudentInArrayList() {
        List<Student> students = new ArrayList<>();
        Student student1 = createStudent("Biplab", 3);
        students.add(student1);
        Student student2 = createStudent("John", 1);
        students.add(student2);
        Student student3 =  createStudent("Pal", 5);
        students.add(student3);
        Student student4 = createStudent("Biplab", 2);
        students.add(student4);
        System.out.println("Original students list: " + students);
        Collections.sort(integers, new NameComparator());
        System.out.println("Sorted students list: " + students);
 }
}
public class Student {
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", no=" + no +
                '}';
    }
}
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]

同样,如果我们想按照“no”对Students排序,那么可以再添加一个Comparator(NoComparator.java),并将其传递给排序方法,然后数据将按“no”排序。

现在,如果我们想通过“name”然后“no”对学生进行排序,那么可以在compare(…)内结合两种逻辑来实现。

class NameNoComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        int result = o1.getName().compareTo(o2.getName());
        if(result == 0) {
            result =  o1.getNo() <  o2.getNo() ? -1 : o1.getNo() == o2.getNo() ? 0 : 1;
        }
       return result;
    }
}

输出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}]

integers.add;

Map

Map这个体系下经常使用的主要有HashMap和TreeMap这两个实现类,可以与set体系中的HashSet和TreeSet进行对比学习,前面提到Set的底层就是用Map,所以他们之间有些许类似,比如他们中的元素都是无序的。map在这里是映射的意思,跟数学中集合的映射概念是一个意思,map中的元素是以键值对的形式存储的,一个键对应一个值,并且需要保证键的唯一性,否则值将会被覆盖。

数据(Students对象)存储在Set中

在Set的这个情况下,我们需要将HashSet转换为ArrayList,或使用TreeSet对数据进行排序。此外,我们知道要使Set工作,equals(…)和hashCode()方法需要被覆盖。下面是基于“no”字段覆盖equals和hashcode的例子,且这些是IDE自动生成的代码。与Comparable或Comparator相关的其他代码与ArrayList相同。

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return no == student.no;
    }
    @Override
    public int hashCode() {
        return Objects.hash(no);
    }

System.out.println(“Original list: ” +integers);

HashMap

HashMap与HashSet一样,底层数据结构基于哈希表,允许存在null的键,null的值。

package map;

import java.util.Collection;
import java.util.HashMap;

public class HashMapDemo1 {
    public static void main(String[] args) {
        HashMap<String, Integer> hm = new HashMap<String, Integer>();
        hm.put("s1", 21); // 添加元素
        hm.put("s2", 22);
        hm.put("s3", 23);
        hm.put("s4", 24);
        hm.put(null, null); // 存入null键,null值

        Collection<Integer> values = hm.values(); // 获取集合中所有的值
        System.out.println(values);
        System.out.println(hm.get("s1")); // 根据key获取对应的值
        System.out.println(hm.containsKey("s1")); // 判断是否包含给定的键
        System.out.println(hm.isEmpty()); // 集合是否为空
        System.out.println(hm.size()); // 集合中包含的键值对个数
        System.out.println(hm.remove("s2")); // 移除集合中给定键所对应的值,并返回该值
        System.out.println(hm.size()); // 集合中包含的键值对个数
        values = hm.values(); // 获取集合中所有的值
        System.out.println(values);
    }
}

运行结果

[null, 22, 21, 23, 24]
21
true
false
5
22
4
[null, 21, 23, 24]

通常,我们需要成对的取出集合中的键值对,那么根据前面的经验,查找一下,发现并没有类似ArrayList,HashSet中所使用的迭代器,那么用什么方法可以获取键值对呢,Map集合中两种方法可以实现

  1. 使用keySet()方法先将Map转换成Set,然后用Set中的迭代器进行迭代
  2. 使用entrySet()方法,将Map转成具有映射关系的Set视图

先看第一种

package map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class HashMapDemo2 {
    public static void main(String[] args) {
        HashMap<String, Integer> hm = new HashMap<String, Integer>();
        hm.put("s1", 21);
        hm.put("s2", 22);
        hm.put("s3", 23);
        hm.put("s4", 24);

        Set<String> keySet = hm.keySet();

        Iterator<String> it = keySet.iterator();

        while (it.hasNext()) {
            String key = (String) it.next();
            Integer value = hm.get(key);

            System.out.println("key=" + key + "," + "value=" + value);
        }

    }
}

再来看第二种,也就是使用entrySet方法

package map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapDemo3 {
    public static void main(String[] args) {
        HashMap<String, Integer> hm = new HashMap<String, Integer>();
        hm.put("s1", 21);
        hm.put("s2", 22);
        hm.put("s3", 23);
        hm.put("s4", 24);

        Set<Map.Entry<String, Integer>> entry = hm.entrySet();

        Iterator<Map.Entry<String, Integer>> it = entry.iterator();

        while (it.hasNext()) {
            Map.Entry<String, Integer> me = it.next();
            String key = me.getKey();
            Integer value = me.getValue();
            System.out.println("key=" + key + "," + "value=" + value);
        }

    }
}

数据(Students对象)存储在数组中

为了对数组排序,我们需要做与排序ArrayList相同的事情(要么执行Comparable要么传递Comparable给sort方法)。在这种情况下,sort方法是“Arrays.sort(Object[]
a )”而非“Collections.sort(..)”。

private void sortStudentInArray() {
       Student [] students = new Student[4];
        Student student1 = createStudent("Biplab", 3);
        students[0] = student1;
        Student student2 = createStudent("John", 1);
        students[1] = student2;
        Student student3 =  createStudent("Pal", 5);
        students[2] = student3;
        Student student4 = createStudent("Biplab", 2);
        students[3] = student4;
        System.out.print("Original students list: ");
        for (Student student: students) {
            System.out.print( student + " ,");
        }
        Arrays.sort(students);
        System.out.print("nSorted students list: ");
        for (Student student: students) {
            System.out.print( student +" ,");
        }
        Arrays.sort(students, Collections.reverseOrder());
        System.out.print("nReversed students list: " );
        for (Student student: students) {
            System.out.print( student +" ,");
        }
   }
//Student class
// All properties goes here
  @Override
   public int compareTo(Object o) {
       int result =this.getName().compareTo(((Student)o).getName());
       if(result ==0) {
           result =  (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
       }
       return  result;
   }

输出:

Original students list: Student{name='Biplab', no=3} ,Student{name='John', no=1} ,Student{name='Pal', no=5} ,Student{name='Biplab', no=2} ,
Sorted students list: Student{name='Biplab', no=2} ,Student{name='Biplab', no=3} ,Student{name='John', no=1} ,Student{name='Pal', no=5} ,
Reversed students list: Student{name='Pal', no=5} ,Student{name='John', no=1} ,Student{name='Biplab', no=3} ,Student{name='Biplab', no=2} ,

Collections.sort;

TreeMap

有了前面学习TreeSet的基础,学习TreeMap就容易多了,与TreeSet一样,TreeMap底层数据结构也是基于二叉树,可以对Map集合中的元素进行排序,同样有两种方式

  1. 根据键的自然顺序进行排序
  2. 根据创建映射时提供的 Comparator 进行排序

具体的使用方法可以参考上述所讲的TreeSet,本小节的总结见最后。

结论

我们经常对Comparable/Comparator的使用以及何时使用哪个感到困惑。下面是我总结的Comparable/Comparator的使用场景。

Comparator:

  • 当我们想排序一个无法修改的类的实例时。例如来自jar的类的实例。
  • 根据用例需要排序不同的字段时,例如,一个用例需要通过“name”排序,还有个想要根据“no”排序,或者有的用例需要通过“name和no”来排序。

Comparable:

应该在定义类时知道排序的顺序时使用,并且不会有其他任何需要使用Collection
/数组来根据其他字段排序的情况。

注意:我没有介绍Set /
List的细节。我假设读者已经了解了这些内容。此外,没有提供使用Set /
array进行排序的详细示例,因为实现与ArrayList非常相似,而ArrayList我已经详细给出了示例。

最后,感谢阅读。

System.out.println(“Sorted list: “+integers);

集合工具类

Collections.sort(integers, Collections.reverseOrder;

Collections

Collections是集合框架中的一个工具类,该类中提供了一系列静态的方法来操作集合,包括对list集合的排序,二分查找,查询集合的最大最小值等,像上面介绍的比较常用的ArrayList,HashSet,TreeMap等集合都是线程不同步的,当有多线程操作这些集合的时候,可以使用该工具类synchronizedList,synchronizedSet,synchronizedMap等方法将线程不同步的集合转成同步的。

package utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CollectionsDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("abcd");
        list.add("aaa");
        list.add("zz");
        list.add("y");
        list.add("kkkkk");

        // 使用二分查找指定元素,找到则返回索引
        int index = Collections.binarySearch(list, "zz");
        System.out.println("111 : " + index);

        // 没有找到,返回(-(插入点) - 1)
        index = Collections.binarySearch(list, "zzz");
        System.out.println("222 : " + index);

        // 查找列表最大值
        String max = Collections.max(list);
        System.out.println("333 : " + max);

        // 根据元素的自然顺序对列表按升序进行排序
        Collections.sort(list);
        System.out.println("444 : " + list);

        // 根据给定比较器对指定列表进行排序
        Collections.sort(list, new StrLenComparator());
        System.out.println("555 : " + list);

        // reverseOrder()强行逆转实现了 Comparable 接口的对象 collection 的自然顺序
        Collections.sort(list, Collections.reverseOrder());
        System.out.println("666 : " + list);

        // 反转列表中元素的顺序
        Collections.reverse(list);
        System.out.println("777 : " + list);

        // 随机排列列表的元素
        Collections.shuffle(list);
        System.out.println("888 : " + list);

        // 返回一个空的集合,该集合不可变,如果对该集合进行增删操作,会抛出java.lang.UnsupportedOperationException异常
        List<String> emptyList = Collections.<String> emptyList();
        System.out.println("999 : " + emptyList);

        emptyList = Collections.EMPTY_LIST;
        System.out.println("000 : " + emptyList);
    }
}

class StrLenComparator implements Comparator<String> {

    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

运行结果

111 : 2
222 : -6
333 : zz
444 : [aaa, abcd, kkkkk, y, zz]
555 : [y, zz, aaa, abcd, kkkkk]
666 : [zz, y, kkkkk, abcd, aaa]
777 : [aaa, abcd, kkkkk, y, zz]
888 : [kkkkk, abcd, y, aaa, zz]
999 : []
000 : []

System.out.println(“Reversed List: ” +integers);

Arrays

Arrays包含用来操作数组(比如排序和搜索)的各种方法,同样里面也都是静态方法。如果指定数组引用为
null,则此类中的方法都会抛出 NullPointerException。

package utils;

import java.util.Arrays;
import java.util.List;

public class ArraysDemo {
    public static void main(String[] args) {
        String[] array = { "abc", "ggg", "dh", "zzzz", "o" };

        // 数组转集合,如果试图对集合进行增删操作,则会抛出UnsupportedOprationException异常
        List<String> list = Arrays.asList(array);
        System.out.println("111 : " + list);

        // 数组转字符串
        String string = Arrays.toString(array);
        System.out.println("222 : " + string);

        // 使用二分查找指定元素,找到则返回索引
        int index = Arrays.binarySearch(array, "ccc");
        System.out.println("333 : " + index);

        // 复制数组
        String[] newArray = Arrays.copyOf(array, array.length);
        System.out.println("444 : " + Arrays.asList(newArray));

        // 复制指定范围的数组
        String[] newArrayRange = Arrays.copyOfRange(array, 0, 2);
        System.out.println("555 : " + Arrays.asList(newArrayRange));

        // 判断两个数组是否相等
        boolean equals = Arrays.equals(array, newArray);
        System.out.println("666 : " + equals);

        // 对数组元素进行排序
        Arrays.sort(array);
        System.out.println("777 : " + Arrays.asList(array));

        // 转成字符串
        String arrayString = Arrays.toString(array);
        System.out.println("888 : " + arrayString);

        // 用指定的值替换数组中的每个值
        Arrays.fill(array, "new");
        System.out.println("999 : " + Arrays.asList(array));
    }
}

运行结果

111 : [abc, ggg, dh, zzzz, o]
222 : [abc, ggg, dh, zzzz, o]
333 : -2
444 : [abc, ggg, dh, zzzz, o]
555 : [abc, ggg]
666 : true
777 : [abc, dh, ggg, o, zzzz]
888 : [abc, dh, ggg, o, zzzz]
999 : [new, new, new, new, new]

}

总结

集合可以存储不同类型的对象,并且长度可变

List集合:元素有序,可重复,可存储null元素,如果想要元素不重复,则存入对象需要重写equals方法

  ArrayList:底层数据结构是数组,查询快

  LinkedList:底层数据结构是链表,增删操作快

Set集合:元素无序,不可重复,可存储null元素

  HashSet:底层数据结构是哈希表,存取速度快,元素唯一、线程不同步

  TreeSet:底层数据结构式二叉树,可以对元素进行排序,元素有序、线程不同步

Mpa集合:集合存储的是键值对,键需要保证唯一,否则值将会被覆盖

  HashMap:底层数据结构是哈希表,允许使用null键和null值,线程不同步,但是效率高

  TreeMap:底层数据结构是二叉树,允许使用null键和null值,线程不同步,可以对元素进行排序

工具类:Collections是集合框架中的一个工具类,该类提供了一系列静态的方法来操作集合,而Collection是集合框架的一个顶层接口,二者直接或间接的关系。

输出:

Original list: [5, 10, 0, -1]

Sorted list: [-1, 0, 5, 10]

Reversed List: [10, 5, 0, -1]

数据存储在HashSet中

private void sortNumbersInHashSet() {

Set integers = new HashSet<>();

integers.add;

integers.add;

integers.add;

integers.add;

System.out.println(“Original set: ” +integers); // Collections.sort;
This throws error since sort method accepts list not collection

List list = new ArrayList;

Collections.sort;

System.out.println(“Sorted set: “+list);

Collections.sort(list, Collections.reverseOrder;

System.out.println(“Reversed set: ” +list);

}

输出:

Original set: [0, -1, 5, 10]Sorted set: [-1, 0, 5, 10]Reversed set:
[10, 5, 0, -1]

在这个例子中存储在HashSet中),我们看到HashSet被转换为ArrayList进行排序。在不转换为ArrayList的情况下,可以通过使用TreeSet来实现排序。TreeSet是Set的另一个实现,并且在使用默认构造函数创建Set时,使用自然排序进行排序。

数据存储在TreeSet中

private void sortNumbersInTreeSet() {

Set integers = new TreeSet<>();

integers.add;

integers.add;

integers.add;

integers.add;

System.out.println(“Original set: ” + integers);

System.out.println(“Sorted set: “+ integers);

Set reversedIntegers = new TreeSet(Collections.reverseOrder;

reversedIntegers.add;

reversedIntegers.add;

reversedIntegers.add;

reversedIntegers.add;

System.out.println(“Reversed set: ” + reversedIntegers);

}

输出:

Original set: [-1, 0, 5, 10]Sorted set: [-1, 0, 5, 10]Reversed set:
[10, 5, 0, -1]

在这种情况下,“Original set:”和“Sorted
set:”两者相同,因为我们已经使用了按排序顺序存储数据的TreeSet,所以在插入后不用排序。

到目前为止,一切都如期工作,排序似乎是一件轻而易举的事。现在让我们尝试在各个Collection中存储自定义对象(比如Student),并查看排序是如何工作的。

数据(Student对象)存储在ArrayList中

private void sortStudentInArrayList() {

List students = new ArrayList<>();

Student student1 = createStudent(“Biplab”, 3);

students.add;

Student student2 = createStudent(“John”, 1);

students.add;

Student student3 = createStudent;

students.add;

Student student4 = createStudent(“Biplab”, 2);

students.add;

System.out.println(“Original students list: ” + students);

Collections.sort;// Error here

System.out.println(“Sorted students list: ” + students);

Collections.sort(students, Collections.reverseOrder;

System.out.println(“Reversed students list: ” + students);

}private Student createStudent(String name, int no) {

Student student = new Student();

student.setName;

student.setNo; return student;

}public class Student {

String name; int no; public String getName() { return name;

} public int getNo() { return no;

} public void setName(String name) { this.name = name;

} public void setNo { this.no = no;

}

@Override public String toString() { return “Student{” + “name='” + name

  • ”’ + “, no=” + no + ‘}’;

}

}

这会抛出编译时错误,并显示以下错误消息:

sort(java.util.List<T>)in Collections cannot be appliedto
(java.util.List<com.example.demo.dto.Student>)reason: no instance
of type variable T exist so that Student conforms to Comparable<?
Super T>

为了解决这个问题,要么Student类需要实现Comparable,要么需要在调用Collections.sort时传递Comparator对象。在整型情况下,排序方法没有错误,因为Integer类实现了Comparable。让我们看看,实现Comparable或传递Comparator如何解决这个问题,以及排序方法如何实现Collection排序。

使用Comparable排序

package com.example.demo.dto;public class Student implements Comparable{

String name; int no; public String getName() { return name;

} public int getNo() { return no;

} public void setName(String name) { this.name = name;

} public void setNo { this.no = no;

} @Override

public String toString() { return “Student{” + “name='” + name + ”’ +
“, no=” + no + ‘}’;

} @Override

public int compareTo { return this.getName().compareTo( o).getName;

}

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’Biplab’, no=3},
Student{name=’Biplab’, no=2}, Student{name=’John’, no=1},
Student{name=’Pal’, no=5}]

Reversed students list: [Student{name=’Pal’, no=5},
Student{name=’John’, no=1}, Student{name=’Biplab’, no=3},
Student{name=’Biplab’, no=2}]

在所有示例中,为了颠倒顺序,我们使用“Collections.sort(students,
Collections.reverseOrder()”,相反的,它可以通过改变compareTo方法的实现而达成目标,且compareTo
的实现看起来像这样 :

@Override

public int compareTo { return ( o).getName.compareTo(this.getName;

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’Pal’, no=5}, Student{name=’John’,
no=1}, Student{name=’Biplab’, no=3}, Student{name=’Biplab’, no=2}]

Reversed students list: [Student{name=’Biplab’, no=3},
Student{name=’Biplab’, no=2}, Student{name=’John’, no=1},
Student{name=’Pal’, no=5}]

如果我们观察输出结果,我们可以看到“Sorted students
list:”以颠倒的顺序输出学生信息。

到目前为止,对学生的排序是根据学生的“name”而非“no”来完成的。如果我们想按“no”排序,我们只需要更改Student类的compareTo实现,如下所示:

@Override

public int compareTo { return (this.getNo() < o).getNo() ? -1 :
(this.getNo() == o).getNo() ? 0 : 1));

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’John’, no=1},
Student{name=’Biplab’, no=2}, Student{name=’Biplab’, no=3},
Student{name=’Pal’, no=5}]

Reversed students list: [Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=3}, Student{name=’Biplab’, no=2},
Student{name=’John’, no=1}]

在上面的输出中,我们可以看到“no”2和3的两名学生具有相同的名字“Biplab”。

现在假设我们首先需要按“name”对这些学生进行排序,如果超过1名学生具有相同姓名的话,则这些学生需要按“no”排序。为了实现这一点,我们需要改变compareTo方法的实现,如下所示:

@Override

public int compareTo { int result = this.getName().compareTo(
o).getName; if(result == 0) {

result = (this.getNo() < o).getNo() ? -1 : (this.getNo() ==
o).getNo() ? 0 : 1));

} return result;

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’Biplab’, no=2},
Student{name=’Biplab’, no=3}, Student{name=’John’, no=1},
Student{name=’Pal’, no=5}]

Reversed students list: [Student{name=’Pal’, no=5},
Student{name=’John’, no=1}, Student{name=’Biplab’, no=3},
Student{name=’Biplab’, no=2}]

使用Comparator排序

为了按照“name”对Students进行排序,我们将添加一个Comparator并将其传递给排序方法:

public class Sorting { private void sortStudentInArrayList() {

List students = new ArrayList<>();

Student student1 = createStudent(“Biplab”, 3);

students.add;

Student student2 = createStudent(“John”, 1);

students.add;

Student student3 = createStudent;

students.add;

Student student4 = createStudent(“Biplab”, 2);

students.add;

System.out.println(“Original students list: ” + students);

Collections.sort(integers, new NameComparator;

System.out.println(“Sorted students list: ” + students);

}

}public class Student {

String name; int no; public String getName() { return name;

} public int getNo() { return no;

} public void setName(String name) { this.name = name;

} public void setNo { this.no = no;

} @Override

public String toString() { return “Student{” + “name='” + name + ”’ +
“, no=” + no + ‘}’;

}

}class NameComparator implements Comparator { @Override

public int compare(Student o1, Student o2) { return
o1.getName().compareTo(o2.getName;

}

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’Biplab’, no=3},
Student{name=’Biplab’, no=2}, Student{name=’John’, no=1},
Student{name=’Pal’, no=5}]

同样,如果我们想按照“no”对Students排序,那么可以再添加一个Comparator(NoComparator.java),并将其传递给排序方法,然后数据将按“no”排序。

现在,如果我们想通过“name”然后“no”对学生进行排序,那么可以在compare内结合两种逻辑来实现。

class NameNoComparator implements Comparator { @Override

public int compare(Student o1, Student o2) { int result =
o1.getName().compareTo(o2.getName; if(result == 0) {

result = o1.getNo() < o2.getNo() ? -1 : o1.getNo() == o2.getNo() ? 0
: 1;

} return result;

}

}

输出:

Original students list: [Student{name=’Biplab’, no=3},
Student{name=’John’, no=1}, Student{name=’Pal’, no=5},
Student{name=’Biplab’, no=2}]

Sorted students list: [Student{name=’Biplab’, no=2},
Student{name=’Biplab’, no=3}, Student{name=’John’, no=1},
Student{name=’Pal’, no=5}]

数据(Students对象)存储在Set中

在Set的这个情况下,我们需要将HashSet转换为ArrayList,或使用TreeSet对数据进行排序。此外,我们知道要使Set工作,equals和hashCode()方法需要被覆盖。下面是基于“no”字段覆盖equals和hashcode的例子,且这些是IDE自动生成的代码。与Comparable或Comparator相关的其他代码与ArrayList相同。

@Override

public boolean equals { if (this == o) return true; if (o == null ||
getClass() != o.getClass return false;

Student student = o; return no == student.no;

} @Override

public int hashCode() { return Objects.hash;

}

数据(Students对象)存储在数组中

为了对数组排序,我们需要做与排序ArrayList相同的事情(要么执行Comparable要么传递Comparable给sort方法)。在这种情况下,sort方法是“Arrays.sort(Object[]
a )”而非“Collections.sort”。

private void sortStudentInArray() {

Student [] students = new Student[4];

Student student1 = createStudent(“Biplab”, 3);

students[0] = student1;

Student student2 = createStudent(“John”, 1);

students[1] = student2;

Student student3 = createStudent;

students[2] = student3;

Student student4 = createStudent(“Biplab”, 2);

students[3] = student4;

System.out.print(“Original students list: “); for (Student student:
students) {

System.out.print( student + ” ,”);

}

Arrays.sort;

System.out.print(“nSorted students list: “); for (Student student:
students) {

System.out.print( student +” ,”);

}

Arrays.sort(students, Collections.reverseOrder;

System.out.print(“nReversed students list: ” ); for (Student student:
students) {

System.out.print( student +” ,”);

}

}//Student class// All properties goes here

@Override public int compareTo { int result
=this.getName().compareTo(o).getName; if(result ==0) {

result = (this.getNo() < o).getNo() ? -1 : (this.getNo() ==
o).getNo() ? 0 : 1));

} return result;

}

输出:

Original students list: Student{name=’Biplab’, no=3}
,Student{name=’John’, no=1} ,Student{name=’Pal’, no=5}
,Student{name=’Biplab’, no=2} ,

Sorted students list: Student{name=’Biplab’, no=2}
,Student{name=’Biplab’, no=3} ,Student{name=’John’, no=1}
,Student{name=’Pal’, no=5} ,

Reversed students list: Student{name=’Pal’, no=5} ,Student{name=’John’,
no=1} ,Student{name=’Biplab’, no=3} ,Student{name=’Biplab’, no=2} ,

结论

我们经常对Comparable/Comparator的使用以及何时使用哪个感到困惑。下面是我总结的Comparable/Comparator的使用场景。

Comparator:

当我们想排序一个无法修改的类的实例时。例如来自jar的类的实例。

根据用例需要排序不同的字段时,例如,一个用例需要通过“name”排序,还有个想要根据“no”排序,或者有的用例需要通过“name和no”来排序。

Comparable:

应该在定义类时知道排序的顺序时使用,并且不会有其他任何需要使用Collection
/数组来根据其他字段排序的情况。

注意:我没有介绍Set /
List的细节。我假设读者已经了解了这些内容。此外,没有提供使用Set /
array进行排序的详细示例,因为实现与ArrayList非常相似,而ArrayList我已经详细给出了示例。

小编整理了一些java进阶学习资料和面试题,需要资料的请加JAVA高阶学习Q群:664389243
这是小编创建的java高阶学习交流群,加群一起交流学习深造。群里也有小编整理的2019年最新最全的java高阶学习资料!

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图