Java对象的比较

简介: Java对象的比较

PriorityQueue中插入对象

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

来看下面这个例子:

class Student {
    public int age;//年龄
    public String name;//姓名
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
}
 
public class TestPriorityQueue {
    public static void TestPriorityQueue(){
        PriorityQueue<Student> p = new PriorityQueue<>();
        p.offer(new Student(18, "lisi"));
        p.offer(new Student(6, "wangwu"));
    }
 
    public static void main(String[] args) {
        TestPriorityQueue();
    }
}
 

你会发现这样的运行结果:

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

元素的比较

基本类型的比较

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

public class TestCompare {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        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 = true;
        boolean b2 = false;
        System.out.println(b1 == b2);
        System.out.println(b1 != b2);
    }
}

对象比较问题

class Student {
    public int age;//年龄
    public String name;//姓名
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
}
 
public class TestPriorityQueue {
    public static void main(String[] args) {
        Student s1 = new Student(18, "lisi");
        Student s2 = new Student(6, "wangwu");
        Student s3 = s1;
 
        //System.out.println(s1 > s2); 编译报错
        System.out.println(s1 == s2);//编译成功->打印false,因为它们指向的是不同对象
        //System.out.println(s1 < s2); 编译报错
        System.out.println(s1 == s2);//编译成功->打印true,因为s1和s3指向的是同一对象
    }
}
 

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

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

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

对象的比较

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

覆写基类的equals

代码如下:

class Student {
        public int age;//年龄
        public String name;//姓名
 
        public Student(int age, String name) {
            this.age = age;
            this.name = name;
        }
    }
    
    @Override
    public boolean equals(Object o) {
        //自己和自己比较
        if(this == o) {
            return true;//指向一个对象则返回true
        }
        //o如果是null对象,或者o不是Student的子类
        if(o == null || !(o instanceof Student)) {
            return false;
        }
 
        //注意基本类型可以直接比较,但引用类型最好调用其equal方法
        Student c = (Student)o;
        return age == c.age && name.equals(c.name);
    }

注意:一般覆写equals的套路如上

1.如果指向同一个对象,返回true

2.如果传入的是null,返回false

3.如果传入的对象类型不是Student, 返回false

4.按照类的实现目标完成比较,例如这里只要年龄和姓名一样,就认为是相同的学生

5.注意下调调用其它的引用类型的比较也需要equals,例如这里的name比较

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

基于Comparble接口类的比较

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

public interface Comparable<E> {
    //返回值
    //<0:表示this指向的对象小于o指向的对象
    //==0:表示this指向的对象等于o指向的对象
    //>0:表示this指向的对象大于o指向的对象
    int compareTo(E o);
}

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

使用举例:

public class Student implements Comparable<Student> {
    public int age;
    public String name;
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
 
    //根据年龄比较,不管姓名
    //这里我们认为null是最小的
    @Override
    public int compareTo(Student o) {
        if(o == null) {
            return 1;
        }
        return age - o.age;
    }
 
    public static void main(String[] args) {
        Student s1 = new Student(18, "lisi");
        Student s2 = new Student(6, "wangwu");
        Student s3 = new Student(18, "wangwu");
 
        System.out.println(s1.compareTo(s3));//==0
        System.out.println(s1.compareTo(s2));//>0
        System.out.println(s2.compareTo(s1));//<0
    }
}

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

基于比较器的比较

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

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

public interface Comparator<T> {
    //返回值
    //<0:表示o1指向的对象小于o2指向的对象
    //==0:表示o1指向的对象等于o2指向的对象
    //>0:表示o1指向的对象大于o2指向的对象
    int compare(T o1, T o2);
}
 

注意:区分Comparable和Comparator.

覆写Comparator中的compare方法

代码如下:

class Student {
    public int age;//年龄
    public String name;//姓名
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
}
 
public class StudentComparator implements Comparator<Student> {
    //根据年龄比较,不管姓名
    //这里我们认为null是最小的
    @Override
    public int compare(Student o1, Student o2) {
        if(o1 == o2) {
            return 0;
        }
 
        if(o1 == null) {
            return -1;
        }
 
        if(o2 == null) {
            return 1;
        }
 
        return o1.age - o2.age;
    }
 
    public static void main(String[] args) {
        Student p = new Student(18, "lisi");
        Student q = new Student(6, "wangwu");
        Student o = new Student(18, "wangwu");
 
        //定义比较器对象
        StudentComparator comparator = new StudentComparator();
 
        //使用比较器对象进行比较
        System.out.println(comparator.compare(p, o));
        System.out.println(comparator.compare(p, q));
        System.out.println(comparator.compare(q, p));
    }
}

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

三种方式的对比

覆写的方法 说明
Object.equals 因为所有类都是继承自Object的,所以直接覆写即可,不过只能比较相等与否
Comparable.compareTo 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序
Comparator.compare 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强

集合框架中PriorityQueue的比较方式

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

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

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

相关文章
|
5天前
|
安全 Java 编译器
java中类与对象回顾总结-2
java中类与对象回顾总结
20 3
|
5天前
|
Java 编译器
java中类与对象回顾总结-1
java中类与对象回顾总结
15 3
|
5天前
|
Java
【专栏】Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
|
5天前
|
消息中间件 Java RocketMQ
MQ产品使用合集之在同一个 Java 进程内建立三个消费对象并设置三个消费者组订阅同一主题和标签的情况下,是否会发生其中一个消费者组无法接收到消息的现象
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
10 1
|
5天前
|
Java 编译器
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
11 4
|
5天前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
23 4
|
5天前
|
Java
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
|
5天前
|
缓存 Java 程序员
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
|
5天前
|
Java
从源码出发:JAVA中对象的比较
从源码出发:JAVA中对象的比较
20 3
|
5天前
|
Java
Java一分钟之-类与对象:面向对象编程入门
【5月更文挑战第8天】本文为Java面向对象编程的入门指南,介绍了类与对象的基础概念、常见问题及规避策略。文章通过代码示例展示了如何定义类,包括访问修饰符的适当使用、构造器的设计以及方法的封装。同时,讨论了对象创建与使用时可能遇到的内存泄漏、空指针异常和数据不一致等问题,并提供了相应的解决建议。学习OOP需注重理论与实践相结合,不断编写和优化代码。
30 1