java对象的比较

男娘i 2024-04-28 07:38 142阅读 0赞

java对象的比较

  • ?1. 问题提出?
  • ?2. 元素的比较?
    • 2.1 基本类型的比较
    • 2.2 对象的比较
  • ? 3. 对象的比较 ?
    • 3.1 覆写基类的equal
    • 3.2 基于`Comparble`接口类的比较
    • 3.3 基于比较器比较
    • 3.4 三种方式对比
  • ?4. 集合框架中`PriorityQueue`的比较方式?
  • ?5. 模拟实现`PriorityQueue`?

大家好,我是晓星航。今天为大家带来的是 java对象的比较 相关内容的讲解!?

?1. 问题提出?

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

202304211044850.png

  1. class Card {
  2. public int rank; // 数值
  3. public String suit; // 花色
  4. public Card(int rank, String suit) {
  5. this.rank = rank;
  6. this.suit = suit;
  7. }
  8. }
  9. public class TestPriorityQueue {
  10. public static void TestPriorityQueue()
  11. {
  12. PriorityQueue<Card> p = new PriorityQueue<>();
  13. p.offer(new Card(1, "♠"));
  14. p.offer(new Card(2, "♠"));
  15. }
  16. public static void main(String[] args) {
  17. TestPriorityQueue();
  18. }
  19. }

优先级队列底层使用堆,而向堆中插入元素时,为了满足堆的性质,必须要进行元素的比较,而此时Card是没有办法直接进行比较的,因此抛出异常。

202304211045594.png 202304211052161.png

?2. 元素的比较?

2.1 基本类型的比较

在Java中,基本类型的对象可以直接比较大小。

  1. public class TestCompare {
  2. public static void main(String[] args) {
  3. int a = 10;
  4. int b = 20;
  5. System.out.println(a > b);
  6. System.out.println(a < b);
  7. System.out.println(a == b);
  8. char c1 = 'A';
  9. char c2 = 'B';
  10. System.out.println(c1 > c2);
  11. System.out.println(c1 < c2);
  12. System.out.println(c1 == c2);
  13. boolean b1 = true;
  14. boolean b2 = false;
  15. System.out.println(b1 == b2);
  16. System.out.println(b1 != b2);
  17. }
  18. }

2.2 对象的比较

  1. class Card {
  2. public int rank; // 数值
  3. public String suit; // 花色
  4. public Card(int rank, String suit) {
  5. this.rank = rank;
  6. this.suit = suit;
  7. }
  8. }
  9. public class TestPriorityQueue {
  10. public static void main(String[] args) {
  11. Card c1 = new Card(1, "♠");
  12. Card c2 = new Card(2, "♠");
  13. Card c3 = c1;
  14. //System.out.println(c1 > c2); // 编译报错
  15. System.out.println(c1 == c2);
  16. // 编译成功 ----> 打印false,因为c1和c2指向的是不同对象
  17. //System.out.println(c1 < c2); // 编译报错
  18. System.out.println(c1 == c3);
  19. // 编译成功 ----> 打印true,因为c1和c3指向的是同一个对象
  20. }
  21. }

c1、c2和c3分别是Card类型的引用变量,上述代码在比较编译时:

c1 > c2 编译失败

c1== c2 编译成功

c1 < c2 编译失败

从编译结果可以看出,Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。 那为什么==可以比较?

因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而==默认情况下调用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。

  1. // Object中equal的实现,可以看到:直接比较的是两个引用变量的地址
  2. public boolean equals(Object obj) {
  3. return (this == obj);
  4. }

? 3. 对象的比较 ?

有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?

3.1 覆写基类的equal

  1. public class Card {
  2. public int rank; // 数值
  3. public String suit; // 花色
  4. public Card(int rank, String suit) {
  5. this.rank = rank;
  6. this.suit = suit;
  7. }
  8. @Override
  9. public boolean equals(Object o) {
  10. // 自己和自己比较
  11. if (this == o) {
  12. return true;
  13. }
  14. // o如果是null对象,或者o不是Card的子类
  15. if (o == null || !(o instanceof Card)) {
  16. return false;
  17. }
  18. // 注意基本类型可以直接比较,但引用类型最好调用其equal方法
  19. Card c = (Card)o;
  20. return rank == c.rank
  21. && suit.equals(c.suit);
  22. }
  23. }

注意: 一般覆写 equals 的套路就是上面演示的

  1. 如果指向同一个对象,返回 true
  2. 如果传入的为 null,返回 false
  3. 如果传入的对象类型不是 Card,返回 false
  4. 按照类的实现目标完成比较,例如这里只要花色和数值一样,就认为是相同的牌
  5. 注意下调用其他引用类型的比较也需要 equals,例如这里的 suit 的比较

覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。

3.2 基于Comparble接口类的比较

Comparble是JDK提供的泛型的比较接口类,源码实现具体如下:

  1. public interface Comparable<E> {
  2. // 返回值:
  3. // < 0: 表示 this 指向的对象小于 o 指向的对象
  4. // == 0: 表示 this 指向的对象等于 o 指向的对象
  5. // > 0: 表示 this 指向的对象等于 o 指向的对象
  6. int compareTo(E o);
  7. }

对用用户自定义类型,如果要想按照大小与方式进行比较时:在定义类时,实现Comparble接口即可,然后在类中重写compareTo方法。

  1. public class Card implements Comparable<Card> {
  2. public int rank; // 数值
  3. public String suit; // 花色
  4. public Card(int rank, String suit) {
  5. this.rank = rank;
  6. this.suit = suit;
  7. }
  8. // 根据数值比较,不管花色
  9. // 这里我们认为 null 是最小的
  10. @Override
  11. public int compareTo(Card o) {
  12. if (o == null) {
  13. return 1;
  14. }
  15. return rank - o.rank;
  16. }
  17. public static void main(String[] args){
  18. Card p = new Card(1, "♠");
  19. Card q = new Card(2, "♠");
  20. Card o = new Card(1, "♠");
  21. System.out.println(p.compareTo(o)); // == 0,表示牌相等
  22. System.out.println(p.compareTo(q));// < 0,表示 p 比较小
  23. System.out.println(q.compareTo(p));// > 0,表示 q 比较大
  24. }
  25. }

Compareblejava.lang中的接口类,可以直接使用。

3.3 基于比较器比较

按照比较器方式进行比较,具体步骤如下:

  • 用户自定义比较器类,实现Comparator接口

    public interface Comparator {

    1. // 返回值:

    // < 0: 表示 o1 指向的对象小于 o2 指向的对象
    // == 0: 表示 o1 指向的对象等于 o2 指向的对象
    // > 0: 表示 o1 指向的对象等于 o2 指向的对象

    1. int compare(T o1, T o2);

    }

注意:区分Comparable和Comparator。

  • 覆写Comparator中的compare方法

    import java.util.Comparator;
    class Card {

    1. public int rank; // 数值
    2. public String suit; // 花色
    3. public Card(int rank, String suit) {
    4. this.rank = rank;
    5. this.suit = suit;
    6. }

    }
    class CardComparator implements Comparator {

    // 根据数值比较,不管花色
    // 这里我们认为 null 是最小的
    @Override
    public int compare(Card o1, Card o2) {

    1. if (o1 == o2) {
    2. return 0;
    3. }
    4. if (o1 == null) {
    5. return -1;
    6. }
    7. if (o2 == null) {
    8. return 1;
    9. }
    10. return o1.rank - o2.rank;

    }

    1. public static void main(String[] args){
    2. Card p = new Card(1, "♠");
    3. Card q = new Card(2, "♠");
    4. Card o = new Card(1, "♠");

    // 定义比较器对象

    1. CardComparator cmptor = new CardComparator();

    // 使用比较器对象进行比较

    1. System.out.println(cmptor.compare(p, o)); // == 0,表示牌相等
    2. System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小
    3. System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大
    4. }

    }

注意:Comparatorjava.util 包中的泛型接口类,使用时必须导入对应的包。

3.4 三种方式对比

202304211047353.png 202304211052859.png

?4. 集合框架中PriorityQueue的比较方式?

集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小,PriorityQueue采用了:ComparbleComparator两种方式。

  1. Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法
  2. 用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现Comparator接口并覆写compare方法。

    // JDK中PriorityQueue的实现:
    public class PriorityQueue extends AbstractQueue

    1. implements java.io.Serializable {
    2. // ...

    // 默认容量

    1. private static final int DEFAULT_INITIAL_CAPACITY = 11;
    2. // 内部定义的比较器对象,用来接收用户实例化PriorityQueue对象时提供的比较器对象
    3. private final Comparator<? super E> comparator;
    4. // 用户如果没有提供比较器对象,使用默认的内部比较,将comparator置为null
    5. public PriorityQueue() {
    6. this(DEFAULT_INITIAL_CAPACITY, null);
    7. }
    8. // 如果用户提供了比较器,采用用户提供的比较器进行比较
    9. public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {

    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility

    1. if (initialCapacity < 1)
    2. throw new IllegalArgumentException();
    3. this.queue = new Object[initialCapacity];
    4. this.comparator = comparator;
    5. }
    6. // ...

    // 向上调整:
    // 如果用户没有提供比较器对象,采用Comparable进行比较
    // 否则使用用户提供的比较器对象进行比较

    1. private void siftUp(int k, E x) {
    2. if (comparator != null)
    3. siftUpUsingComparator(k, x);
    4. else
    5. siftUpComparable(k, x);
    6. }
    7. // 使用Comparable
    8. @SuppressWarnings("unchecked")
    9. private void siftUpComparable(int k, E x) {
    10. Comparable<? super E> key = (Comparable<? super E>) x;
    11. while (k > 0) {
    12. int parent = (k - 1) >>> 1;
    13. Object e = queue[parent];
    14. if (key.compareTo((E) e) >= 0)
    15. break;
    16. queue[k] = e;
    17. k = parent;
    18. }
    19. queue[k] = key;
    20. }
    21. // 使用用户提供的比较器对象进行比较
    22. @SuppressWarnings("unchecked")
    23. private void siftUpUsingComparator(int k, E x) {
    24. while (k > 0) {
    25. int parent = (k - 1) >>> 1;
    26. Object e = queue[parent];
    27. if (comparator.compare(x, (E) e) >= 0)
    28. break;
    29. queue[k] = e;
    30. k = parent;
    31. }
    32. queue[k] = x;
    33. }

    }

?5. 模拟实现PriorityQueue?

  1. class LessIntComp implements Comparator<Integer>{
  2. @Override
  3. public int compare(Integer o1, Integer o2) {
  4. return o1 - o2;
  5. }
  6. }
  7. class GreaterIntComp implements Comparator<Integer>{
  8. @Override
  9. public int compare(Integer o1, Integer o2) {
  10. return o2 - o1;
  11. }
  12. }
  13. // 假设:创建的是小堆----泛型实现
  14. public class MyPriorityQueue<E> {
  15. private Object[] hp;
  16. private int size = 0;
  17. private Comparator<? super E> comparator = null;
  18. // java8中:优先级队列的默认容量是11
  19. public MyPriorityQueue(Comparator<? super E> com) {
  20. hp = new Object[11];
  21. size = 0;
  22. comparator = com;
  23. }
  24. public MyPriorityQueue() {
  25. hp = new Object[11];
  26. size = 0;
  27. comparator = null;
  28. }
  29. // 按照指定容量设置大小
  30. public MyPriorityQueue(int capacity) {
  31. capacity = capacity < 1 ? 11 : capacity;
  32. hp = new Object[capacity];
  33. size = 0;
  34. }
  35. // 注意:没有此接口,给学生强调清楚
  36. // java8中:可以将一个集合中的元素直接放到优先级队列中
  37. public MyPriorityQueue(E[] array){
  38. // 将数组中的元素放到优先级队列底层的容器中
  39. hp = Arrays.copyOf(array, array.length);
  40. size = hp.length;
  41. // 对hp中的元素进行调整
  42. // 找到倒数第一个非叶子节点
  43. for(int root = ((size-2)>>1); root >= 0; root--){
  44. shiftDown(root);
  45. }
  46. }
  47. // 插入元素
  48. public void offer(E val){
  49. // 先检测是否需要扩容
  50. grow();
  51. // 将元素放在最后位置,然后向上调整
  52. hp[size] = val;
  53. size++;
  54. shiftUp(size-1);
  55. }
  56. // 删除元素: 删除堆顶元素
  57. public void poll(){
  58. if(isEmpty()){
  59. return;
  60. }
  61. // 将堆顶元素与堆中最后一个元素进行交换
  62. swap((E[])hp, 0, size-1);
  63. // 删除最后一个元素
  64. size--;
  65. // 将堆顶元素向下调整
  66. shiftDown(0);
  67. }
  68. public int size(){
  69. return size;
  70. }
  71. public E peek(){
  72. return (E)hp[0];
  73. }
  74. boolean isEmpty(){
  75. return 0 == size;
  76. }
  77. // 向下调整
  78. private void shiftDown(int parent){
  79. if(null == comparator){
  80. shiftDownWithcompareTo(parent);
  81. }
  82. else{
  83. shiftDownWithComparetor(parent);
  84. }
  85. }
  86. // 使用比较器比较
  87. private void shiftDownWithComparetor(int parent){
  88. // child作用:标记最小的孩子
  89. // 因为堆是一个完全二叉树,而完全二叉树可能有左没有有
  90. // 因此:默认情况下,让child标记左孩子
  91. int child = parent * 2 + 1;
  92. // while循环条件可以一直保证parent左孩子存在,但是不能保证parent的右孩子存在
  93. while(child < size)
  94. {
  95. // 找parent的两个孩子中最小的孩子,用child进行标记
  96. // 注意:parent的右孩子可能不存在
  97. // 调用比较器来进行比较
  98. if(child+1 < size && comparator.compare((E)hp[child+1], (E)hp[child]) < 0 ){
  99. child += 1;
  100. }
  101. // 如果双亲比较小的孩子还大,将双亲与较小的孩子交换
  102. if(comparator.compare((E)hp[child], (E)hp[parent]) < 0) {
  103. swap((E[])hp, child, parent);
  104. // 小的元素往下移动,可能导致parent的子树不满足堆的性质
  105. // 因此:需要继续向下调整
  106. parent = child;
  107. child = child*2 + 1;
  108. }
  109. else{
  110. return;
  111. }
  112. }
  113. }
  114. // 使用compareTo比较
  115. private void shiftDownWithcompareTo(int parent){
  116. // child作用:标记最小的孩子
  117. // 因为堆是一个完全二叉树,而完全二叉树可能有左没有有
  118. // 因此:默认情况下,让child标记左孩子
  119. int child = parent * 2 + 1;
  120. // while循环条件可以一直保证parent左孩子存在,但是不能保证parent的右孩子存在
  121. while(child < size)
  122. {
  123. // 找parent的两个孩子中最小的孩子,用child进行标记
  124. // 注意:parent的右孩子可能不存在
  125. // 向上转型,因为E的对象都实现了Comparable接口
  126. if(child+1 < size && ((Comparable<? super E>)hp[child]).
  127. compareTo((E)hp[child])< 0){
  128. child += 1;
  129. }
  130. // 如果双亲比较小的孩子还大,将双亲与较小的孩子交换
  131. if(((Comparable<? super E>)hp[child]).compareTo((E)hp[parent]) < 0){
  132. swap((E[])hp, child, parent);
  133. // 小的元素往下移动,可能导致parent的子树不满足堆的性质
  134. // 因此:需要继续向下调整
  135. parent = child;
  136. child = child*2 + 1;
  137. }
  138. else{
  139. return;
  140. }
  141. }
  142. }
  143. // 向上调整
  144. void shiftUp(int child){
  145. if(null == comparator){
  146. shiftUpWithCompareTo(child);
  147. }
  148. else{
  149. shiftUpWithComparetor(child);
  150. }
  151. }
  152. void shiftUpWithComparetor(int child){
  153. // 获取孩子节点的双亲
  154. int parent = ((child-1)>>1);
  155. while(0 != child){
  156. // 如果孩子比双亲还小,则不满足小堆的性质,交换
  157. if(comparator.compare((E)hp[child], (E)hp[parent]) < 0){
  158. swap((E[])hp, child, parent);
  159. child = parent;
  160. parent = ((child-1)>>1);
  161. }
  162. else{
  163. return;
  164. }
  165. }
  166. }
  167. void shiftUpWithCompareTo(int child){
  168. // 获取孩子节点的双亲
  169. int parent = ((child-1)>>1);
  170. while(0 != child){
  171. // 如果孩子比双亲还小,则不满足小堆的性质,交换
  172. if(((Comparable<? super E>)hp[child]).compareTo((E)hp[parent]) < 0){
  173. swap((E[])hp, child, parent);
  174. child = parent;
  175. parent = ((child-1)>>1);
  176. }
  177. else{
  178. return;
  179. }
  180. }
  181. }
  182. void swap(E[] hp, int i, int j){
  183. E temp = hp[i];
  184. hp[i] = hp[j];
  185. hp[j] = temp;
  186. }
  187. // 仿照JDK8中的扩容方式,注意还是有点点的区别,具体可以参考源代码
  188. void grow(){
  189. int oldCapacity = hp.length;
  190. if(size() >= oldCapacity){
  191. // Double size if small; else grow by 50%
  192. int newCapacity = oldCapacity + ((oldCapacity < 64) ?
  193. (oldCapacity + 2) :
  194. (oldCapacity >> 1));
  195. hp = Arrays.copyOf(hp, newCapacity);
  196. }
  197. }
  198. public static void main(String[] args) {
  199. int[] arr = {
  200. 4,1,9,2,8,0,7,3,6,5};
  201. // 小堆---采用比较器创建小堆
  202. MyPriorityQueue<Integer> mq1 = new MyPriorityQueue(new LessIntComp());
  203. for(int e : arr){
  204. mq1.offer(e);
  205. }
  206. // 大堆---采用比较器创建大堆
  207. MyPriorityQueue<Integer> mq2 = new MyPriorityQueue(new GreaterIntComp());
  208. for(int e : arr){
  209. mq2.offer(e);
  210. }
  211. // 小堆--采用CompareTo比较创建小堆
  212. MyPriorityQueue<Integer> mq3 = new MyPriorityQueue();
  213. for(int e : arr){
  214. mq3.offer(e);
  215. }
  216. }
  217. }

202304211053809.png

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!?

发表评论

表情:
评论列表 (有 0 条评论,142人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java对象比较

    本文章主要介绍了Java对象的比较,首先我们介绍了元素和对象之间分别是如何比较的,然后我们讲解了继承Comparable接口进行比较,最后讲解了什么是PriorithQu...

    相关 Java - 对象比较

    一、问题提出 > 前面讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,为了简单起见,我们只是插入了Integer类

    相关 Java对象比较

    一 点睛 在Java中,有两种方式可用于对象间的比较: 利用"=="运算符:用于比较两个对象的内存地址值(引用值)是否相等。 利用equals()方法:用于比较