自然排序是什么: 如果是基本类型,那就是自然顺序,由小到大。如果是对象,那就是比较哈希值,由小到大。
文章目录
一.Comparator和Comparable区别
-
Comparable
和Comparator
都是用来实现集合中元素的比较、排序的两种比较器接口
,这两种比较器很相似,只是 Comparable 是在集合内部定义的方法实现的排序
,可称之为内比较器
,而Comparator 是在集合外部实现的排序
,可称之为外比较器
。所以,如果想实现排序,就需要在集合外定义 Comparator 接口的方法
或在集合内实现 Comparable 接口的方法
。 -
实际上也就是根据类中的
某个字段
来比较这两个类的大小。说是比较,其实更多的是排序,利用Collections.sort()
和Arrays.sort()
这两个工具类进行排序。 -
Comparable & Comparator
接口都可以用来实现集合中元素的比较、排序,Comparator位于包java.util.Comparator
下,而Comparable位于包java.lang.Comparable
下 -
Comparable接口将比较代码嵌入自身类中,Comparator在一个独立的类中实现比较。
像Integer、String等这些基本类型的JAVA封装类都已经实现了Comparable接口
,这些类对象本身就支持自比较,直接调用Collections.sort()就可以对集合中元素的排序,无需自己去实现Comparable接口。
而有些自定义类的List序列,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较,也就是指定使用Comparator(临时规则排序,也称作专门规则排序)
,如果不指定Comparator,那么就用自然规则排序,这里的自然顺序就是实现Comparable接口设定的排序方式。
二.自然排序:java.lang.Comparable
接口描述
总结为一句话:实现Comparable
,重写 compareTo
方法
public interface Comparable<T>{}
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序
,类的 compareTo
方法被称为它的自然比较方法
。实现此接口的对象列表(和数组)
可以通过 Collections.sort(和 Arrays.sort)进行自动排序
。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparable接口中只定义了一个方法:
public int compareTo(T o);//比较此对象与指定对象的顺序。O为要比较的对象
他即返回值有三种情况:
- 比较者(
调用用该方法的对象
)大于被比较者(即CompareTo方法里的参数对象
),那么返回1(positive) - 比较者等于被比较者,返回0(zero)
- 比较者小于被比较者,返回-1(negative)
String s1 = "a";
String s2="c";
s1.compareTo(s2);//得到-2说明s1在s2前两位
s2.compareTo(s1);//得到2说明s2在s1后两位
这个接口是对实现他的类自然排序,即常说的字典排序
下面给出一个很简单的程序来说明这个情况:实现Comparable接口的一个类
public class Fruit implements Comparable<Fruit> {
private String name;
private int price;
public Fruit(String name, int price) {
this.name=name;
this.price=price;
}
@Override
public int compareTo(Fruit o) {
//Integer类也实现了Comparable这个接口
return ((Integer)price).compareTo(o.price);
}
}
public class Test {
public static void main(String[] args) {
Fruit f1=new Fruit("apple", 5);
Fruit f2=new Fruit("banana", 7);
Fruit f3=new Fruit("pear", 3);
Fruit f4=new Fruit("watermelon", 5);
System.out.println(f1.compareTo(f2));// -1
System.out.println(f1.compareTo(f3));// 1
System.out.println(f1.compareTo(f4));// 0
}
}
注意一下,前面说实现Comparable接口的类是可以支持和自己比较的,但是其实代码里面Comparable的泛型未必就一定要是对象,将泛型指定为String,或任何类型都可以,只要开发者指定了具体的比较算法就行。
String的compareTo()的源码实现
private final char value[];//String的底层是字符数组 a.compareTo(b)
public int compareTo(String anotherString) {
int len1 = value.length;//获取调用该方法的字符串的长度a
int len2 = anotherString.value.length;//获取比较字符串的长度b
int lim = Math.min(len1, len2);//(a <= b) ? a : b; min底层代码 这句代码是为了获取较短的字符串的长度
char v1[] = value; //创建两个字符数组,分别指向这两个字符串的所在
char v2[] = anotherString.value;
//循环比较,循环次数,是较短的字符串的长度,如果用较长的字符串的长度,那么会出现nullPointException
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
//比较相对应索引的元素,如果元素不同则比较返回中间差距的顺序,如果相等,那么就继续循环比较
if (c1 != c2) {
return c1 - c2;//字符对应的Unicode码表中的数字,这也就是为什么说String是按照字典书序比较的,如a比b靠前,那么a对应的数字比b小,相减返回负数,差多少顺序,就返回多少
}
k++;
}
//如果两个字符串的长度不同,其它都相同,那么返回的就是长度的差距了
return len1 - len2;
}
我们可以看到String对于compareTo的实现就是依据Unicode码表中字符对应的数字
来判断的,返回的是字符串长度
差或者是字符间在码表上的差距
,依据具体情况,返回也不同.
Integer的compareTo()的源码实现
//Integer的compareTo方法,底层依据的是compare方法,这个方法是Comparator接口的一个方法
public int compareTo(Integer anotherInteger) {
//实际上Integer的比较是通过Integer中包括的整数来比较的
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {//a.compateTo(b)
//如果a比b小,那么返回-1,相等就是0,否则就是1
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
根据对String和Integer的compareTo的实现,自己来实现一个类的compareTo方法;
public class ComparableLength implements Comparable<ComparableLength>{
@Override
public int compareTo(ComparableLength o) {
int len1 = Integer.toString(this.hashCode(), 16).length();
int len2 = Integer.toString(o.hashCode(), 16).length();
return (len1 - len2 > 0) ? 1 : -1 ;
}
public String sort(ComparableLength cl){
if(this.compareTo(cl) == 1){
return "该对象比后者hashcode长度长";
}else {
return "该对象比后者hashcode长度短,也可能相等";
}
}
@Test
public void testComparable(){
ComparableLength c1 = new ComparableLength();
ComparableLength c2 = new ComparableLength();
ComparableLength c3 = new ComparableLength();
ComparableLength c4 = c1;
System.out.println(c1.sort(c2) + c1.hashCode());
System.out.println(c2.sort(c1));
System.out.println(c2.sort(c3) + c2.hashCode());
System.out.println(c1.sort(c3) + c3.hashCode());
System.out.println(c4.sort(c3) + c4.hashCode());
}
}
结果
上面方法简单比较了一下对象的hashcode的长度,接下来我们比较一下hashcode是否相等,和equals()返回同样结果,这也是建议我们做的;
public class ComparableLength implements Comparable<ComparableLength>{
public String sort(ComparableLength c2){
if(this.compareTo(c2) == 1)
return "这两个对象不是同一个对象";
else
return "这两个对象是同一个对象";
}
@Override
public int compareTo(ComparableLength o) {
String str1 = Integer.toString(this.hashCode());
String str2 = Integer.toString(o.hashCode());
int len1 = str1.length();
int len2 = str2.length();
char[] ch1 = str1.toCharArray();
char[] ch2 = str2.toCharArray();
int lim = len1 - len2 ;
if(lim == 0){
int k = 0;
while (k < len1) {
char char1 = ch1[k];
char char2 = ch2[k];
if(char1 != char2)
return 1;
k++;
}
}else if(lim != 0)
return 1;
return 0;
}
@Test
public void testComparable(){
ComparableLength c1 = new ComparableLength();
ComparableLength c2 = new ComparableLength();
ComparableLength c3 = new ComparableLength();
ComparableLength c4 = c1;
System.out.println(c1.sort(c2) + "--"+c1.equals(c2)+"--"+c1.hashCode());
System.out.println(c2.sort(c1) + "--"+c2.equals(c1));
System.out.println(c2.sort(c3) + "--"+c2.equals(c3)+"--"+c2.hashCode());
System.out.println(c1.sort(c3) + "--"+c1.equals(c3)+"--"+ c3.hashCode());
System.out.println(c4.sort(c1) + "--"+c4.equals(c1)+"--"+ c4.hashCode());
}
}
结果
编写代码Comparable
引入lombok查询,无需手动编写get/set方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
- 创建Student类实现Comparator
/**
* 自然排序(内比较器)
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Comparable {
private int id;
private String name;
@Override
public int compareTo(Object obj) {
if (obj instanceof Student) {
Student stu = (Student) obj;
return id - stu.id;
}
return 0;
}
Student 实现了自然排序接口Comparable
,如何利用Comparable接口对Studen数组进行排序的呢?
可以通过使用java.util.Arrays
类来给整型数组
排序:。
使用 Arrays.sort 方法给整型数组排序。通过API 文档发现, Arrays.sort 方法有很多重载
形式,其中就包括 sort(Object[] obj)
,也就是说 Arryas 也能对对象数组进行排序
,排序过程中比较两个对象“大小”
时使用的就是 Comparable 接口的 compareTo 方法。
//调用上面创建实现Comparable接口的Student对象
public static void main(String[] args) {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
Student[] studentArr = {stu1,stu4,stu3,stu2};
System.out.println("Array: "+Arrays.toString(studentArr));
Arrays.sort(studentArr);
System.out.println("Sort:"+Arrays.toString(studentArr));
}
}
执行结果
studentArr中对象的顺序并不是按id
来添加的,Arrays.sort(studentArr )那么如何比较两个对象的大小
?
- 由于Student 实现的
Comparable接口
。sort 方法
会将待比较的那个对象强制类型转换成 Comparable ,并调用compareTo
方法,根据其返回值来判断这两个对象的“大小”。所以调用sort 方法
后无序数组 studentArr 的顺序根据id
进行了升序排序
排序算法和 Student 类绑定了, Student 只有一种排序算法。但现实社会不是这样的,如果我们不想按学号排序,想按姓名来给学生排序怎么办?
- 我们只能修改 Student 类的 Comparable 接口的 compareTo 方法,
改成按姓名排序
。
如果在同一个系统里有两个操作,一个是按学号排序,另外一个是按姓名排序,这怎么办?
- 不可能在 Student 类体中写两个 compareTo 方法的实现。这么看来
Comparable有局限性
了。为了弥补这个不足,JDK 还为我们提供了另外一个排序方式,也就是下面要说的比较器排序
三.比较器排序:java.util.Comparator
接口描述
总结为一句话:实现Comparator
接口,重写compare
方法
Comparator
并不想Comparable那样有许多的实现类,该接口的实现类只有两个,Collator
, RuleBasedCollator
,但是该接口提供的两个方法,仍然是十分有用的.
接口:
public interface Comparator<T>{}
上面我提到了,之所以提供比较器排序接口,是因为有时需要对同一对象进行多种不同方式的排序
,这点自然排序 Comparable 不能实现
。另外, Comparator 接口的一个好处是将比较排序算法
和具体的实体类
分离。
- 通过 API 发现, Arrays.sort 有种重载形式:
sort(T[] a, Comparator<? super T> c)
,这个方法参数的写法用到了泛型。可以把它理解成这样形式: sort(Object[] a, Comparator c),意思就是是按照比较器 c 给出的比较排序算法,对 Object 数组进行排序。
接口方法:
int compare(T o1, T o2) 比较用来排序的两个参数
boolean equals(Object obj) 指示某个其他对象是否“等于”此 Comparator。
//描述compare:
public int compare(Object o1, Object o2)
本来的顺序就是参数的先后顺序o1、o2;
如果保持这个顺序就返回-1,交换顺序就返回1,什么都不做就返回0;
所以 升序的话 如果o1<o2,就返回-1。
- Comparator 接口中定义了两个方法:
compare(Object o1, Object o2)
和equals
方法- 由于 equals 方法是所有对象都有的方法,因此当我们实现 Comparator 接口时,我们只需
重写 compare 方法
,而不需重写 equals 方法。 - Comparator 接口中对重写 equals 方法的描述是:
“注意,不重写 Object.equals(Object) 方法总是安全的。然而,在某些情况下,重写此方法可以允许程序确定两个不同的 Comparator 是否强行实施了相同的排序,从而提高性能。”
。我们只需知道第一句话就OK了,也就是说,可以不用去想应该怎么实现 equals 方法,因为即使我们不显示实现 equals 方法,而是使用Object类的 equals 方法,代码依然是安全的。
- 由于 equals 方法是所有对象都有的方法,因此当我们实现 Comparator 接口时,我们只需
编写代码
那么我们来写个代码,来用一用比较器排序。还是用 Student 类来做,只是没有实现 Comparable 接口。由于比较器的实现类只用显示实现一个方法,因此,我们可以不用专门写一个类来实现它,当我们需要用到比较器时,可以写个匿名内部类来实现 Comparator 。
引入lombok查询,无需手动编写get/set方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
创建Student类
/**
* 比较器排序(外比较器)
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
}
class ComparatorTest {
public static void main(String[] args) {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
Student[] studentArr = {stu1,stu4,stu3,stu2};
System.out.println("Array: " + Arrays.toString(studentArr));
Arrays.sort(studentArr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student && o2 instanceof Student) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
//return s2.getId() - s1.getId(); // 按Id排
return s1.getName().compareTo(s2.getName()); // 按姓名排
}
return 0;
}
});
System.out.println("Sorted: " + Arrays.toString(studentArr));
}
}
当需要Student按学号排序时,只需修改我实现Comparator的内部类中的代码,而不用修改 Student 类。
注意: 当然,你也可以用 Student 类实现 Comparator 接口,这样Student就是一个比较器了(Comparator)。当需要使用这种排序的时候,将 Student 看作 Comparator 来使用就可以了,可以将 Student 作为参数传入 sort 方法,因为 Student is a Comparator 。但这样的代码不是个优秀的代码,因为我们之所以使用比较器(Comparator),其中有个重要的原因就是,这样可以将比较算法和具体类分离,降低类之间的耦合。
四.TreeSet对Comparable(自然排序)和Comparator(比较器排序)支持
TreeSet对这两种比较方式都提供了支持,分别对应着TreeSet的两个构造方法:
- TreeSet():根据TreeSet中元素实现的 Comparable 接口的 compareTo 方法比较
- TreeSet(Comparator comparator):根据给定的 comparator 比较器,对 TreeSet 中的元素比较排序
测试TreeSet对Comparable的支持
/**
* 自然排序(内比较器)
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Comparable {
private int id;
private String name;
@Override
public int compareTo(Object obj) {
if (obj instanceof Student) {
Student stu = (Student) obj;
return id - stu.id;
}
return 0;
}
/**
* 使用自然排序
* Student必须实现Comparable接口,否则会抛出ClassCastException
*/
public static void main(String[] args) {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
SortedSet set = new TreeSet();
set.add(stu1);
set.add(stu3); // 若Student没有实现Comparable接口,抛出ClassCastException
set.add(stu4);
set.add(stu2);
set.add(stu4);
System.out.println(set);
}
}
测试结果
测试TreeSet对Comparator的支持
/**
* 比较器排序(外比较器)
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
/**
* 使用比较器排序
* Student可以只是个简单的Java类,不用实现Comparable接口
*/
public static void main(String[] args) {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
SortedSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student
&& o2 instanceof Student) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return s1.getName().compareTo(s2.getName());
}
return 0;
}
});
set.add(stu1);
set.add(stu3);
set.add(stu4);
set.add(stu2);
set.add(stu4);
System.out.println(set);
}
}
测试结果:
五.总结
- java.util.Arrays提供了一系列用于对数组操作的静态方法,查找排序等等。
- java.util.Collections也提供了一系列这样的方法,只是它是用于处理集合的,它不是只能处理Collection接口以及子接口的实现类,同样也可以处理
Map接口
的实现类。
https://www.cnblogs.com/lin-jing/p/8278271.html
https://www.cnblogs.com/lin-jing/p/8279205.html
https://blog.csdn.net/sinat_37976731/article/details/79902968
https://www.cnblogs.com/andywithu/p/7239613.html
https://blog.csdn.net/boy_chen93/article/details/84571984
https://blog.csdn.net/diweikang/article/details/80788194
https://blog.csdn.net/awhip9/article/details/61918072