目录

一  Java对象的比较

1.1  基本类型的比较

1.2  引用类型的比较

二  集合框架中PriorityQueue(堆)的比较方式

2.1  小根堆的实现  

2.2  大根堆的实现

2.2.1  使用Comparble接口的比较方式

2.2.2  实现 Comparator接口的比较方式

三  对象的比较

3.1 覆写基类的equals

3.2 基于Comparble接口类的比较

3.3 基于比较器 Comparator接口比较

3.4  小结:三种方式对比


一  Java对象的比较
1.1  基本类型的比较

        在Java中,基本类型的对象(byte , short , int , long , float , double , char , boolean)可以直接比较大小。

        int a1 = 11;
        int a2 = 22;
        System.out.println(a1 > a2);
        System.out.println(a1 < a2);
        System.out.println(a1 == a2);

        char c1 = 'A';
        char c2 = 'B';
        System.out.println(c1 > c2);
        System.out.println(c1 < c2);
        System.out.println(c1 == c2);


        boolean b1 = true;
        boolean b2 = false;
        System.out.println(b1 == b2);
        System.out.println(b1 != b2);
1.2  引用类型的比较

        在介绍引用类型比较之前,请大家先猜一猜下面这段代码的运行结果。

            Student student1 = new Student("zhangsan", 13);
            Student student2 = new Student("lisi", 12);
            Student student3 = student1;
            System.out.println(student1 > student2);
            System.out.println(student1 < student2);
            System.out.println(student1 == student2);
            System.out.println(student1 == student3);

        其中,第一行输出和第二行输出均报错,第三行输出的结果为false,因为student1和student2指向的是不同对象,第四行输出的结果为true,因为student1和student3指向的是同一个对象。

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

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

二  集合框架中PriorityQueue(堆)的比较方式
2.1  小根堆的实现  

        在默认情况下,Java中的PriorityQueue均为小根堆,这显然并不满足我们日常的使用需求。

            PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
            priorityQueue.offer(5);
            priorityQueue.offer(3);
            priorityQueue.offer(2);
            priorityQueue.offer(4);

            System.out.println(priorityQueue.poll());

        此时,打印结果为2。

2.2  大根堆的实现

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

        我们可以先看一下在JDK中的中PriorityQueue的实现:

        public class PriorityQueue<E> extends AbstractQueue<E>
            implements java.io.Serializable {

// 默认容量
        private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 内部定义的比较器对象,用来接收用户实例化PriorityQueue对象时提供的比较器对象
        private final Comparator<? super E> comparator;
// 用户如果没有提供比较器对象,使用默认的内部比较,将comparator置为null
        public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
            }
// 如果用户提供了比较器,采用用户提供的比较器进行比较
        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
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
               }

        可以看出,在PriorityQueue的实现中,默认的初始大小为11,默认的比较方法是采用系统提供的比较方法,即建立小根堆。如果用户提供了比较器,采用用户提供的比较器进行比较。也就是说我们可以自定义一个比较器来进行元素的比较,从而可以实现大根堆。

2.2.1  使用Comparble接口的比较方式

        Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法。

使用lambda表达式写法:

PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>((o1,o2) -> {return o2.compareTo(o1);});
2.2.2  实现 Comparator接口的比较方式

        用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现 Comparator接口并覆写compare方法。

写法1:传入比较器

class IntegerCmt implements Comparator<Integer>{
    @Override
    public int compare(Integer t1, Integer t2) {
        return t2.compareTo(t1);
    }
}

IntegerCmt integerCmt = new IntegerCmt();
PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(integerCmt);

写法2:匿名内部类


 PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(new Comparator<Integer>() {
     @Override
     public int compare(Integer integer, Integer t1) {
                return t1.compareTo(integer);
            }
        });

        此时的priorityQueue2底层则为大根堆。

三  对象的比较

   有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?那么我们就以上述无法进行大小比较的Student对象为例。

3.1 覆写基类的equals

        equals方法是用来比较两个对象是否相等的最常用方法之一。通常需要重写equals方法来根据类的需求来比较两个类的对象是否相等。需要注意的是,重写equals方法时需要遵守以下几点:

  • 对称性:如果a.equals(b)返回true,则b.equals(a)也应该返回true。
  • 自反性:a.equals(a)应该返回true。
  • 传递性:如果a.equals(b)返回true,b.equals(c)也返回true,则a.equals(c)也应该返回true。
  • 一致性:如果a.equals(b)返回true,那么在a和b之间的比较应该保持不变,直到a或b被修改。
  • 非空性:a.equals(null)应该返回false。
       @Override
       public boolean equals(Object o) {
           if (this == o) return true;
           if (o == null || getClass() != o.getClass()) return false;
           Student student = (Student) o;
           return age == student.age && Objects.equals(name, student.name);
       }

       @Override
       public int hashCode() {
           return Objects.hash(name, age);
       }

        如果指向同一个对象,返回 true, 如果传入的为 null,返回 false,如果传入的对象类型不是 Student,返回 false, 按照类的实现目标完成比较,例如这里只要姓名和年龄一样,就认为是相同的。注意下调用其他引用类型的比较也需要 equals,例如这里的name的比较覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行 比较。

3.2 基于Comparble接口类的比较

        Comparable接口是Java中提供的一种比较机制,通过实现该接口,对象可以进行自然排序,即按照其属性的自然顺序进行排序。实现Comparable接口需要实现compareTo方法,该方法返回一个整数值,表示要比较的对象与当前对象的大小关系。compareTo方法需要遵守以下规则:

  • 如果当前对象小于要比较的对象,则返回负整数;
  • 如果当前对象等于要比较的对象,则返回0;
  • 如果当前对象大于要比较的对象,则返回正整数。
class Student implements Comparable<Student>{
       public String name;
       public int age;

       public Student(String name, int age) {
           this.name = name;
           this.age = age;
       }
    @Override
    public int compareTo(Student student) {
        return this.age - student.age;
}

        如下代码的结果为1。 

     Student student1 = new Student("zhangsan",13);
     Student student2 = new Student("lisi",12);
     System.out.println(student1.compareTo(student2));

      

3.3 基于比较器 Comparator接口比较

        Comparator接口是Java中提供的一种比较机制,通过实现该接口,可以对不同的对象进行比较。Comparator接口需要实现compare方法,该方法返回一个整数值,表示要比较的两个对象之间的大小关系。compare方法需要遵守以下规则:

  • 如果第一个对象小于第二个对象,则返回负整数;
  • 如果第一个对象等于第二个对象,则返回0;
  • 如果第一个对象大于第二个对象,则返回正整数。

        Comparator接口适用于需要根据不同的属性进行比较的情况,可以灵活地在代码中进行调用。例如:

        按照姓名进行比较:

class NameComparator implements Comparator<Student>{

    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

        按照年龄进行比较:

class AgeComparator implements Comparator<Student>{

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

        如下代码的结果分别为1,14,结果返回值均大于0。 

        NameComparator nameComparator = new NameComparator();
        AgeComparator ageComparator = new AgeComparator();
        System.out.println(ageComparator.compare(student1, student2));
        System.out.println(nameComparator.compare(student1, student2));

        

3.4  小结:三种方式对比

Object.equals: 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否。

Comparable.compareTo 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序。

Comparator.compare 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐