java比较器:java.lang.Comparable和java.util.Comparator
1 介绍
在java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
java实现对象排序的方式有两种:
自然排序:java.lang.Comparable
定制排序:java.util.Comparator
2 java.lang.Comparable
2.1 方式一:自然排序,java.lang.Comparable
Comparable接口强行对实现它的每个类的对象进行整体排序,这种排序被称为类的自然排序。
实现Comparable的类必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小。
实现Comparable接口的对象列表(和数组)可以通过Collections.sort或Arrays.sort进行自动排序。实现此接口的对象可用作有序映射中的键或有序集合中的元素,无需指定比较器。
对于类C的每一个e1和e2来说,当且仅当e1.compareTo(e2)==0与e1.equals(e2)具有相同的boolean值时,类C的自然排序才叫做与equals一致。建议(但不是必须)最好使自然排序与equals一致。
2.2 实例
注意:String类实现了Comparable接口,重写了Comparable接口的compareTo方法,所以可以进行字符串比较,即String的compareTo方法。
import org.junit.Test;
import java.util.Arrays;
/*
* 一、说明:java中的对象,正常情况下,只能比较:==或者!=。不能使用>或<的
* 但是在开发场景中,需要对多个对象进行排序,言外之意,需要比较对象的大小。
* 如何实现?使用两个接口中的任何一个:Comparable 和 Comparator
*
* 二、Comparable接口的使用
*
* */
public class Compare {
/*
* Comparable接口的使用举例(String的比较实现了Comparable接口)
* */
/*
注意:单元测试Junit,不要把注解放在public static void main中
* */
@Test
public void test1(){
String[] arr=new String[]{"CC","EE","AA","DD"};
System.out.println(Arrays.asList(arr));
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
[CC, EE, AA, DD]
[AA, CC, DD, EE]




注意:java.util.Comparator是JDK 1.8 之前已有的函数式接口
2.3 自定义类实现Comparable自然排序

源码可知,这三个类都实现了Comparable接口。
鼠标移到Goods类里面,idea快捷键生成getter和setter,以及constructor:alt+insert
商品类:
import java.math.BigDecimal;
import java.math.BigInteger;
public class Goods {
private String name;
private BigDecimal price;
private BigInteger number;
public Goods() {
}
public Goods(String name, BigDecimal price, BigInteger number) {
this.name = name;
this.price = price;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigInteger getNumber() {
return number;
}
public void setNumber(BigInteger number) {
this.number = number;
}
}
@Test
public void test2(){
Goods[] g=new Goods[4];
g[0]=new Goods("xiaomiMouse",new BigDecimal("3.45"),new BigInteger("3"));
g[1]=new Goods("huaweiMouse",new BigDecimal("1.35"),new BigInteger("10"));
g[2]=new Goods("dellMouse",new BigDecimal("8.99"),new BigInteger("7"));
g[3]=new Goods("lenovoMouse",new BigDecimal("4.5"),new BigInteger("14"));
Arrays.sort(g);
System.out.println(Arrays.toString(g));
}

comparable接口:自然排序

自定义类实现Comparable接口:
import java.math.BigDecimal;
import java.math.BigInteger;
public class Goods implements Comparable{
private String name;
private BigDecimal price;
private BigInteger number;
public Goods() {
}
public Goods(String name, BigDecimal price, BigInteger number) {
this.name = name;
this.price = price;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigInteger getNumber() {
return number;
}
public void setNumber(BigInteger number) {
this.number = number;
}
//指明商品比较大小的方式
@Override
public int compareTo(Object o) {
//判断o是不是Goods类的实例或者子类
if(o instanceof Goods){
/*编译阶段,public int compareTo(Object o),对于o,编译器认为是Object的引用o,
是所有类的父类,父类引用不能直接赋值给子类,所以必须使用强制类型转换(向下转型)
* */
Goods goods= (Goods) o;
if(this.price.compareTo(goods.price) > 0){
return 1;
}else if(this.price.compareTo(goods.price) < 0){
return -1;
}else{
return 0;
}
}
}
}
方式一:

方式二:
可以使用Integer.compare或者Double.compare等

因为该方法返回值类型是int,但是return 0没有意义,如果不是Goods类型,直接抛出异常即可。

重新执行test2:
[Goods@17d10166, Goods@1b9e1916, Goods@ba8a1dc, Goods@4f8e5cde]
数组的打印,是打印的对象的地址,第一种方式是通过重写打印的对象的类的toString方法来修改打印的对象,第二种方式就是Arrays.asList(数组)转换成集合,通过集合对象.forEach()或者集合对象.stream().forEach()来打印。
查看源码,Arrays.toString()本质上是循环对数组元素调用String.valueOf(),String.valueOf()源码如下:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
每个obj对象不为null,则调用obj.toString(),obj.toString()如下所示:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
需要重写类的中toString()方法,同样还是idea在类中alt+insert,选择toString():
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
", number=" + number +
'}';
}
第一种方式打印数组:Arrays.toString(对象数组)按照自然排序由小到大的方式):
@Test
public void test2(){
Goods[] g=new Goods[4];
g[0]=new Goods("xiaomiMouse",new BigDecimal("3.45"),new BigInteger("3"));
g[1]=new Goods("huaweiMouse",new BigDecimal("1.35"),new BigInteger("10"));
g[2]=new Goods("dellMouse",new BigDecimal("8.99"),new BigInteger("7"));
g[3]=new Goods("lenovoMouse",new BigDecimal("4.5"),new BigInteger("14"));
Arrays.sort(g);
System.out.println(Arrays.toString(g));
}
//打印出来实际是一行字符串(这里方便显示,手动换行)
[Goods{name='huaweiMouse', price=1.35, number=10},
Goods{name='xiaomiMouse', price=3.45, number=3},
Goods{name='lenovoMouse', price=4.5, number=14},
Goods{name='dellMouse', price=8.99, number=7}]
第2种方式每个对象打印(打印的效果还是按照类中重写的toString()方法而来):
@Test
public void test2(){
Goods[] g=new Goods[4];
g[0]=new Goods("xiaomiMouse",new BigDecimal("3.45"),new BigInteger("3"));
g[1]=new Goods("huaweiMouse",new BigDecimal("1.35"),new BigInteger("10"));
g[2]=new Goods("dellMouse",new BigDecimal("8.99"),new BigInteger("7"));
g[3]=new Goods("lenovoMouse",new BigDecimal("4.5"),new BigInteger("14"));
Arrays.sort(g);
List<Goods> g2=Arrays.asList(g);
Consumer<Goods> s= System.out::println;
g2.forEach(s);
}
Goods{name='huaweiMouse', price=1.35, number=10}
Goods{name='xiaomiMouse', price=3.45, number=3}
Goods{name='lenovoMouse', price=4.5, number=14}
Goods{name='dellMouse', price=8.99, number=7}
2.4 自定义类,实现Comparable,按照多个要求进行排序
//指明商品比较大小的方式
//先按照价格由低到高排序,再按照数目由低到高排列
@Override
public int compareTo(Object o) {
//判断o是不是Goods类的实例或者子类
if(o instanceof Goods){
/*编译阶段,public int compareTo(Object o),对于o,
编译器认为是Object的引用o,
是所有类的父类,父类引用不能直接赋值给子类,
所以必须使用强制类型转换(向下转型)
* */
Goods goods= (Goods) o;
if(this.price.compareTo(goods.price) > 0){
return 1;
}else if(this.price.compareTo(goods.price) < 0){
return -1;
}else{
return this.number.compareTo(goods.number);
}
}
throw new RuntimeException("传入的数据类型不一致!");
}
@Test
public void test2(){
Goods[] g=new Goods[4];
g[0]=new Goods("xiaomiMouse",new BigDecimal("3.45"),new BigInteger("3"));
g[1]=new Goods("huaweiMouse",new BigDecimal("1.35"),new BigInteger("10"));
g[2]=new Goods("dellMouse",new BigDecimal("8.99"),new BigInteger("7"));
g[3]=new Goods("lenovoMouse",new BigDecimal("8.99"),new BigInteger("4"));
Arrays.sort(g);
List<Goods> g2=Arrays.asList(g);
Consumer<Goods> s= System.out::println;
g2.forEach(s);
}
//先按照价格,再按照数目,都是由小到大
Goods{name='huaweiMouse', price=1.35, number=10}
Goods{name='xiaomiMouse', price=3.45, number=3}
Goods{name='lenovoMouse', price=8.99, number=4}
Goods{name='dellMouse', price=8.99, number=7}
如果要数目从大到小排序,如下修改:

再次执行如下:

3 java.util.Comparator
3.1 方式2:定制排序,java.util.Comparator
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序,强行对多个对象进行整体排序的比较。
重写**compare(Object o1,Object o2)**方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。
还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
tips:idea搜索类名(找到这个类所在的源码位置),ctrl+shift+A:


函数式接口只有一个抽象方法:compare


3.2 匿名内部类重写Comparator接口的compare方法


@Test
public void test3(){
/*
Comparator
* */
String[] arr=new String[]{"CC","EE","AA","DD"};
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
System.out.println(Arrays.toString(arr));
}
[EE, DD, CC, AA]
3.3 java8的新特性lambda表达式重写compare方法
@Test
public void test3(){
/*
Comparator
* */
String[] arr=new String[]{"CC","EE","AA","DD"};
Comparator<String> u=(k1,k2)->-k1.compareTo(k2);
Arrays.sort(arr, u);
System.out.println(Arrays.toString(arr));
}
[EE, DD, CC, AA]
3.4 自定义类使用Comparator
@Test
public void test4(){
Goods[] g=new Goods[4];
g[0]=new Goods("xiaomiMouse",new BigDecimal("3.45"),new BigInteger("3"));
g[1]=new Goods("huaweiMouse",new BigDecimal("1.35"),new BigInteger("10"));
g[2]=new Goods("dellMouse",new BigDecimal("8.99"),new BigInteger("7"));
g[3]=new Goods("lenovoMouse",new BigDecimal("8.99"),new BigInteger("4"));
Arrays.sort(g, new Comparator<Goods>() {
@Override
public int compare(Goods o1, Goods o2) {
if(o1.getPrice().equals(o2.getPrice())){
return -o1.getNumber().compareTo(o2.getNumber());
}else{
return o1.getPrice().compareTo(o2.getPrice());
}
}
});
List<Goods> g4=Arrays.asList(g);
Consumer<Goods> goo=System.out::println;
g4.forEach(goo);
}
Goods{name='huaweiMouse', price=1.35, number=10}
Goods{name='xiaomiMouse', price=3.45, number=3}
Goods{name='dellMouse', price=8.99, number=7}
Goods{name='lenovoMouse', price=8.99, number=4}
4 Comparable和Comparator使用的对比
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
Comparator接口属于临时性的比较。
本文详细介绍了Java中对象排序的两种方式:Comparable接口的自然排序和Comparator接口的定制排序。Comparable用于实现对象的自然排序,需要重写compareTo方法。Comparator则允许临时定制排序规则,适用于不便修改原有类的情况。示例代码展示了如何在自定义类中实现Comparable接口进行多字段排序,以及使用匿名内部类和lambda表达式实现Comparator。

被折叠的 条评论
为什么被折叠?



