Java如何对数组、List、Map或Stream进行排序
学习如何使用Comparator、Comparable和新的lambda表达式对Java中的原始类型和自定义对象的集合、列表和映射进行排序。我们将学习如何升序和降序排序。
//对数组进行排序 Arrays.sort( arrayOfItems ); Arrays.sort( arrayOfItems, Collections.reverseOrder() ); Arrays.sort(arrayOfItems, 2, 6); Arrays.parallelSort(arrayOfItems); //对List进行排序 Collections.sort(numbersList); Collections.sort(numbersList, Collections.reverseOrder()); //对Set进行排序 Set to List -> Sort -> List to Set Collections.sort(numbersList); //对Map进行排序 TreeMap<Integer, String> treeMap = new TreeMap<>(map); unsortedeMap.entrySet() .stream() .sorted(Map.Entry.comparingByValue()) .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue())); //Java 8 Lambda表达式 Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName()); Collections.sort(list, nameSorter); Collections.sort(list, Comparator.comparing(Employee::getName)); //按照分组排序 Collections.sort(list, Comparator .comparing(Employee::getName) .thenComparing(Employee::getDob));
1.对象List的排序
要对对象List进行排序,有两种常用方法,即Comparable和Comparator接口。在接下来的示例中,我们将以不同方式对Employee对象的集合进行排序。
public class Employee implements Comparable<Employee> { private Long id; private String name; private LocalDate dob; }
1.1 Comparable接口
Comparable接口使实现它的类自然排序。这使得这些类可以与它们的其他实例进行比较。
实现Comparable接口的类必须重写compareTo()方法,在其中可以指定相同类的两个实例之间的比较逻辑。
- 实现Comparable接口的对象可以在使用Collections.sort()和Arrays.sort() API时自动排序。
- 实现此接口的对象将在放入排序映射(作为键)或排序集(作为元素)时自动排序。
- 强烈建议(尽管不是必需的)自然排序与equals()方法一致。几乎所有实现Comparable的Java核心类都具有与equals()一致的自然排序。
//自然排序 ArrayList<Employee> list = new ArrayList<>(); //添加employees.. Collections.sort(list);
要以相反的顺序对列表进行排序,最好的方法是使用Comparator.reversed() API来强制反向排序。
// 逆排序 ArrayList<Employee> list = new ArrayList<>(); //添加employees.. Collections.sort(list, Comparator.reversed());
1.2 Comparator接口
当不寻求自然排序时,我们可以利用Comparator接口来应用自定义排序行为。
Comparator不需要修改类的源代码。我们可以在实现Comparator接口的单独类中创建比较逻辑,并重写其compare()方法。
在排序时,我们将此比较器的实例与自定义对象列表一起传递给sort()方法。
例如,我们想按员工的名字对员工列表进行排序,而自然排序已经通过id字段实现。因此,要根据名称字段进行排序,我们必须使用Comparator接口编写自定义排序逻辑。
public class NameSorter implements Comparator<Employee> { @Override public int compare(Employee e1, Employee e2) { return e1.getName().compareToIgnoreCase( e2.getName() ); } }
请注意,在给定示例中在sort()方法中使用NameSorter作为第二个参数。
ArrayList<Employee> list = new ArrayList<>(); //添加employees Collections.sort(list, new NameSorter());
要进行反向排序,我们只需要在Comparator实例上调用reversed()方法。
ArrayList<Employee> list = new ArrayList<>(); //添加employees Collections.sort(list, new NameSorter().reversed());
1.3 使用Lambda表达式进行排序
Lambda表达式有助于即时编写比较器实现。我们不需要创建一个单独的类来提供一次性的比较逻辑。
Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName()); Collections.sort(list, nameSorter);
1.4 按组排序
要在不同字段上对对象集合应用SQL样式排序(分组排序),可以使用多个比较器链。可以使用Comparator.comparing()和Comparator.thenComparing()方法创建这些比较器链。
例如,我们可以按姓名对员工列表进行排序,然后再按年龄进行排序。
ArrayList<Employee> list = new ArrayList<>(); Collections.sort(list, Comparator .comparing(Employee::getName) .thenComparing(Employee::getDob));
2. 对数组进行排序
使用java.util.Arrays.sort()方法以各种方式对给定的数组进行排序。sort()是一个重载的方法,可以接受各种类型的方法参数。
此方法实现了一种双轴快速排序算法,对所有数据集提供O(n log(n))性能,通常比传统的(单轴)快速排序实现更快。
2.1 升序排列
使用Arrays.sort()方法以升序对整数数组进行排序的Java程序。
//未排序 array Integer[] numbers = new Integer[] { 15, 11, ... }; //排序 array Arrays.sort(numbers);
2.2 降序排列
Java提供了Collections.reverseOrder()比较器,以在一行中反转默认的排序行为。我们可以使用此比较器以降序对数组进行排序。请注意,数组中的所有元素必须由指定的比较器相互比较。
//未排序array Integer[] numbers = new Integer[] { 15, 11, ... }; //降序排序 array Arrays.sort(numbers, Collections.reverseOrder());
2.3 对数组范围进行排序
Arrays.sort()方法是一个重载的方法,还接受两个额外的参数,即fromIndex(包含)和toIndex(不包括)。
提供了上述参数后,数组将在从fromIndex位置到toIndex位置的提供范围内排序。
下面是一个示例,将数组从元素9到18进行排序。即{9, 55, 47, 18}将被排序,其余元素将不受影响。
//未排序array Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 }; //排序 array Arrays.sort(numbers, 2, 6); //打印 System.out.println(Arrays.toString(numbers)); // [15, 11, 9, 18, 47, 55, 1123, 520, 366, 420]
2.4 并行排序
Java 8引入了许多用于并行处理数据集和流的新API。其中一个API是Arrays.parallelSort()。
parallelSort()方法将数组分成多个子数组,每个子数组在不同的线程中使用Arrays.sort()进行排序。最后,所有排序后的子数组将合并成一个排序后的数组。
parallelSort()和sort()这两个API的输出最终是相同的。只是利用Java并发的问题。
//并行排序完整数组 Arrays.parallelSort(numbers); //并行排序数组范围 Arrays.parallelSort(numbers, 2, 6); //以逆序方式并行排序数组 Arrays.parallelSort(numbers, Collections.reverseOrder());
3.对Set进行排序
在Java中,没有直接支持对Set进行排序。要对Set进行排序,请按照以下步骤操作:
- 将Set转换为List。
- 使用Collections.sort() API对List进行排序。
- 将List转换回Set。
//未排序set HashSet<Integer> numbersSet = new LinkedHashSet<>(); //with Set items List<Integer> numbersList = new ArrayList<Integer>(numbersSet) ; //set -> list //排序 list Collections.sort(numbersList); //排序set numbersSet = new LinkedHashSet<>(numbersList); //list -> set
4.对map进行排序
Map是键值对的集合。因此,从逻辑上讲,我们可以按键或按值对Map进行排序。
4.1 按键排序
通过将所有Map条目添加到TreeMap对象中,可以最好且最有效地按键对Map进行排序。TreeMap始终按排序顺序存储键。
//未排序Map HashMap<Integer, String> map = new HashMap<>(); //根据key排序map TreeMap<Integer, String> treeMap = new TreeMap<>(map);
4.2 按值排序
在Java 8中,Map.Entry类具有用于帮助我们按值对Map进行排序的静态方法comparingByValue()。
comparingByValue()方法返回一个比较器,按值上的Map.Entry自然排序。
HashMap<Integer, String> unSortedMap = new HashMap<>(); //Unsorted Map //LinkedHashMap保持元素插入的顺序。 LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>(); unSortedMap.entrySet() .stream() .sorted(Map.Entry.comparingByValue()) .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));
5.总结
在上述示例中,我们学习了如何对数组、列表、映射和集合进行排序。
我们看到了不同初始化和使用Comparator接口的方法,包括lambda表达式。我们还学会了如何有效地使用Comparator接口。