为什么要用这两个接口(作用)?
答: 我们一般用"=="和equals()方法来比较基本数据类型的两个值的大小;现在我们有需求要比较两个对象的大小,而比较对象的大小实则是比较对象中属性的大小,这个时候我们就有对对象进行排序的需求,那么该对象所在的类就要实现这两个接口然后重写接口中的抽象方法compareTo()和compare()来比较对象属性的大小从而对对象进行排序.
Comparable: ( 自然排序 ):默认从小到大排序:
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
查看String类中重写的compareTo方法的源码:
public int compareTo(String anotherString) {
int len1 = value.length;//len1:当前字符串长度
int len2 = anotherString.value.length;//len2:参数字符串长度
int lim = Math.min(len1, len2);//len1和len2两者最小值
//分别转为字符数组
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {//比较两者字符串中较小的长度内,两者是否相等。
char c1 = v1[k];
char c2 = v2[k];
//若不相等,则直接返回该位置字符的ASCII码相减后的值
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//若各位置都相等,则将两个字符串长度的差值返回
return len1 - len2;
}
观察源码得到: 对从小到大排序的理解:
返回负数:当前String类的对象(长度)小所以排在前,传入的对象(长度)大所以排在后;
返回正数:当前String类的对象(长度)大所以排在后,传入的对象(长度)小所以排在前;
返回0,表示当前类对象和传入的对象一样大,然后按ASCII码排(具体的字符顺序abcdefg…)
这一点我们在包装类中也可以看到,比如我们查看Integer类中的compareTo方法:
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
我们也验证了我们的理解是对的.根据返回值int来比较大小从而进行从小到大排序.
3.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
代码示例:
我们自定义一个商品类Goods并实现comparable接口,属性名称name和价格price,提供构造器和get/set方法
并重写toString和compareTo方法,我们主要关注我们自己重写的compareTo方法:
public class Goods implements Comparable {
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int compareTo(Object o) {
if (o instanceof Goods){//对象o是否是Goods类的实例
Goods goods = (Goods) o;//如果是,强制转换为Goods类型
//按照价格从小到大排,如果价格一样,继续按照名称从小到大排
if (this.price> goods.price){
return 1;//正数表示当前对象价格大,从小到大排,所以排后面
}else if (this.price< goods.price){
return -1;//负数表示当前对象价格小,从小到大排,所以排前面
}else {
// return 0;
//如果价格一样,我们先不返回0,继续按照名称从小到大排
return this.name.compareTo(goods.name);//调的String类的compareTo方法
}
}
//都不满足表示传入的对象不对,排不了
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
}
然后我们测试一下:
import org.junit.Test;
import java.util.Arrays;
public class CompareTest {
@Test
public void ComparableTest() {
Goods[] goods = new Goods[5];
goods[0] = new Goods("banana", 20);
goods[1] = new Goods("apple", 10);
goods[2] = new Goods("tee", 99);
goods[3] = new Goods("coffee", 30);
goods[4] = new Goods("egg", 20);
/*我们这里用Arrays工具类中的排序方法来测试,
当然还可以用集合中的Collections.sort()方法和TreeSet()来进行排序 */
Arrays.sort(goods);//给goods排序,调用的是compareTo()方法来进行比较
System.out.println(Arrays.toString(goods));
}
}
控制台输出结果:价格从小到大,价格相同时名称从小到大:
[Goods{name='apple', price=10.0}, Goods{name='banana', price=20.0}, Goods{name='egg', price=20.0}, Goods{name='coffee', price=30.0}, Goods{name='tee', price=99.0}]
Comparator: ( 定制排序 ):
1.当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2;
代码示例: 我们还是用上面Goods类来做示范,这一次我们不按照它默认的从小到大排序,
我们自己定制排序方式,按照从大到小排序,就是价格高的排前面:
我们用两种方式来体会comparator:
①常规方式:实现comparator接口,重写compare()方法,然后在测试
import java.util.Comparator;
public class Goods implements Comparator {
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int compare(Object o1, Object o2) {//因为这里传入的对象是Object类型的,所以要向下转型
if (o1 instanceof Goods && o2 instanceof Goods){
//先判断,然后强转
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o1;
//这里调了包装类Double中的compare方法,底层也是默认从小到大排序
//本来是从小到大排序,我们加上负号表示从大到小排序
return -Double.compare(g1.getPrice(), g2.getPrice());
}
return 0;
}
测试类:
@Test
public void test1() {
Goods[] goods = new Goods[5];
goods[0] = new Goods("banana", 20);
goods[1] = new Goods("apple", 10);
goods[2] = new Goods("tee", 99);
goods[3] = new Goods("coffee", 30);
goods[4] = new Goods("egg", 20);
Arrays.sort(goods);
System.out.println(Arrays.toString(goods));
然后我们运行,这时候出现异常了:
为什么呢?因为sort()方法是通过调compareTo方法实现的,而我们在Goods类中是重写的compare方法
显然行不通。
注意:这里我演示的方式①是错误的,我们不能这样写,因为comparator接口不支持这种写法,这也是为了能够更好的理解和体现这两种接口的区别。
我们通过实现comparable接口然后重写compareTo方法来进行自然排序,但是当我们不想用之前写的自然排序的时候,我们可以在排序的时候调用sort()重载的构造器,也就是②这种方式来自定义排序,我们一旦写了这种方法,之前的自然排序也就被我们覆盖掉了,则我们就使用的是我们新自定义的排序方式来进行排序的.
②特别方式:不实现comparator接口同时也就不用在Goods类中重写compare方法,
我们只需要排序的时候调用sort()重载的构造器:
sort(T[] a,Comparator<? super T> c)
用Comparator匿名子类对象的方式,然后重写compare方法来定制自己的排序方式:
public class Goods {
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
测试类:
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
public class CompareTest {
@Test
public void test1() {
Goods[] goods = new Goods[5];
goods[0] = new Goods("banana", 20);
goods[1] = new Goods("apple", 10);
goods[2] = new Goods("tee", 99);
goods[3] = new Goods("coffee", 30);
goods[4] = new Goods("egg", 20);
Arrays.sort(goods, new Comparator<Goods>() {
//这里sort构造器用的是Comparator匿名子类对象的方式,体现了临时性
@Override
public int compare(Goods o1, Goods o2) {
//这里调了包装类Double中的compare方法,底层也是默认从小到大排序
//本来是从小到大排序,我们加上负号表示从大到小排序
return -Double.compare(o1.getPrice(), o2.getPrice());
}
});
}
}
控制台输出结果:按照价格从大到小
[Goods{name='tee', price=99.0}, Goods{name='coffee', price=30.0}, Goods{name='banana', price=20.0}, Goods{name='egg', price=20.0}, Goods{name='apple', price=10.0}]
区别总结:
Comparable相当于“内部比较器”:该类本身支持排序,该类的对象在任何位置都可以进行排序;
Comparator相当于“外部比较器”:类如果没有实现comparable接口,也可以通过Comparator匿名子类对象的方式进行排序,很灵活,具有临时性;