java中对象的比较(三种方式)

2022-08-31 11:07:30

问题提出

上节课我们讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,为了简单起见,我们只是插入了Integer类型,那优先级队列中能否插入自定义类型对象呢?先来看一段代码:

classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(1,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>();//先向这个优先级队列中插入一个元素
        qu.offer(card1);//打印的结果为[Card{rank=1, suit='♦'}]System.out.println(qu);}}

可以看到此时打印的结果是好的,接下来继续看:当我们向这个堆中加入第二个自定义对象的时候,我们来看会发生什么把:

classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(1,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>();//先向这个优先级队列中插入一个元素
        qu.offer(card1);
        qu.offer(card2);System.out.println(qu);}}

此时我们会发现出现了类型转换异常(ClassCastException):这是为什么呢?
在这里插入图片描述
此时我们点开我们报错的代码:
在这里插入图片描述
然后找到了对应的报错语句:
在这里插入图片描述
此处我们发现其强行将x转换成了Comparable类型,换到我们上面的代码中去相当于x就是我们的Card,而Card这个类型要是想实现类型转换至少应该继承我们的Comparable接口,并且还有一个问题,之前我们在讲优先级队列的时候提到过,我们每个元素再插入的时候都会调整成小根堆,调整成为小根堆的过程其实就是比较大小的过程,之前我们所有的代码插入的是对象是整数,整数非常容易比较大小,但是这次我们插入的是自定义对象,就拿我们card举例,当我们插入第二个自定义对象的时候,我们是比较花色还是比较牌的数字的大小,这个并没有告诉我们怎么比,所以最终当我们的优先级队列查看到第二个插入的元素的时候,需要构建小根堆,但是并没有告诉我们比较插入进来的对象的方式,最终导致了类型转换一吃昂,所以此时我们应该让Card类去实现我们的Comparable接口,并告诉我们比较的方式是什么.
并且大家要注意了:为什么我们之前插入一个整数的时候人家就自动每插入一个元素就可以自动比较大小并且建大/小堆呢?我们来看
在这里插入图片描述
我们摁住Ctrl进入到Integer的源码中:
在这里插入图片描述
此时我们会发现人家Integer早已经实现了Comparable接口

解析

此处我们提供两种方法来告诉我们比较的方式是什么:
1:Comparable接口
2:Comparator接口

Comparable接口

按照rank的值进行比较

底层为小根堆

下面我们来给出在优先级队列中插入一个自定义对象时为了防止类型转换异常的正确比较方式:
先来看我们的代码:

classCardimplementsComparable<Card>{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}@OverridepublicintcompareTo(Card o){//此处是告诉我们按照牌的数值来比较,此时默认为小根堆returnthis.rank- o.rank;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(2,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>();
        qu.offer(card2);
        qu.offer(card1);
        qu.offer(card3);//输出结果为[Card{rank=1, suit='♦'}, Card{rank=2, suit='♦'}, Card{rank=3, suit='♦'}]System.out.println(qu);}

注意事项:
1:首先因为我们向我们优先级队列中插入的是我们自定义的对象card,其有两个属性,一个是牌的值card,一个是牌的花色,上面的代码我们是先按照牌的值来进行比较的
2:当我们的card类实现了comparable接口后,就要重写我们的compareTo方法,当return this.rank - o.rank的时候,就代表我们每次向堆中插入card对象后,我们的堆是按照小根堆的形式进行调整的,所以比较方式也是按照小根堆的形式,比较的值就是我们card对象中rank的值

底层为大根堆
classCardimplementsComparable<Card>{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}@OverridepublicintcompareTo(Card o){//此处是告诉我们按照牌的数值来比较,此时默认为大根堆return o.rank-this.rank;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(2,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>();
        qu.offer(card2);
        qu.offer(card1);
        qu.offer(card3);//输出结果为[Card{rank=3, suit='♦'}, Card{rank=1, suit='♦'}, Card{rank=2, suit='♦'}]System.out.println(qu);}}

注意事项:
注意我们此处指定我们向优先级队列中插入的这个自定义对象是按照rank的值来进行比较的,同时我们还规定了这个堆每次在插入的时候是按照大根堆的形式调整的.

Comparator接口

按照rank的值进行比较

底层为小根堆
classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(2,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>(newComparator<Card>(){@Overridepublicintcompare(Card o1,Card o2){//根据rank值来比较 默认为小根堆return o1.rank- o2.rank;}});
        qu.offer(card2);
        qu.offer(card1);
        qu.offer(card3);//输出结果为[Card{rank=1, suit='♦'}, Card{rank=2, suit='♦'}, Card{rank=3, suit='♦'}]System.out.println(qu);}}

注意事项
此处我们是使用Comparator接口来实现的,注意与之前的不同。然后o1.rank - o2.rank代表默认建堆形式为小根堆

底层为大根堆
classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(2,"♦");Card card3=newCard(3,"♦");//创建一个优先级队列Queue<Card> qu=newPriorityQueue<>(newComparator<Card>(){@Overridepublicintcompare(Card o1,Card o2){//根据rank值来比较 默认为小根堆return o2.rank- o1.rank;}});
        qu.offer(card2);
        qu.offer(card1);
        qu.offer(card3);//输出结果为[Card{rank=3, suit='♦'}, Card{rank=1, suit='♦'}, Card{rank=2, suit='♦'}]System.out.println(qu);}}

注意事项
此处我们是使用Comparator接口来实现的,注意与之前的不同。然后o2.rank - o1.rank代表默认建堆形式为大根堆

元素的比较

对象的比较(三种方式)

classCardimplementsComparable<Card>{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}@OverridepublicStringtoString(){return"Card{"+"rank="+ rank+", suit='"+ suit+'\''+'}';}@OverridepublicintcompareTo(Card o){returnthis.rank- o.rank;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(2,"♦");Card card3=newCard(3,"♦");//直接编译报错System.out.println(card1> card2);//false,因为在内存中的地址不相同System.out.println(card1== card2);//编译出错System.out.println(card1< card2);//card1.compareTo(card2)结果为-1,原因是card1中的值为1小于card2中的值2//如果card1中的值为2,card2中的值为1的话,此时结果为1//这里的值是1还是-1跟之前compareTo中的书写形式有关System.out.println(card1.compareTo(card2));//结果为false//因为我们的Card类中并没有重写我们Object类中的equals方法,所以此处的equals方法调用的是Object类中的equals方法System.out.println(card1.equals(card2));}}

注意事项
1:从编译结果可以看出,Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。那为什么==可以比较?
因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equals方法,而==默认情况下调用的就是equals方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意
在这里插入图片描述
什么情况下就不符合题意呢?来看:

classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(1,"♦");Card card3=newCard(3,"♦");//结果为falseSystem.out.println(card1.equals(card2));}}

此时我们card1这个引用和card2这个引用所指向的对象我们会发现实际上是一张牌,但是因为这两个引用虽然逻辑上是一样的,但是在堆中却开辟了两个内存来存储这两个引用所指向的对象,而且此处的equals方法调用的是我们Object类中的equals方法,其比较方式为引用的比较,对应到上述的代码就是card1 == card2,故为false,但是我们明明是一张牌,为什么要返回false,我们想让它为true,就像字符串的equals方法一样比较其内容,不比较地址,那我们就需要在Card这个类中重写我们Object类中的equals方法,(字符串中之所以可以使用equals方法比较内容,是因为其在String类中重写了equals方法)下面来看代码

classCard{publicint rank;// 数值publicString suit;// 花色publicCard(int rank,String suit){this.rank= rank;this.suit= suit;}//注意equals与hashcode方法是成对出现的,并且直接生成无需我们自己修改@Overridepublicbooleanequals(Object o){if(this== o)returntrue;if(o==null||getClass()!= o.getClass())returnfalse;Card card=(Card) o;return rank== card.rank&&Objects.equals(suit, card.suit);}@OverridepublicinthashCode(){returnObjects.hash(rank, suit);}}publicclassTestDemo{publicstaticvoidmain(String[] args){Card card1=newCard(1,"♦");Card card2=newCard(1,"♦");Card card3=newCard(3
  • 作者:努力彪
  • 原文链接:https://blog.csdn.net/qq_41972686/article/details/121037675
    更新时间:2022-08-31 11:07:30