java equal、Comparble、Comparator java中对象的比较equal、Comparble、Comparator的区别
@陌上初薰 人气:0关于对象值相等的比较
三种比较风格
- 比较身份:==,通过等号来比较身份
- 比较值:通过使用equals方法,它是Object这个祖宗类所提供的的一个方法,也可以自己写个类,重写equals,自定制比较值的规则。
- 这里的equals需要通过用户手动重写才能够按照值比较,换句话说就是你要是想比较值的话,就要约定好你到底按照什么样的规则来进行比较,因为如果是比较身份的话,编译器比较清楚,可以比较引用的地址,但如果是比较值的话,你一个类中有很多属性,到底按哪些规则来比较,谁重要谁不重要,编译器自己也不知道,这就需要用户根据业务场景来指定按照哪些字段进行比较。
- 如果equals没有手动重写,默认执行的就是Object版本中的equals,此时的比较规则也是在比较身份。
- 比较类型: 通过instanceof,在类型转型之前先比较一下,看看当前引用的真实类型到底是什么,以免出现类型转换异常。
验证== 和 equas在比较风格上的区别
代码实现:按照身份来比较
package java2021_1018; //写一个辅助的类Card(扑克牌-之前写过) class Card{ //按照点数来比较 public String rank;//点数 public String suit;//花色 //提供构造方法 public Card(String rank, String suit) { this.rank = rank; this.suit = suit; } } public class TestCompare { public static void main(String[] args) { //写一个类方法,在类方法中构造出这两张牌 Card a=new Card("3","♠");//第一张牌:a的点数为1,花色为黑桃 Card b=new Card("3","♠");//第二张牌:b的点数为1,花色为黑桃 Card c=a; //此时已经有了三张牌,但是这三张牌来的方式并不一样,a是通过new一个Card对象, //b也是是通过new一个Card对象,而c是相当于和直接a指向同一个card对象 //《使用 == 等号,进行比较》 System.out.println("==============使用 == 等号,进行比较的结果=============="); System.out.println(a == c);//结果为true System.out.println(a == b);//结果为false //第二个结果为true是因为a和b分别new了两个对象,这两个对象的身份是不相等的,所以执行 == 比较就是false的情况 //《使用equals进行比较》 System.out.println("==============使用equals进行比较的结果=============="); System.out.println(a.equals(c));//结果为true System.out.println(a.equals(b));//结果为false /*此时发现当前使用的equals和 == 结果一样,没什么区别,这说明当前这个equals你没有对他进行重写的时候,它就仍然是按照身份的方式进行比较的; * 所以,如果equals没有手动重写,默认执行的就是Object版本中的equals,此时的比较规则也是在比较身份。*/ } }
打印结果:
覆写基类的equal
给equals加上一个重写,使它变成比较值(内容)的方法
代码实现:按照值来比较
package java2021_1018; class Card{ public String rank;//点数 public String suit;//花色 public Card(String rank, String suit) { this.rank = rank; this.suit = suit; } @Override public boolean equals(Object obj) {//参数:obj,类型:Object //按照值来比较this和obj //1.考虑自己和自己比较的情况 if(this == obj){ //先看看比较的这两个对象是不是同一个对象,如果是,就返回true return true;//因为两个对象(引用)如果身份相同的话,那么值也肯定是相同的 } //2.考虑obj为null的情况,认为结果为false, 避免出现空引用异常。 因为this是不可能为null的,如果它是null,就无法调用equals方法了 //所以如果obj为null,this不为null,那就返回false,有了这样的一个条件判断,就可以保证在后面这个obj == null逻辑执行的时候,就不会出现空引用异常了 if(obj == null){ return false; } //3、考虑类型不匹配的情况,即考虑obj这个类型是不是当前的Card类型,如果equals里传了一个其他参数类型进来,此时两个类型不同是无法比较相等的,所以需要判断一下哦 if (!(obj instanceof Card)){//如果obj这个参数不是Card这个类型的话,就返回false return false; //同时类型转换也带有类型转换失败的风险,所以在使用之前也要先确认好类型是否匹配 } //4.真正的比较内容 Card other = (Card)obj;//此时的参数类型是Object类型,所以需要先对obj进行一个类型的强转,并赋值给一个变量other //再去比较判断 点数或花色 或 点数和花色 是否相等 return this.rank.equals(other.rank) && this.suit.equals(other.suit); } //这相当于是一个标准的重写equals的一个模板,以后再写其他的一些自己的比较方法的时候也要按照这种思路一步步往下考虑 } public class TestCompare1 { public static void main(String[] args) { Card a=new Card("3","♠");//第一张牌:a的点数为1,花色为黑桃 Card b=new Card("3","♠");//第二张牌:b的点数为1,花色为黑桃 Card c=a; System.out.println("==============使用 == 等号,进行比较的结果=============="); System.out.println(a == c);//结果为true System.out.println(a == b);//结果为true,因为a和b分别new了两个对象,这两个对象的身份是不相等的,所以执行 == 比较就是false的情况 System.out.println("==============使用equals进行比较的结果=============="); System.out.println(a.equals(c));//结果为true System.out.println(a.equals(b));//结果为true } }
打印结果:
注意:一般覆写 equals 的套路就是上面演示的
在实现equals中所需考虑的几个步骤,及涉及到的细节,在实现其他类的equals时也基本上就是代码中所考虑的这几个操作(套路)。
- 如果指向同一个对象,返回 true
- 如果传入的为 null,返回 false
- 如果传入的对象类型不是 Card,返回 false
- 按照类的实现目标完成比较,例如这里只要花色和数值一样,就认为是相同的牌
- 注意下调用其他引用类型的比较也需要 equals,例如这里的 suit 的比较
- 覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。
关于对象值大于、等于、小于的比较–基于自然顺序(按照<小于号的形式)
基于Comparble接口类的比较
Comparble这个接口相当于就是重新定义小于这个操作
下面通过代码来体会一下Comparble这个接口的作用,还是基于Card这个类来进行比较,如果想要使用Comparble这个接口的话,就需要让Card实现一个Comparble,由于Comparble是一个带泛型的接口,于是就需要给它写一个泛型参数,但也不是非写不可。
代码实现:基于Comparble接口类的比较
package java2021_1018; class Card implements Comparable<Card>{//实现一个 Comparable的接口,由于Comparble是一个带泛型的接口,于是就需要给它写一个泛型参数 //按照点数来比较 public String rank;//点数 public String suit;//花色 //提供构造方法 public Card(String rank, String suit) { this.rank = rank; this.suit = suit; } //重写compareTo方法 @Override public int compareTo(Card o) { if(o == null){//如果o传过来的参数是一个空引用,就认为this比null要大 //一般也认为null的值比较小 return 1; } //点数的取值:是2~10的一系列整数,和J Q K A;如果点数值在2~10的话,直接返回成整数,如果点数值是J Q K A的话,就手动把这四个点数设置成11,12,13,14然后把值算出来之后再去分别比较大小 int rank1 = this.getValue();//this的值 int rank2 = o.getValue();//o的值 return rank1-rank2;//返回二者的差值,返回的值参考compareTo的语法规则 } //写一个获取值的方法, private int getValue() { //通过这个方法把String 类型的rank(J Q K A)变成数字点数11,12,13,14 int value = 0; if("J".equals(rank)){ value = 11; }else if("Q".equals(rank)){ value = 12; }else if("K".equals(rank)){ value = 13; }else if ("A".equals(rank)){ value = 14; }else { value = Integer.parseInt(rank);//把字符串转成数字 //单独处理J Q K A,然后其他的2~10直接使用Integer.parseInt,把字符串转换成整数。 } return value; } public class TestCompare1 { public static void main(String[] args) { //写一个类方法,在类方法中构造出这两张牌 Card a=new Card("3","♠");//第一张牌:a的点数为1,花色为黑桃 Card b=new Card("2","♦");//第二张牌:b的点数为1,花色为黑桃 Card c=a; //调用compareTo比较方法 System.out.println(a.compareTo(b)); System.out.println(a.compareTo(c)); } }
compareTo方法的语法规则
- 如果认为this 比 o 小,就返回一个<0的整数
- 如果认为this 比 o 大,就返回一个>0的整数
- 如果认为this 比 o 相等,就返回0
不同的点数比较后所打印出来的结果如下图:
关于对象值大于、等于、小于的比较-- 基于比较器比较
基于Comparator接口类的比较
Comparator也是一个接口,想要使用它也要让你的类去实现这个接口,同时去重写一个compare方法,但是不同的是compare方法里面有两个参数,
Comparable和Comparator,他俩的区别在于Comparator定义出的比较器和原来的类不是一个耦合在一起的关系
即使用Comparable的时候,你必须让要比较的类实现Comparable接口,换句话说,就是需要修改这个类的代码,比如刚才待比较类是Card,如果要是用Comparable这种实现方式的时候,必须得修改Card的源码。
而使用Comparator的时候,你是重新创建一个新的类实现Comparator接口,不需要修改待比较类的代码,比如刚才待比较类是Card,如果要是用Comparator这种实现方式的时候,就不用改Card的源码
所以说使用Comparable这种实现方式的时候,它的耦合性就更强一些,但这是我们不愿意看到的。
代码实现:基于Comparator接口类的比较
package java2021_1018; import java.util.Comparator; //写一个辅助的类Card(扑克牌-之前写过) class Card { //按照点数来比较 public String rank;//点数 public String suit;//花色 //提供构造方法 public Card(String rank, String suit) { this.rank = rank; this.suit = suit; } //写一个获取值的方法, public int getValue() { //通过这个方法把String 类型的rank(J Q K A)变成数字点数11,12,13,14 int value = 0; if("J".equals(rank)){ value = 11; }else if("Q".equals(rank)){ value = 12; }else if("K".equals(rank)){ value = 13; }else if ("A".equals(rank)){ value = 14; }else { value = Integer.parseInt(rank);//把字符串转成数字 //单独处理J Q K A,然后其他的2~10直接使用Integer.parseInt,把字符串转换成整数。 } return value; } }z //写一个CardComparator类 class CatdComparator implements Comparator<Card>{//这里指定泛型参数就是针对谁比较就写谁 //实现一个compare方法 @Override public int compare(Card o1, Card o2) {//compare方法里面有两个参数,类型都是Card类型 //判断特殊情况 if(o1 == o2){//如果o1 和 o2身份相等 return 0; } //判断o1 、 o2是不是null的情况 if(o1 == null){ return -1; } if(o2 == null){ return 1; } //比较值 int value1 = o1.getValue(); int value2 = o2.getValue(); return value1-value2; } } public class TestCompare1 { public static void main(String[] args) { //写一个类方法,在类方法中构造出这两张牌 Card a=new Card("3","♠");//第一张牌:a的点数为1,花色为黑桃 Card b=new Card("K","♠");//第二张牌:b的点数为1,花色为黑桃 Card c=a; //Comparator的使用:先创建一个Comparator的实例 CatdComparator comparator = new CatdComparator(); System.out.println(comparator.compare(a,b)); } }
疑问:为什么有了Comparable还需要有一个Comparator呢?
1、因为Comparable使用的时候必须要修改待比较类的代码,实际开发中不是所有的类都能修改源码,(如果这个类是库或者是其他组的人提供的此时就不能随便改人家的代码,只能改自己的代码)。
2、Comparable只能定义一种比较规则,Comparator可以定义多种比较规则(即可以实现多个Comparator类)。如:代码中的CardComparator类,定义多份之后就可以有多种不同的比较规则了,然后就可以在不同的比较规则里面分别针对当前的场景来自定制按照什么样的方式来比较了。
三种比较方式对比
覆写的方法 | 说明 |
---|---|
Object.equals | 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否 |
Comparable.compareTo | 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序 |
Comparator.compare | 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性 |
加载全部内容