对象排序 — java 7 collection详解(三)

来源:互联网 发布:武汉ui知乎 编辑:程序博客网 时间:2024/06/07 03:25

转载自:http://peiquan.blog.51cto.com/7518552/1293970

 承接着前两篇collection讲解,这一次继续谈一下在collection里的对象排序。在谈对象排序排序之前,先来了解一下,在java里是如何判断两个对象是相等(同)的。

一、基础补充

    在根类Object里提供里两个方法hasCode()和equal()方法,如果两个对象相等,则hasCode的返回值应一致,equal()应该返回true。现在来看一下java API,了解一下这两个方法的定义:

    hasCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。

    hasCode的常规协定是:

  • 在 Java 应用程序执行期间,如果对象进行equals比较时没有被修改任何信息,那么同一对象多次调用 hashCode方法,必须一致地返回相同的整数。同一程序的两次运行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法判断出两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode方法 要求一定生成不同的整数结果。但是,应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

    equals(object obj):指示某个对象obj是否与此对象“相等”。

    equals方法需要遵循一下规则:

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
  • 传递性:对于任何非空引用值 xy 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么x.equals(z) 应返回 true
  • 一致性:对于任何非空引用值 x 和 y,如果对象进行equals比较时没有被修改任何信息,多次调用 x.equals(y)始终返回 true 或始终返回 false
  • 对于任何非空引用值 xx.equals(null) 都应返回 false

    简单来说就是,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。 另外,对于hasCode()和equals()方法,如果重载了其中一个,一般来说都需要重载另外一个,即两个方法都要重载。

    对于hasCode()和 equals()方法,初看之下,可能觉得有点难懂。其实,这两个方法的重写很简单,举个例子:重写Student类的hasCode和equals方法(假设Student类只有两个成员变量,班别和姓名),

public class Student {private String grade;private String name;public Student(String grade, String name) {super();this.grade = grade;this.name = name;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((grade == null) ? 0 : grade.hashCode());result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (!(obj instanceof Student))return false;Student other = (Student) obj;if (grade == null) {if (other.grade != null)return false;} else if (!grade.equals(other.grade))return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}public static void main(String[] args) {Student stu1 = new Student("7班", "张三");Student stu2 = new Student("7班", "张三");Student stu3 = new Student("7班", "李四");System.out.println(stu1.equals(stu2));//trueSystem.out.println(stu1.hashCode() == stu2.hashCode());//trueSystem.out.println(stu1.equals(stu3));//falseSystem.out.println(stu1 == stu2);//false}}

    通过Student来看,重写equals方法时,其实就是需要去判断两个对象所携带的信息是否一致。至于hasCode()方法,可能对prime = 31有点迷茫,为什么要用31呢??用别的数字行吗??为此我搜索过相关信息,笼统地说,31是一个魔法数,利于方便地为当前的对象分配内存地址(这个数字不大不小) 减少hash冲突 ,提高查询速度等。

   同样注意到是:System.out.println(stu1 == stu2);在这里引入这个判断输出,只是想指出:equals()方法判断的是对象所包含的的数据是否一致,而==判断是否为同一个对象的应用,详情可以查看这里。

  温馨提示:对于eclipse和netbeans都提供了比较完善的关于equals和hasCode的重写方法,以eclipse为例,可以按快捷键ctrl+shift+S,接着选择重写equals()和hasCode()方法。

二、Comparable接口

    Comparable定义了类对象的排序方式,compareTo()方法定义了排序的逻辑,默认是按照类的自然排序逻辑来排序。如下表,总结了一些常用类的默认排序方式:

Class

默认的排序方式

Class

默认的排序方式

Byte

升序(按照数字由小到大)

Float

升序

Character

升序

BigInteger

升序

Long

升序

BigDecimal

升序

Integer

升序

Boolean

Boolean.FALSE < Boolean.TRUE

Short

升序

File

默认使用系统路径的排序方式

Double

升序

String

字典排序

Date

按照日期先后排序

  

   上表所有类都实现了Comparable接口,都可以使用Collections.sort()和Arrays.sort()进行排序,如下所示:

   Collections.sort(list) ;Arrays.sort(array);

    注意的是,所有待排序的对象都应该是可比较(大小)的,如果对象之间不能相互比较(大小),会抛出ClassCastException。同时排序的元素里不能有null,否则会抛出NullPointerException。给一个简答的例子—将string数组排序输出:

String[] fruits = new String[]{"orange","Apple","pair","watermelon"};Arrays.sort(fruits);for (String str : fruits) {System.out.print(str + " ");}


    其输出如下:Apple orange pair watermelon。

    如果需要实现自定义类的排序,则可以选择实现Comparable接口,Comparable接口的代码如下:

public interface Comparable<T> {    public int compareTo(T o);}

     在Comparable接口里只定义了compareTo(obj)方法,用来获得此对象与指定对象obj的顺序。如果该对象小于、等于或大于指定对象obj,则分别返回负整数、零或正整数。如果两个对象不能相互比较(大小),此方法会抛出ClassCastException。

    Demo:Student.java演示如何实现Comparable接口,这里按照Id的升序输出,

public class Student implements Comparable<Student> {private int Id;private int score;private String name;public Student(int id, int score, String name) {super();if (name == null) {throw new NullPointerException();}Id = id;this.score = score;this.name = name;}//按照Id值得大小,升序输出@Overridepublic int compareTo(Student stu) {return Id > stu.Id ? 1 : (Id < stu.Id ? -1 : 0);
}//为了输出的方便和排版,重写toString方法,只是输出Id的值@Overridepublic String toString() {return String.valueOf(Id);}public static void main(String[] args) {Student[] stu = {new Student(102, 60, "张三"),new Student(101, 70, "李四"),new Student(103, 90, "王五")};List<Student> stuList = Arrays.asList(stu);//未排序前的对象顺序System.out.println("未排序前的对象顺序:" + stuList);//以自然逻辑排序的对象顺序,即Comparable定义的排序逻辑(Id值得大小)Collections.sort(stuList);System.out.println("自然逻辑排序的对象顺序:" + stuList);}}

    其输出如下:image

    在Student.java里实现了Student对象按照Id值大小顺序输出对象,类似得可以定义按照成绩或者名字的自然逻辑顺序输出对象,如下:

    return score > stu.score ? 1:(score < stu.score ? -1:0);

    既然谈到排序,我们再深入一点谈实现Comparable接口需要遵守的规则。在java里,倡导所有实现Comparable接口的类都应该保持与equals()一致的排序顺序(也可以不保持)。什么意思呢??简单得说,对一个类里的任意两个对象obj1和obj2,obj.compareTo(obj2) == 0 与 obj1.equals(obj2)应该具有相同的boolean值。换一种说法就是,在实现Comparable接口的类里应该重写equals()方法(伴随着需要重写hasCode()方法),如上面的Student.java,我们这样重写equals()与hasCode()方法:

@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + Id;result = prime * result + name.hashCode();result = prime * result + score;return result;}@Overridepublic boolean equals(Object obj) {if (!(obj instanceof Student))return false;Student other = (Student) obj;return (Id == other.Id) && (score == other.score) && (name.equals(other.name));}

     这样Student.java的逻辑就算比较完善了,如果你曾经重写过equals和hasCode方法,会发现这里的重写方法里没有考虑到null异常(因为在构造函数里,确保里Student的对象 不会包含null,所有不需要考虑null异常)。温馨提示一下,对于eclipse和netbeans都提供了比较完善的关于equals和hasCode的重写方法,以eclipse为例,可以按快捷键ctrl+shift+S,接着选择重写equals()和hasCode()方法。

三、Comparator接口

    对于没有实现Comparable接口类,如果需要排序输出对象,同样可以选择提供Comparator的实现,Comparator接口代码如下:

public interface Comparator<T> {    int compare(T o1, T o2);}

    在Comparator接口也只是定义里一个compare方法,用来定义对象的排序逻辑,如果对象o1小于、等于或大于对象o2,则分别返回负整数、零或正整数。如果两个对象不能相互比较(大小),此方法会抛出ClassCastException。

   Demo:Student2.java,演示了如何使用Comparator定义输出对象的顺序:

public class Student2 {private int Id;private int score;private String name;public Student2(int id, int score, String name) {super();if (name == null) {throw new NullPointerException();}this.Id = id;this.score = score;this.name = name;}//为了输出的方便和排版,重写toString方法,只是输出Id的值@Overridepublic String toString() {return String.valueOf(Id);}public static class OrderById implements Comparator<Student2>{@Overridepublic int compare(Student2 stu1, Student2 stu2) {return stu1.Id > stu2.Id ? 1:(stu1.Id < stu2.Id ? -1:0);}}public static void main(String[] args) {Student2[] stu = {new Student2(102, 60, "张三"),new Student2(101, 70, "李四"),new Student2(103, 90, "王五")};List<Student2> stuList = Arrays.asList(stu);//未排序前的对象顺序System.out.println("未排序前的对象顺序:" + stuList);//以Comparator定义的排序输出对象(Id值得大小)Collections.sort(stuList,new Student2.OrderById());System.out.println("自然逻辑排序的对象顺序:" + stuList);}}

    其输出如下:image

    相对Comparable的实现,Comparator是在类里通过一个静态类来定义输出对象的排序逻辑(按照Id的大小,升序输出;类似的可以定义score和name的排序逻辑),还有一点不同的是sort()函数的使用:

    Collections.sort(stuList,new Student2.OrderById());

    其他的实现基本一致,那是否可以认为Comparator与Comparable是等同的呢??如果只是定义一种排序逻辑,选择Comparator和Comparable都可以,习惯上选择Comparable。可需要多种排序逻辑输出对象的时候,就需要Comparable和Comparator的配合使用。为了更为详细的演示排序,这次同样实现对象的逆序输出,而且以内部类的形式实现Comparator:

Demo:Student.java

public class Student implements Comparable<Student> {private int Id;private int score;private String name;public Student(int id, int score, String name) {super();if (name == null) {throw new NullPointerException();}Id = id;this.score = score;this.name = name;}// 为了输出的方便和排版,重写toString方法,只是输出Id的值@Overridepublic String toString() {return String.valueOf(Id);}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + Id;result = prime * result + name.hashCode();result = prime * result + score;return result;}@Overridepublic boolean equals(Object obj) {if (!(obj instanceof Student))return false;Student other = (Student) obj;return (Id == other.Id) && (score == other.score) && (name.equals(other.name));}// 按照Id值得大小,升序输出@Overridepublic int compareTo(Student stu) {return Id > stu.Id ? 1 : (Id < stu.Id ? -1 : 0);// return score > stu.score ? 1:(score < stu.score ? -1:0);}// 按照分数的高低排序static final Comparator<Student> ORDERBYSCORE_ORDER = new Comparator<Student>() {@Overridepublic int compare(Student stu1, Student stu2) {return stu1.score > stu2.score ? 1 : (stu1.score < stu2.score ? -1 : 0);}};// 按姓名的字典顺序排序static final Comparator<Student> ORDERBYNAME_ORDER = new Comparator<Student>() {@Overridepublic int compare(Student stu1, Student stu2) {return stu1.name.compareTo(stu2.name);}};public static void main(String[] args) {Student[] stu = { new Student(102, 60, "张三"), new Student(101, 70, "李四"),new Student(103, 90, "王五") };List<Student> stuList = Arrays.asList(stu);// 未排序前的对象顺序System.out.println("未排序前的对象顺序:" + stuList);// 以自然逻辑排序的对象顺序,Comparable定义的逻辑Collections.sort(stuList);System.out.println("自然逻辑排序的对象顺序:" + stuList);// 降序排序输出对象Collections.sort(stuList, Collections.reverseOrder());System.out.println("降序排序输出对象:" + stuList);// 使用Comparator顺序输出对象,OederByScoreCollections.sort(stuList, ORDERBYSCORE_ORDER);System.out.println("OederByScore顺序输出对象" + stuList);// 使用另一个Comparator顺序输出对象,OederByNameCollections.sort(stuList, ORDERBYNAME_ORDER);System.out.println("OederByName顺序输出对象" + stuList);}}


    其输出如下:

未排序前的对象顺序:[102, 101, 103] 
自然逻辑排序的对象顺序:[101, 102, 103] 
降序排序输出对象:[103, 102, 101] 
OederByScore顺序输出对象[102, 101, 103] 
OederByName顺序输出对象[102, 101, 103]

    如上代码,可以实现了多种排序逻辑的对象输出,同时注意到逆序输出的方式:

    Collections.sort(stuList, Collections.reverseOrder());

    最后提一下的是,在Demo Student里,都是使用Collections.sort()方法来输出对象的,同样地也可以使用Arrays.sort()方法来输出对象,类似的Arrays.sort()也有两种重载形式:

    Arrays.sort(array);

    Arrays.sort(array,Comparator);

    其用法和Student提到的方法一致。



0 0
原创粉丝点击