java中对象的比较

谁践踏了优雅 2024-02-23 08:12 88阅读 0赞

文章目录

  • 一、 PriorityQueue中插入对象
  • 二、元素的比较
    • 2.1 基本类型的比较
    • 2.2 引用类型比较
  • 三、对象的比较
    • 3.1 覆写基类的equals
    • 3.2 基于Comparble接口类的比较
    • 3.3 基于比较器比较
    • 3.4 三种方式对比
  • 四、 集合框架中PriorityQueue的比较方式
  • 五、使用PriorityQueue创建大小堆,解决TOPK问题

一、 PriorityQueue中插入对象

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

  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. PriorityQueue<Card> p = new PriorityQueue<>();
  12. p.offer(new Card(1, "♠"));
  13. p.offer(new Card(2, "♠"));
  14. }
  15. public static void main(String[] args) {
  16. TestPriorityQueue();
  17. }
  18. }

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

二、元素的比较

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); // 编译成功 ----> 打印false,因为c1和c2指向的是不同对象
  16. //System.out.println(c1 < c2); // 编译报错
  17. System.out.println(c1 == c3); // 编译成功 ----> 打印true,因为c1和c3指向的是同一个对象
  18. }
  19. }

Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较, 那为什么可以比较?
因为对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而默认情况下调用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用的对象内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。

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

三、对象的比较

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

3.1 覆写基类的equals

  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 && suit.equals(c.suit);
  21. }
  22. }

一般覆写 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. }

Compareble是java.lang中的接口类,可以直接使用。

3.3 基于比较器比较

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

  1. public interface Comparator<T> {
  2. // 返回值:
  3. // < 0: 表示 o1 指向的对象小于 o2 指向的对象
  4. // == 0: 表示 o1 指向的对象等于 o2 指向的对象
  5. // > 0: 表示 o1 指向的对象等于 o2 指向的对象
  6. int compare(T o1, T o2);
  7. }

用户自定义比较器类,实现Comparator接口,覆写Comparator中的compare方法

  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. //比较器类
  10. class CardComparator implements Comparator<Card> {
  11. // 根据数值比较,不管花色
  12. // 这里我们认为 null 是最小的
  13. @Override
  14. public int compare(Card o1, Card o2) {
  15. if (o1 == o2) {
  16. return 0;
  17. }
  18. if (o1 == null) {
  19. return -1;
  20. }
  21. if (o2 == null) {
  22. return 1;
  23. }
  24. return o1.rank - o2.rank;
  25. }
  26. public static void main(String[] args){
  27. Card p = new Card(1, "♠");
  28. Card q = new Card(2, "♠");
  29. Card o = new Card(1, "♠");
  30. // 定义比较器对象
  31. CardComparator cmptor = new CardComparator();
  32. // 使用比较器对象进行比较
  33. System.out.println(cmptor.compare(p, o)); // 0,表示牌相等
  34. System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小
  35. System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大
  36. }
  37. }

Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包。

3.4 三种方式对比

在这里插入图片描述

四、 集合框架中PriorityQueue的比较方式

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

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

    // JDK中PriorityQueue的实现:
    public class PriorityQueue extends AbstractQueue implements java.io.Serializable{

    1. // 默认容量
    2. private static final int DEFAULT_INITIAL_CAPACITY = 11;
    3. // 内部定义的比较器对象,用来接收用户实例化PriorityQueue对象时提供的比较器对象
    4. private final Comparator<? super E> comparator;
    5. // 用户如果没有提供比较器对象,使用默认的内部比较,将comparator置为null
    6. public PriorityQueue() {
    7. this(DEFAULT_INITIAL_CAPACITY, null);
    8. }
    9. // 如果用户提供了比较器,采用用户提供的比较器进行比较
    10. public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
    11. if (initialCapacity < 1)
    12. throw new IllegalArgumentException();
    13. this.queue = new Object[initialCapacity];
    14. this.comparator = comparator;
    15. }
    16. // ...
    17. // 向上调整:
    18. // 如果用户没有提供比较器对象,采用Comparable进行比较否则使用用户提供的比较器对象进行比较
    19. private void siftUp(int k, E x) {
    20. if (comparator != null)
    21. siftUpUsingComparator(k, x);
    22. else
    23. siftUpComparable(k, x);
    24. }
    25. // 使用Comparable
    26. @SuppressWarnings("unchecked")
    27. private void siftUpComparable(int k, E x) {
    28. Comparable<? super E> key = (Comparable<? super E>) x;
    29. while (k > 0) {
    30. int parent = (k - 1) >>> 1;
    31. Object e = queue[parent];
    32. if (key.compareTo((E) e) >= 0)
    33. break;
    34. queue[k] = e;
    35. k = parent;
    36. }
    37. queue[k] = key;
    38. }
    39. // 使用用户提供的比较器对象进行比较
    40. @SuppressWarnings("unchecked")
    41. private void siftUpUsingComparator(int k, E x) {
    42. while (k > 0) {
    43. int parent = (k - 1) >>> 1;
    44. Object e = queue[parent];
    45. if (comparator.compare(x, (E) e) >= 0)
    46. break;
    47. queue[k] = e;
    48. k = parent;
    49. }
    50. queue[k] = x;
    51. }

    }

五、使用PriorityQueue创建大小堆,解决TOPK问题

  1. //使用比较器创建小根堆
  2. class LessIntComp implements Comparator<Integer>{
  3. @Override
  4. public int compare(Integer o1, Integer o2) {
  5. return o1 - o2;
  6. }
  7. }
  8. //使用比较器创建大根堆
  9. class GreaterIntComp implements Comparator<Integer>{
  10. @Override
  11. public int compare(Integer o1, Integer o2) {
  12. return o2 - o1;
  13. }
  14. }
  15. public class TestDemo<E> {
  16. //求最小的K个数,通过比较器创建大根堆
  17. public static int[] smallestK(int[] array, int k) {
  18. if(k <= 0) {
  19. return new int[k];
  20. }
  21. GreaterIntComp greaterCmp = new GreaterIntComp();
  22. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(greaterCmp);
  23. //先将前K个元素,创建大根堆
  24. for(int i = 0; i < k; i++) {
  25. maxHeap.offer(array[i]);
  26. }
  27. //从第K+1个元素开始,每次和堆顶元素比较
  28. for (int i = k; i < array.length; i++) {
  29. int top = maxHeap.peek();
  30. if(array[i] < top) {
  31. maxHeap.poll();
  32. maxHeap.offer(array[i]);
  33. }
  34. }
  35. //取出前K个
  36. int[] ret = new int[k];
  37. for (int i = 0; i < k; i++) {
  38. int val = maxHeap.poll();
  39. ret[i] = val;
  40. }
  41. return ret;
  42. }
  43. public static void main(String[] args) {
  44. int[] array = {4,1,9,2,8,0,7,3,6,5};
  45. int[] ret = smallestK(array,3);
  46. System.out.println(Arrays.toString(ret));//9 4 1
  47. }
  48. }

发表评论

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

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

相关阅读

    相关 java对象比较

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

    相关 Java - 对象比较

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

    相关 Java对象比较

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