有的时候需要对数组里的element进行排序。当然可以自己编写合适的排序方法,但既然Java包里有自带的Arrays.sort排序方法,在数组元素比较少的时候为何不用?
Sorting an Array 1. 数字排序 int[] intArray = new int[] { 4, 1, 3, -23 };
Arrays.sort(intArray);
输出: [-23, 1, 3, 4]
2. 字符串排序,先大写后小写 String[] strArray = new String[] { "z", "a", "C" };
Arrays.sort(strArray);
输出: [C, a, z]
3. 严格按字母表顺序排序,也就是忽略大小写排序 Case-insensitive sort
Arrays.sort(strArray, String.CASE_INSENSITIVE_ORDER);
输出: [a, C, z]
4. 反向排序, Reverse-order sort
Arrays.sort(strArray, Collections.reverseOrder());
输出:[z, a, C]
5. 忽略大小写反向排序 Case-insensitive reverse-order sort
Arrays.sort(strArray, String.CASE_INSENSITIVE_ORDER);
Collections.reverse(Arrays.asList(strArray));
输出: [z, C, a]
java初学者最常见的错误思想,就是试图去写一些方法来完成数组的排序功能,其实,数组排序功能,在java的api里面早已实现,我们没有必要去重复制造*。
Arrays类有一个静态方法sort,利用这个方法我们可以传入我们要排序的数组进去排序,因为我们传入的是一个数组的引用,所以排序完成的结果也通过这个引用的来更改数组.对于整数、字符串排序,jdk提供了默认的实现,如果要对一个对象数组排序,则要自己实现java.util.Comparator接口。
package demo1.client;
import java.util.Arrays;
import java.util.Comparator;
public class ArraySortDemo {
public void sortIntArray() {
int[] arrayToSort = new int[] { 48, 5, 89, 80, 81, 23, 45, 16, 2 };
System.out.println("排序前");
for (int i = 0; i < arrayToSort.length; i++)
System.out.println(arrayToSort[i]);
// 调用数组的静态排序方法sort
Arrays.sort(arrayToSort);
System.out.println("排序后");
for (int i = 0; i < arrayToSort.length; i++)
System.out.println(arrayToSort[i]);
}
public void sortStringArray() {
String[] arrayToSort = new String[] { "Oscar", "Charlie", "Ryan",
"Adam", "David" };
System.out.println("排序前");
for (int i = 0; i < arrayToSort.length; i++)
System.out.println(arrayToSort[i]);
System.out.println("排序后");
//调用数组的静态排序方法sort
Arrays.sort(arrayToSort);
for (int i = 0; i < arrayToSort.length; i++)
System.out.println(arrayToSort[i]);
}
public void sortObjectArray() {
Dog o1 = new Dog("dog1", 1);
Dog o2 = new Dog("dog2", 4);
Dog o3 = new Dog("dog3", 5);
Dog o4 = new Dog("dog4", 2);
Dog o5 = new Dog("dog5", 3);
Dog[] dogs = new Dog[] { o1, o2, o3, o4, o5 };
System.out.println("排序前");
for (int i = 0; i < dogs.length; i++) {
Dog dog = dogs[i];
System.out.println(dog.getName());
}
Arrays.sort(dogs, new ByWeightComparator());
System.out.println("排序后:");
for (int i = 0; i < dogs.length; i++) {
Dog dog = dogs[i];
System.out.println(dog.getName());
}
}
public static void main(String[] args) {
ArraySortDemo t = new ArraySortDemo();
t.sortIntArray();
t.sortStringArray();
t.sortObjectArray();
}
}
class Dog {
private String name;
private int weight;
public Dog(String name, int weight) {
this.setName(name);
this.weight = weight;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class ByWeightComparator implements Comparator {
public final int compare(Object pFirst, Object pSecond) {
int aFirstWeight = ((Dog) pFirst).getWeight();
int aSecondWeight = ((Dog) pSecond).getWeight();
int diff = aFirstWeight - aSecondWeight;
if (diff > 0)
return 1;
if (diff < 0)
return -1;
else
return 0;
}
}
在我的印象里,对于数组排序,最简单的方法就是使用Arrays.sort(数组a);它也使我理所应当的认为:Arrays这个类完全实现这个sort的方法,而实际上并非如此,先看下sort的原型:public static <T extends Comparable<? super T>> void sort(List<T> list),蓝色部分即是说你要排序的这个集合元素已经实现了接口Comparable,等等,我好像没有实现任何Comparable接口,事实上,String类和所有的包装类对象在JavaAPI中已经实现了这个接口。参考API:
public final class String extends implements , <>,
如果你尝试为没有实现Comparable接口的元素排序,编译器会出错:The generic method sort(List<T>) of type Collections is not applicable for the arguments(ArrayList<你刚编写的类>). The inferred type 你刚编写的类is not a valid substitute for the bounded parameter <T extends Comparable<? super T>>。
此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序,类的 compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。对于类C的每一个 e1 和 e2 来说,当且仅当 (e1.compareTo((Object)e2) == 0) 与 e1.equals((Object)e2) 具有相同的布尔值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使 e.equals(null) 返回 false,e.compareTo(null) 也会抛出 NullPointerException。强烈推荐(虽然不是必需的)使自然排序与 equals 一致。这是因为在使用其自然排序与 equals 不一致的元素(或键)时,没有显式比较器的有序集合(和有序映射表)行为表现“怪异”。尤其是,这样的有序集合(或有序映射表)违背了根据 equals 方法定义的集合(或映射表)的常规协定。例如,如果将两个键 a 和 b 添加到一个没有使用显式比较器的有序集合中,使得 (!a.equals((Object)b) && a.compareTo((Object)b) == 0),则第二个 add 操作返回 false(有序集合的大小没有增加),因为从有序集合的角度来看,a 和 b 是等效的。 实现了comparable接口的类,其对象可以作为SortedMap的key,或者SortedSet的元素。
我们只能在类中实现compareTo()一次,不可能说经常来修改类的代码实现自己想要的排序,因此如果要以不同于compareTo()方法中指定的顺序排序我们的类对象,那么该怎么办呢?大家一定注意到Collections.sort()有一个重载版本。 public static <T> void sort(List<T> list, Comparator<? super T> c)根据指定比较器产生的顺序对指定列表进行排序。此列表内的所有元素都必须可使用指定比较器相互比较(也就是说,对于列表中的任意 e1 和 e2 元素,c.compare(e1, e2) 不得抛出 ClassCastException)。此排序被保证是稳定的:不会因调用 sort 而对相等的元素进行重新排序。 Comparator<? super T> c 第二个参数是实现了接口Comparator的实例对象。注意这两者的区别,Comparable 接口的方法:public int compareTo(T o),绝不会是Object; 只有一个参数,而Comparator接口却有两个参数:int compare(T o1, T o2);
Comparator 可以通过 Collections类的reverseOrder()这个方法强势返回对Comparable排序的倒转,如:Arrays.sort(a, Collections.reverseOrder()); 如果是String,就是按照逆字典顺序进行排序。
这两个接口和集合类本身无关,但通常和集合内的元素有关,因为集合的排序要用到它们中的方法。一个类的实例要想实现排序,必须实现Comparable,或者提供相应的Comparator。说清楚这两者的来历与大概的作用,以及共同点,那么再说下他们两者的区别。只是Comparable(可比较的)是在集合内部定义的方法实现的排序,比如,两个人要比较身高,分辨高矮是人类固有的能力,两个人只要站到一起就能分出谁高谁矮。Comparator(比较器)是在集合外部实现的排序。所以,如想实现排序,就需要在集合外定义Comparator接口的方法compare()或在集合内实现Comparable接口的方法compareTo(),Comparable是一个对象本身就已经支持自比较所需要实现的接口(如String Integer自己就可以完成比较大小操作)而Comparator是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。还有个地方就是,实现Comparable只能定义一种比较方法,但是有时候会对一个集合进行不同的排序方法,此时就可以提供别各种各样的Comparator来对集合排序,而对于要排序的元素不需要更改,所以我觉得Comparator提供了更多的灵活性。使用这种策略来比较时,如何进行比较和两个对象本身无关,而是由第三者(即比较器)来完成的。只要实现Comparator接口,任何一个对象都可能成为一个“比较器”,但比较器并不是比较自己的实例,而是比较另外两个对象,比较器在这里充当“仲裁者”的角色,这也就是为什么compare()方法需要两个参数。比如,两个人要比较谁智商更高,靠他们自身无法进行,这时要借助一个比较器(比如,智商测试题)。那么先看一个非常简单易懂的示例:
import java.util.*;
public class Pockets {
public static void main(String[] args) {
String[] sa = {"nickel", "button", "key", "lint"};
Sorter s = new Sorter(); //新建一个排序器
for(String s2: sa){ System.out.print(s2 + " "); }
Arrays.sort(sa, s); //这个排序器s是与sa这个类对象是相关。
System.out.println();
for(String s2:sa){ System.out.print(s2+" "); }
}
static class Sorter implements Comparator<String>{ //注意红色的部分就是你要去排序的类,如果你文不对题,硬让这个类去排序一个未知的class Dog的类,会报错:The method sort(T[], Comparator<? super T>) in the type Arrays is not applicable for the arguments (UU[], Pockets.Sorter)
public int compare(String a, String b){
return a.compareTo(b);
}
}
}
再在更加复杂的概念上理解这个排序机制,在网上找到一个例子,维护一个简单的员工数据库,每个员工是一个Employee类的实例。Employee类可定义为:
public class Employee {
private String num;
private String name;
private int age;
private int salary;
public Employee(String num, String name) {
this.num = num;
this.name = name;
}
public void setName(String newNum) {
num = newNum;
}
public void setAge(int newAge) {
age = newAge;
}
public void setSalary(int newSalary) {
salary = newSalary;
}
public String getNum() {
return num;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSalary() {
return salary;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Employee Information:");
sb.append("\n");
sb.append("Number:");
sb.append(getNum());
sb.append("\n");
sb.append("Name:");
sb.append(getName());
sb.append("\n");
sb.append("Age:");
sb.append(getAge());
sb.append("\n");
sb.append("Salary:");
sb.append(getSalary());
sb.append("\n");
return sb.toString();
}
}
EmployeeDatabase类创建Employee类的实例,并把它们存入集合:
Java code
import java.util.*;
public class EmployeeDatabase {
public static void main(String[] args) {
List<Employee> allEmployees = new ArrayList<Employee>();
Employee employee1 = new Employee("AAA", "Barack Omaba");
employee1.setAge(50);
employee1.setSalary(9999);
allEmployees.add(employee1);
Employee employee2 = new Employee("BBB", "George Bush");
employee2.setAge(60);
employee2.setSalary(5999);
allEmployees.add(employee2);
System.out.println(allEmployees);
}
}
现在,你需要检索所有员工,并让他们按一定顺序显示(比如按年龄递增),这时需要用到Collections.sort()方法。Collections.sort()有两种策略:一种是让集合元素本身实现Comparable接口,另一种是使用用户提供的比较器(即Comparator)。使用第一种策略时,必须修改元素类的定义,让它实现Comparable,因此,你必须把Employee类修改为:
public class Employee implements Comparable<Employee> {
public int compareTo(Employee another) {
return getAge() - another.getAge();
}
// 其余部分不变
}
说明一下,因为compareTo()方法约定:本对象大于另一个对象时,返回大于0的整数,小于时返回小于0的整数,等于时返回0。所以,可以直接返回两者年龄的差,来实现按年龄比较。这样就可以在main()方法中使用Collections.sort(allEmployees);来对员工按年龄排序了。但是,这种排序是非常不灵活的:第一,需要修改集合元素类Employee,而很多情况下,我们没有办法修改公共的类。第二,没有办法实现多种方式排序,如按编号,按姓名,按薪水等等。这时需要使用另一种策略,即Comparator。Comparator使用其compare()方法返回的整数来比较两个对象,规则和compareTo()一样。如同样实现年龄比较,使用Comparator时,无需修改Employee类,可以在排序的时候定义相应的比较器。使用Collections.sort()方法的另一个版本:
Collections.sort(allEmployees, new Comparator<Employee>() {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
});
这里使用了匿名内部类,实际上相当于先定义一个比较器类,如:
class EmployeeComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
}
再使用:
Collections.sort(allEmployees, new EmployeeComparator());
可以看到,比较器完全独立于元素类Employee,因此可以非常方便地修改排序规则。你还可以定义一系列比较器,供排序时选择使用,如:
// 按薪水升序
class EmployeeSalaryAscendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getSalary() - another.getSalary();
}
}
// 按薪水降序
class EmployeeSalaryDescendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return another.getSalary() - one.getSalary();
}
}
相应的使用方法如:
Collections.sort(allEmployees, new EmployeeSalaryAscendingComparator());
Collections.sort(allEmployees, new EmployeeSalaryDescendingComparator());
使用Comparator时,元素类无需实现Comparable,因此我们保持最初版本的Employee,但实际应用中,可以用Comparable的compareTo()方法来定义默认排序方式,用Comparator定义其他排序方式。实现其compare(T o1,T o2)有一个约定,就是 a.compare(b) == 0 和a.equals(b)要有相同的boolean结果。