前言

在java中重载排序方法的方法目前有两种,一种是实现Comparable接口的compareTo方法,还有一种是用比较器(comparator) 作为参数,其中比较器是实现了Comparator接口的类的实例

类比于C++来看,重载排序方法实质上就是重载类的小于号,但是java标准中没有规定可以重载运算符,所以需要用接口实现这个过程

同样是类比C++,上述的两种方法分别对应着重载小于号的函数作为成员函数友元函数的区别,而其各自的优点分别是:

  1. 覆盖compareTo函数:可以用于比较父子类之间的大小关系
  2. 比较器比较:更好理解,实现也更简单

关于这两种比较方法的语法网上一搜一大堆,我也就不多说了,重点是想记录一下个人的一些理解

1.实现Comparable接口的compareTo方法

class Node implements Comparable<Node>
{
	private int x,y;
	@override
	public int compareTo(Node t)
	{
		if(x!=t.x) return x-t.x;
		return y-t.y;
	}
}

先放上代码,Node类只有两个字段,排序方法首先以 x 进行排序,如果 x 相等的话再以 y 进行排序,下面是一些问题:


  1. 为什么要实现Comparable接口的compareTo的方法,而不是直接在类内提供一个compareTo的方法?

答:主要原因在于Java是一种强类型语言,在调用方法的时候,编译器要能检查这个方法确实存在,而在sort内部会出现这种语句:

// approach used in the standard library -- not recommended
if(((Comparable) a[i]).compareTo(a[j]) > 0)
{
	// rearrange a[i] and a[j]
	...
}
//call in main
Arrays.sort(a);

其中Arrays.sort的参数是Object[]类型,在内部进行比较的过程中,会将元素强制转换为Comparable接口类型,然后调用其compareTo方法,所以需要对相应元素的类内实现Comparable接口的compareTo方法


  1. 为什么不直接在Object类中定义compareTo方法,然后让有需要的子类自己覆盖呢?

答:因为能用到compareTo的地方实际上非常少,在Object类中预留方法会非常浪费


  1. 为什么不将Arrays.sort的形式参数设置为Comparable[]类型,这样如果没有实现Comparable接口的话就会自动报错

答:实际上上述方法理论上是可行的,但现实中为了让接口统一标准,且许多类的内都用到了Object对象,所以用Object[]作为参数显然更符合通用型的习惯


  1. 能不能在类内提供compareTo方法,然后在Arrays.sort的参数表中提供元素的实例进行强制转换呢?

答:理论上是可行的,但代码实现复杂度远大于实现Comparable接口。提供元素的实例无非是为了强制转换,但考虑到这种重载排序的方法有一个很大的优点是可以对同一父类的任意子类比较大小,其原理用到了对象引用的多态性,这里不多赘述(懂的都懂)所以如果想要显式强制转换的话,对同一类型的排序会方便很多;但是对于不同类型的排序,就会显得有点笨拙了。


2.用比较器作为参数

class cmp implements Comparator<Node>
{
	public int compare(Node a,Node b)
	{
		if(a.x!=b.x) return a.x-b.x;
		return a.y-b.y;
	}
}
//call in main
Arrays.sort(a,new cmp());

这种重载排序的方法是为了给那些在类内没有实现Comparable接口,但又想实现自定义排序的情况下准备的。
不过这种方法比较简单,没什么好说的。结合后续lambda表达式以及函数式接口的知识,不难发现这种方法传递的Comparator接口实例完全可以用lambda表达式的一句话代替,写起来可读性强,而且代码复杂度低

平时算法竞赛中的重载排序应该都会选择这种方法,无非就是好写,如下所示

//call in main
Arrays.sort(t,(Node a,Node b)->
{
	if(a.x!=b.x) return a.x-b.x;
	return a.y-b.y;
});

后记

因为在学习重载排序时遇到了一些底层的小问题,在请教过zx学长以及啃博客之后才稍微理清楚了其原理,所以写博客记录一下,如有错误欢迎指正