文章目录
在面试与实际开发过程中,对象的比较一直是一个重点且比较容易混淆的点,于是今天在此总结一下。
1.问题
在上一篇文章中我写到了优先级队列,如果没有看点此链接,优先级队列在插入元素时有个要求:插入元素不为null或者元素之间必须能够进行比较,为了简单起见,上次我们只是插入了Integer类型,那么优先级队列中能否插入自定义类型呢?
import java.util.PriorityQueue;
public class TestPriorityQueue {
public static void testPriorityQueue(){
PriorityQueue<Card>p=new PriorityQueue<>();
p.offer(new Card(1,"♦"));
p.offer(new Card(2,"♦"));
}
public static void main(String[] args) {
testPriorityQueue();
}
}
class Card{
public int rank;//数值
public String suit;//花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
优先级队列底层为堆实现,在向其中插入元素时,为了满足堆的性质,必须要进行元素的比较,而此时Card是没有办法直接进行比较的,因此会抛出异常。
2.元素的比较
2.1基本类型的比较
在Java中,基本类型可直接进行比较
public class TestPriorityQueue {
public static void main(String[] args) {
int a=10;
int b=6;
System.out.println(a > b);
System.out.println(a < b);
System.out.println(a == b);
char c1='A';
char c2='B';
System.out.println(c1 > c2);
System.out.println(c1 < c2);
System.out.println(c1 == c2);
boolean b1=false;
boolean b2=true;
System.out.println(b1 == b2);
System.out.println(b1 != b2);
}
}
2.2对象的比较
public class TestPriorityQueue {
public static void main(String[] args) {
Card c1=new Card(1,'♣');
Card c2=new Card(1,'♦');
Card c3=c1;
//System.out.println(c1 > c2);编译报错
// System.out.println(c1<c2);编译报错
System.out.println(c1 == c2);//编译成功,打印false,因为c1c2指向的是不同的对象
System.out.println(c1 == c3);//编译成功,打印true,因为c1c2指向的是相同的对象
}
}
class Card{
public int rank;
public char suit;
public Card(int rank, char suit) {
this.rank = rank;
this.suit = suit;
}
}
c1、c2和c3分别是Card类型的引用变量,上述代码在比较编译时:
c1 > c2 编译失败
c1== c2 编译成功
c1 < c2 编译失败
从编译结果可以看出,Java中引用类型变量不能以>或<方式进行比较,而==却可以,为什么呢?
因为:对于用户自定义类型,都默认继承自Object类,Object类中提供了equals方法,而==默认情况下调用equals方法,但该方法的比较规则是:没有比较引用变量对象的内容,而是直接比较引用变量的地址。
// Object中equal的实现,可以看到:直接比较的是两个引用变量的地址
public boolean equals(Object obj) {
return (this == obj);
}
3.对象的比较
有些情况下,需要比较引用变量中的内容,比如优先级队列在插入对象时需要根据其中的内容通过比较来调整堆,我们如何处理?
3.1重写基类的equal
class Card{
public int rank;
public char suit;
public Card(int rank, char suit) {
this.rank = rank;
this.suit = suit;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
// o如果是null对象,或者o不是Card的子类
if (o == null || getClass() != o.getClass()) return false;
// 注意基本类型可以直接比较,但引用类型最好调用其equal方法
Card card = (Card) o;
return rank == card.rank &&
suit == card.suit;
}
@Override
public int hashCode() {
return Objects.hash(rank, suit);
}
}
注:一般覆写步骤如上演示;
- 如果指向同一个对象,返回true
- 如果传入null,返回false
- 如果传入的对象类型不为Card,返回false
- 按照类的实现目标完成比较,例如这里只要花色和数值一样,就认为是相同的牌
- 注意下调用其他引用类型的比较也需要 equals,例如这里的 suit 的比较
缺陷:equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。
3.2基于Comparable接口的比较
Comparble是JDK提供的泛型的比较接口类,源码实现具体如下:
public interface Comparable<E> {
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo(E o); }
对于用户自定义类型,如果想按照大小与方式进行比较,在定义类时,实现Comparble接口即可,然后在类中重写compareTo方法。
public class Card implements Comparable<Card> {
public int rank;//数值
public String suit;
// 花色
public Card(int rank, String suit)
{
this.rank = rank; this.suit = suit;
}
// 根据数值比较,不管花色 // 这里我们认为 null 是最小的
@Override
public int compareTo(Card o) {
if (o == null){
return 1;
}
return rank - o.rank;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
System.out.println(p.compareTo(o));
}
// == 0,表示牌相等 System.out.println(p.compareTo(q));
// < 0,表示 p 比较小 System.out.println(q.compareTo(p));
// > 0,表示 q 比较大 }
}
Compareble是java.lang中的接口类,可以直接使用。
3.3基于比较器比较(Comparator接口)
注意区分Comparable和Comparator。
- 重写Comparator中的Compare方法
import java.util.Comparator;
public class Card {
public int rank;//数值
public char suit;//花色
public Card(int rank, char suit) {
this.rank = rank;//数值
this.suit = suit;//花色
}
public static void main(String[] args) {
Card p=new Card(1,'♦');
Card q=new Card(2,'♦');
Card o=new Card(1,'♦');
cardComparator cmpTor=new cardComparator();//定义比较器对象
System.out.println(cmpTor.compare(p, q));
System.out.println(cmpTor.compare(p, o));
System.out.println(cmpTor.compare(q, o));
}
}
class cardComparator implements Comparator<Card>{
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
@Override
public int compare(Card o1, Card o2) {
if(o1==o2){
return 0;
}
if(o1==null){
return -1;
}
if(o2==null){
return 1;
}
return o1.rank-o2.rank;
}
}
注意:Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包。
3.4 三种方式对比
4.面试一问
- 同学,==、equals和hashCode有什么区别?
(1)= =用来比较两个变量的值是否相等,用于比较变量对应内存中所存储的值是否相等,要比较两个基本类型的数据或两个引用变量是否相等,只能用" ==".具体一点:基本类型可直接使用= =来比较其对应值,如果变量指向的数据是对象(引用类型),那么此时涉及到了两块内存,对象本身占一块内存(堆),变量也占用一块内存–》其中存储的数值为对象占用的内存的首地址,如果要比较两个变量是否指向同一个对象(这两个对象是否指向同一块存储空间),可以使用= =解决,但如果要比较两个对象中内容是否相等,==无法解决。
(2)equals是Object类提供的方法。每一个Java类都继承自Object类,如果在没有覆盖equals(Object)方法的情况下,其与= =运算符一样,比较的是引用。
(3)hashCode()方法继承自Object类,也是用来鉴定两个对象是否相等,Object类中的hashCode()方法返回对象在内存中的地址转换成的一个int值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。
一般在覆盖equals方法同时也要覆盖hashCode()方法,这是一个约定,一般情况下用户不会调用hashCode()方法,hashCode()方法返回值和equal是()方法关系如下:
x.equals(y) :返回true,调用x或y任意一个的·hashCode()方法必定返回同样的整数结果
x.equals(y):返回false,那么x和y的hashCode()方法返回值有可能相等也有可能不相等,反之,hashCode()方法的返回值不相等,则equals()方法的返回值也不相等,hashCode()方法的返回值相等,equals()方法的返回值也可能相等,也可能不相等