登神长阶
第七阶 Java对象的比较
🎷一.Java对象的比较
🪗1.基于引用的比较
基于引用的比较在Java中使用==
运算符进行。它主要检查两个对象是否引用内存中的相同位置。以下是基于引用的比较的详细介绍:
- 使用
==
运算符:==
运算符用于比较两个对象的引用是否指向内存中的相同地址。- 如果两个对象的引用指向同一内存地址,则它们被认为是相等的。
- 但如果两个对象虽然内容相同但是不同的实例(即指向不同的内存地址),那么它们将不相等。
-
基本类型的对象可以直接比较大小。
基于引用的比较主要关注对象在内存中的位置,而不考虑其内容。这意味着即使两个对象的内容完全相同,只要它们不是同一个实例,基于引用的比较也会将它们视为不相等。
基本数据类型
public class TestCompare {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a > b); //false
System.out.println(a < b); //true
System.out.println(a == b); //false
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 > c2); //false
System.out.println(c1 < c2); //true
System.out.println(c1 == c2); //false
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2); //false
System.out.println(b1 != b2); //true
}
}
引用类型
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
Card c1 = new Card(1, "♠");
Card c2 = new Card(2, "♠");
Card c3 = c1;
//System.out.println(c1 > c2); // 编译报错
System.out.println(c1 == c2); // 编译成功 ----> 打印false,
因为c1和c2指向的是不同对象
//System.out.println(c1 < c2); // 编译报错
System.out.println(c1 == c3); // 编译成功 ----> 打印true,
因为c1和c3指向的是同一个对象
}
}
对于用户实现自定义类型,都默认继承自 Object 类,而 Object 类中提供了 equal 方法,而 == 默认情况下调 用的就是 equal 方法, 但是该方法的比较规则是: 没有比较引用变量引用对象的内容,而是直接比较引用变量的地 址 ,但有些情况下该种比较就不符合题意。
// Object中equal的实现,可以看到:直接比较的是两个引用变量的地址
public boolean equals(Object obj) {
return (this == obj);
}
🎸2.基于对象的比较
基于对象的比较在Java中是通过比较对象的内容而不是简单地比较对象的引用。这种比较通常通过重写equals()
方法来实现。以下是基于对象的比较的详细介绍:
-
使用
equals()
方法:equals()
方法是用于比较两个对象的内容是否相等的常用方法。- 大多数Java类都会覆盖
equals()
方法以便进行内容比较。 - 默认情况下,
equals()
方法比较的是两个对象的引用,但可以通过重写该方法来自定义比较的行为。 - 重写
equals()
方法时应当遵循一些约定,如对称性、传递性、一致性和非空性。
-
重写
equals()
方法的注意事项:- 对称性:如果
a.equals(b)
为true,则b.equals(a)
也应为true。 - 传递性:如果
a.equals(b)
和b.equals(c)
均为true,则a.equals(c)
也应为true。 - 一致性:对于相同的对象重复调用
equals()
方法应始终返回相同的结果。 - 非空性:对象不能为null。即调用
a.equals(null)
时应返回false。
- 对称性:如果
基于对象的比较允许开发者根据对象的内容来确定它们是否相等,而不仅仅是根据它们在内存中的位置。这使得在处理复杂数据结构或需要考虑对象状态的情况下,能够更精确地比较对象。
例如:
public class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String 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 || !(o instanceof Card)) {
return false;
}
// 注意基本类型可以直接比较,但引用类型最好调用其equal方法
Card c = (Card)o;
return rank == c.rank
&& suit.equals(c.suit);
}
}
🎹二.基于Comparble接口类的比较
Comparble是JDK提供的泛型的比较接口类,源码实现具体如下:
public interface Comparable<E> {
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo(E o);
}
在Java中,实现了Comparable
接口的类可以使用compareTo()
方法进行对象比较。这种比较方式允许对象自身定义比较逻辑,使得它们可以按照特定的顺序进行排序。以下是基于Comparable
接口的对象比较的详细介绍:
-
Comparable
接口:Comparable
接口位于java.lang
包中,其中定义了一个方法compareTo()
。- 实现了
Comparable
接口的类可以使用compareTo()
方法来比较对象。 compareTo()
方法返回一个整数值,用于表示两个对象之间的大小关系。- 如果当前对象小于另一个对象,则返回负数;如果两个对象相等,则返回零;如果当前对象大于另一个对象,则返回正数。
-
实现
Comparable
接口的步骤:- 类声明时实现
Comparable
接口,并指定泛型类型为当前类。 - 在类中实现
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 比较大
}
}
在这个示例中,Card类实现了Comparable
接口,并重写了compareTo()
方法
通过实现Comparable
接口,类可以定义自己的比较逻辑,从而实现对象之间的自定义排序。
🎻三.基于比较器的比较
除了实现Comparable
接口外,还可以使用比较器(Comparator)来进行对象比较。比较器允许在不修改对象本身的情况下定义多种比较规则。以下是基于比较器的对象比较的详细介绍:
-
Comparator接口:
Comparator
接口定义了用于比较两个对象的方法compare()
。- 通过实现
Comparator
接口,可以创建自定义的比较器,从而对对象进行比较。 - 比较器可以在不修改对象本身的情况下定义多种比较规则,使得同一类型的对象可以根据不同的需求进行排序。
-
使用Comparator的步骤:
- 创建一个实现了
Comparator
接口的类。 - 实现
compare()
方法,定义对象之间的比较逻辑。
- 创建一个实现了
-
示例:
import java.util.Comparator; class Card { public int rank; // 数值 public String suit; // 花色 public Card(int rank, String suit) { this.rank = rank; this.suit = suit; } } 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; } 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, o)); // == 0,表示牌相等 System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小 System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大 } }
🪫四.以上三种比较的分析
比较方式 | 基于比较器比较 | 基于Comparable接口类的比较 | 基于对象的比较 |
---|---|---|---|
主要接口/类 | Comparator 接口和实现了它的比较器类 | Comparable 接口和实现了它的类 | equals() 方法 |
实现方式 | 创建实现了Comparator 接口的比较器类,并实现compare() 方法 | 实现Comparable 接口的类,并实现compareTo() 方法 | 重写类的equals() 方法 |
比较规则的灵活性 | 非常高,可以根据需要定义多种不同的比较规则 | 较高,但仅限于定义类本身的比较规则 | 一般,取决于重写equals() 方法的程度 |
是否修改类的定义 | 不需要修改类的定义,可以在外部定义不同的比较规则 | 不需要修改类的定义,但需要在类内实现compareTo() 方法 | 需要在类中重写equals() 方法 |
对象的排序方式 | 可以在排序时指定不同的比较器,从而实现不同的排序方式 | 对象可以直接通过Collections.sort() 方法进行排序,按照类内定义的比较逻辑 | 无法直接排序对象,但可以通过Comparator 或Comparable 进行间接排序 |
- 基于比较器比较提供了更大的灵活性,允许在不修改类定义的情况下定义多种比较规则,适用于对外部库的类进行比较或需要多种不同比较规则的情况。
- 基于Comparable接口类的比较允许类自身定义对象之间的比较规则,但对类的修改较多。基于对象的比较适用于直接对类的内容进行比较,但排序需要借助其他手段。
覆写方法的类比
覆写的方法
|
说明
|
Object.equals
|
因为所有类都是继承自
Object
的,所以直接覆写即可,不过只能比较相等与否
|
Comparable.compareTo
|
需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序
|
Comparator.compare
|
需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强
|
🔎五.集合框架中PriorityQueue的比较方式
集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小,它根据元素的优先级进行排序。在 PriorityQueue
中,采用了: Comparble和Comparator两种方式。
- Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法
- 用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现Comparator接口并覆写compare方法。
//使用比较器创建小根堆
class LessIntComp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
//使用比较器创建大根堆
class GreaterIntComp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public class TestDemo<E> {
//求最小的K个数,通过比较器创建大根堆
public static int[] smallestK(int[] array, int k) {
if(k <= 0) {
return new int[k];
}
GreaterIntComp greaterCmp = new GreaterIntComp();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(greaterCmp);
//先将前K个元素,创建大根堆
for(int i = 0; i < k; i++) {
maxHeap.offer(array[i]);
}
//从第K+1个元素开始,每次和堆顶元素比较
for (int i = k; i < array.length; i++) {
int top = maxHeap.peek();
if(array[i] < top) {
maxHeap.poll();
maxHeap.offer(array[i]);
}
}
//取出前K个
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
int val = maxHeap.poll();
ret[i] = val;
}
return ret;
}
public static void main(String[] args) {
int[] array = {4,1,9,2,8,0,7,3,6,5};
int[] ret = smallestK(array,3);
System.out.println(Arrays.toString(ret));
}
}
🪘六.总结与反思
要找出时间来考虑一下,一天中做了什么,是正号还是负号。——季米特洛夫
在Java中,对象之间的比较是开发中经常遇到的任务之一。有三种主要的比较方式:基于比较器比较、基于Comparable
接口类的比较以及基于对象的比较(通常是通过重写equals()
方法)。它们分别适用于不同的场景和需求:
-
基于比较器比较:
- 实现了
Comparator
接口的比较器类,可以定义多种不同的比较规则。 - 适用于对外部库的类进行比较或需要多种不同比较规则的情况。
- 实现了
-
基于
Comparable
接口类的比较:- 实现了
Comparable
接口的类,可以在类内部定义对象之间的比较规则。 - 适用于对类自身的比较逻辑进行定义,但可能需要修改类的定义。
- 实现了
-
基于对象的比较:
- 通过重写类的
equals()
方法来实现对象之间的比较。 - 适用于直接对类的内容进行比较,但排序需要借助其他手段。
- 通过重写类的
总的来说,基于比较器比较提供了最大的灵活性,允许在不修改类定义的情况下定义多种比较规则。而基于Comparable
接口类的比较允许类自身定义对象之间的比较规则,但对类的修改较多。基于对象的比较适用于直接对类的内容进行比较,但排序需要借助其他手段。在实际开发中,根据具体的需求和情况选择合适的比较方式可以提高代码的可读性和灵活性。
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸