学习如何使用比较器、可比较器和新的lambda表达式对一个JavaSet 、List 和Map 的原始类型和自定义对象进行排序。我们还将学习升序和降序的排序方法。
//Sorting an array
Arrays.sort( arrayOfItems );
Arrays.sort( arrayOfItems, Collections.reverseOrder() );
Arrays.sort(arrayOfItems, 2, 6);
Arrays.parallelSort(arrayOfItems);
//Sorting a List
Collections.sort(numbersList);
Collections.sort(numbersList, Collections.reverseOrder());
//Sorting a Set
Set to List -> Sort -> List to Set
Collections.sort(numbersList);
//Sorting a 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));
//Group By Sorting
Collections.sort(list, Comparator
.comparing(Employee::getName)
.thenComparing(Employee::getDob));
1.对一个对象列表进行排序
要对一个对象列表进行排序,我们有两种流行的方法,即比较器和比较器接口。在接下来的例子中,我们将以不同的方式对一个Employee 对象的集合进行排序。
public class Employee implements Comparable<Employee> {
private Long id;
private String name;
private LocalDate dob;
}
1.1.可比接口
[Comparable](https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) 接口可以对实现它的类进行自然排序。这使得这些类与它的其他实例具有可比性。
实现Comparable 接口的类必须覆盖compareTo() 方法,在该方法中,它可以指定同一类的两个实例之间的比较逻辑。
- 实现了Comparable接口的对象的列表(和数组)可以通过
Collections.sort()和Arrays.sort()API自动进行排序。 - 实现该接口的对象在被放入一个排序的地图(作为键)或排序的集合(作为元素)时将被自动排序。
- 强烈建议(尽管不是必须)自然排序与
[equals()](https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/)方法一致。几乎所有实现Comparable的Java核心类的自然排序都与equals()一致。
ArrayList<Employee> list = new ArrayList<>();
//add employees..
Collections.sort(list);
要对列表进行反向排序,最好的方法是使用强加反向排序的*Comparator.reversed()*API。
ArrayList<Employee> list = new ArrayList<>();
//add employees..
Collections.sort(list, Comparator.reversed());
1.2.比较器接口
当不寻求自然排序时,我们可以借助于比较器接口来应用自定义的排序行为。
Comparator ,不需要修改类的源代码。我们可以在一个单独的类中创建比较逻辑,该类实现了Comparator 接口并重写了其compare() 方法。
在排序过程中,我们将这个比较器的一个实例和自定义对象的列表一起传递给sort() 方法。
例如,我们想按照员工的名字来排序,而自然排序已经通过id 字段实现。因此,为了对姓名字段进行排序,我们必须使用比较器接口编写自定义排序逻辑。
import java.util.Comparator;
public class NameSorter implements Comparator<Employee>;
{
@Override
public int compare(Employee e1, Employee e2)
{
return e1.getName().compareToIgnoreCase( e2.getName() );
}
}
请注意,在给定的例子中,NameSorter在*sort()*方法中作为第二个参数使用。
ArrayList<Employee> list = new ArrayList<>();
//add employees to list
Collections.sort(list, new NameSorter());
要进行反向排序,我们只需要在比较器实例上调用*reversed()*方法。
ArrayList<Employee> list = new ArrayList<>();
//add employees to list
Collections.sort(list, new NameSorter().reversed());
1.3.用Lambda表达式进行排序
兰姆达表达式有助于在飞行中编写Comparator 实现。我们不需要创建一个单独的类来提供一次性的比较逻辑。
Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName());
Collections.sort(list, nameSorter);
1.4.按组排序
为了在不同字段的对象集合上应用SQL风格的排序**(group by sort**),我们可以在一个链中使用多个比较器。这种比较器的连锁可以使用*Comparator.comparing()和Comparator.thenComparing()*方法创建。
例如,我们可以按姓名对雇员列表进行排序,然后再按年龄排序。
ArrayList<Employee> list = new ArrayList<>();
//add employees to list
Collections.sort(list, Comparator
.comparing(Employee::getName)
.thenComparing(Employee::getDob));
2.对一个数组进行排序
使用java.util.Arrays.sort() 方法以各种方式对一个给定的数组进行排序。*sort()*是一个重载方法,它接受各种类型作为方法参数。
这个方法实现了双支点Quicksort排序算法,在所有数据集上提供了O(n log(n))的性能,通常比传统的(单支点)Quicksort实现要快。
2.1.升序排序
使用*Arrays.sort()*方法对一个整数阵列进行升序排序的Java程序。
//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, ... };
//Sort the array
Arrays.sort(numbers);
2.2.降序排序
Java提供了Collections.reverseOrder() 比较器,可以在一行中逆转默认的排序行为。我们可以使用这个比较器对数组进行降序排序。
注意,数组中的所有元素必须通过指定的比较器进行相互比较。
//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, ... };
//Sort the array in reverse order
Arrays.sort(numbers, Collections.reverseOrder());
2.3.阵列范围的排序
Arrays.sort() 方法是一个重载方法,需要两个额外的参数,即:fromIndex (包括)和toIndex (排除)。
当提供上述参数时,数组将在所提供的范围内进行排序,从位置fromIndex 到位置toIndex 。
下面是一个将数组从元素9到18排序的例子,即{9, 55, 47, 18}将被排序,其余的元素不会被触及。
//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 };
//Sort the array
Arrays.sort(numbers, 2, 6);
//Print array to confirm
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并发性的问题。
//Parallel sort complete array
Arrays.parallelSort(numbers);
//Parallel sort array range
Arrays.parallelSort(numbers, 2, 6);
//Parallel sort array in reverse order
Arrays.parallelSort(numbers, Collections.reverseOrder());
3.对一个集合进行排序
在Java中没有直接支持对Set 的排序。要对一个Set 进行排序,请遵循以下步骤。
- 将
Set转换为List。 - 使用
Collections.sort()API对List进行排序。 - 将
List转换回Set。
//Unsorted set
HashSet<Integer> numbersSet = new LinkedHashSet<>(); //with Set items
List<Integer> numbersList = new ArrayList<Integer>(numbersSet) ; //set -> list
//Sort the list
Collections.sort(numbersList);
//sorted set
numbersSet = new LinkedHashSet<>(numbersList); //list -> set
4.对地图进行排序
一个Map 是键值对的集合。因此,从逻辑上讲,我们可以用两种方式对地图进行排序,即按键排序或按值排序。
4.1.按键排序
最好和最有效的按键排序的方法是将所有的地图条目添加到TreeMap对象中。TreeMap ,总是以排序的顺序存储键。
HashMap<Integer, String> map = new HashMap<>(); //Unsorted Map
TreeMap<Integer, String> treeMap = new TreeMap<>(map); //Sorted by map keys
4.2.按值排序
在Java 8中,Map.Entry 类有static 方法**comparingByValue()**来帮助我们对Map 的值进行排序。
comparingByValue() 方法返回一个比较器,该比较器按自然顺序对Map.Entry 的值进行比较。
HashMap<Integer, String> unSortedMap = new HashMap<>(); //Unsorted Map
//LinkedHashMap preserve the ordering of elements in which they are inserted
LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>();
unSortedMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));
5.5.总结
在上面给出的例子中,我们学习了对数组、列表、地图和集合进行排序。
我们看到了初始化和使用Comparator 接口的不同方法,包括lambda表达式。我们还学会了如何有效地使用比较器接口。
学习愉快!!