详细讲解了Collection、Set、List的相关用法。
详细讲解了Queue、Map的相关用法。
承接着前两篇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方法需要遵循一下规则:
- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。 - 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。 - 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。 - 一致性:对于任何非空引用值
x
和y
,如果对象进行equals比较时没有被修改任何信息,多次调用 x.equals(y) 始终返回true
或始终返回false。
对于任何非空引用值
x
,x.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; } @Override public 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; } @Override public 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)); //true System.out.println(stu1.hashCode() == stu2.hashCode());//true System.out.println(stu1.equals(stu3)); //false System.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{ public int compareTo(T o);}
Demo:Student.java演示如何实现Comparable接口,这里按照Id的升序输出,
public class Student implements Comparable{ 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值得大小,升序输出 @Override public int compareTo(Student stu) { return Id > stu.Id ? 1 : (Id < stu.Id ? -1 : 0);
} //为了输出的方便和排版,重写toString方法,只是输出Id的值 @Override public 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, "王五") }; ListstuList = Arrays.asList(stu); //未排序前的对象顺序 System.out.println("未排序前的对象顺序:" + stuList); //以自然逻辑排序的对象顺序,即Comparable定义的排序逻辑(Id值得大小) Collections.sort(stuList); System.out.println("自然逻辑排序的对象顺序:" + stuList); }}
其输出如下:。
在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()方法:
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Id; result = prime * result + name.hashCode(); result = prime * result + score; return result; } @Override public 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)); }
三、Comparator接口
对于没有实现Comparable接口类,如果需要排序输出对象,同样可以选择提供Comparator的实现,Comparator接口代码如下:
public interface Comparator{ int compare(T o1, T o2);}
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的值 @Override public String toString() { return String.valueOf(Id); } public static class OrderById implements Comparator{ @Override public 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 stuList = Arrays.asList(stu); //未排序前的对象顺序 System.out.println("未排序前的对象顺序:" + stuList); //以Comparator定义的排序输出对象(Id值得大小) Collections.sort(stuList,new Student2.OrderById()); System.out.println("自然逻辑排序的对象顺序:" + stuList); }}
其输出如下:。
相对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{ 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的值 @Override public String toString() { return String.valueOf(Id); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Id; result = prime * result + name.hashCode(); result = prime * result + score; return result; } @Override public 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值得大小,升序输出 @Override public 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 ORDERBYSCORE_ORDER = new Comparator () { @Override public int compare(Student stu1, Student stu2) { return stu1.score > stu2.score ? 1 : (stu1.score < stu2.score ? -1 : 0); } }; // 按姓名的字典顺序排序 static final Comparator ORDERBYNAME_ORDER = new Comparator () { @Override public 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 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顺序输出对象,OederByScore Collections.sort(stuList, ORDERBYSCORE_ORDER); System.out.println("OederByScore顺序输出对象" + stuList); // 使用另一个Comparator顺序输出对象,OederByName Collections.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提到的方法一致。