【JavaSE】对象的比较

news2025/1/12 15:46:03

哈喽,大家好!我是保护小周ღ,本期为大家带来的是Java中自定义类型(对象)的三种比较方式equals 方法, Comparable 泛型接口, Comparator 泛型接口 。在日常编程中,我们常常会需要比较的问题,那么对于我们的基本数据类型来讲可以直接使用 比较运算符来比较大小(> , < ,== , >= , < =…… ),实际中在Java开发中我们经常使用的是“类”类型,也就是对象,那么作为一个对象又该怎么样去比较数据呢?


一、基本数据类型及Integer包装类的比较

几乎任何一种编程语言都支持基本数据类型比较。

但是对于基本数据来讲,背后还有功能更加强大的包装类,也就是引用类型。

引用类型的数据为什么不能比较呢?首先画个图浅浅的理解一下。

Integer valA = new Integer(10);

为什叫引用类型呢,因为访问数据是通过Java栈区上的引用变量存储的地址来达到访问Java 堆区上对象的数据,可以理解为头在栈区,身体在堆区,只有通过头才能找到身体,引用类型的本质是地址.

那么一块地址值又怎么能够比较?即使可以比较,那么又怎么能达到我们想要的效果呢。

理论上引用类型是不可以比较的,但是有一个特例,就是Integer 包装类。

1.1 包装类的装箱/装包操作

装箱/装包 :把基本数据类型转换成 引用类型(对应的包类型)

在进行自动“装箱”的时候,IDEA会自动调用一个 Integer.valueOf() 方法,来帮我们转换,由这个方法我们可以看到当数值的范围在 [-128, 127] 的范围内存储的是基本数据类型,所以在这个范围内的 Integer 类型的数据是可以直接使用比较运算符比较的。

当数值处在[-128, 127] 的范围内,我们Integer 的底层实际上就是调用数组来存储数据。

如果我们超过这个范围就不行了,原因是变成“引用类型了"。地址是无法比较的。

如果我们直接使用关键字 "new" 来创建一个对象值。

其实也不难想象,这个时候 valA 和 valB 是妥妥的引用类型,即指向对象的地址,他们虽然拥有相同的数值,但是他们指向了不同的两个对象,所以两个地址之间的比较是不可能相同的。我们本质是 期望比较数值 127 的关系,很明显达不到我们的要求。


二、对象的比较

对象的比较有三种比较方式:

2.1 equals 方法

Java 中所有的类都继承于 Object 类,Object 类中提供了一个 equals 方法,但是 equals 方法实际上比较的是引用变量的存储的地址而不是比较的是引用类型引用的对象的内容。

这里是 Object 实现的 equals 方法的实现。

public boolean equals(Object obj) {
   return (this == obj);
}

this 表示对当前类引用变量的引用,由此可见,实际上还是比较两个地址是否相同。

所以当我们想要判断两个对象是都相同的时候需要重写我们的 equals 方法,按照我们想要的方式来比较对象的内容判断是否相同。

定义一个学生类 Student, 成员变量: Id 学号, name 姓名, age 年龄, sex 性别,并提供一个构造方法

public class Student {
    public int id;      //学号
    public String name; //姓名
    public int age;     //年龄
    public String sex;  //性别

    //构造方法
    public Student(int id, String name, int age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() { //重写toString 方法方便打印展示效果。
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

来到测试类这边 new 两个对象直接使用 equals 方法来比较。

很明显是不相同的,原因是他是直接比较的两个引用类型存储的地址,这两个对象虽然数值相同,但是他们是两个独立的对象,所以比较的返回值就是不相同的。


重写 equals 方法,我们设计当对象之间的成员变量的数值相同,则认为这两个对象相同。

首先我们需要在 Student 类中重写,因为是基于 Student 类比较嘛,子类重写父类方法,子类对象在调用该方法时调用重写后的方法,没有重写的话默认直接调用父类的方法。

快捷键 :Alt + insert

会弹出这个界面,选择 equals() and hashCode, 一路 next 即可。

在每个类中,在重写equals方法的时侯,一定要重写hashcode方法。这是Java官方的语法规定

这里还有一个 hashCode() 方法没有介绍,hashCode 是通过哈希函数计算映射出这个对象的地址,

equals 是基于对象的比较。

面试问题:

equals 比较一样,hashCode 一定一样吗? 一定一样

hachCode 比较一样,equals 一定一样吗? 不一定

为什么呢? hashCode值是依据地址通过某种公式计算来的, 当遇到不同的地址时不能保证计算的结果一定不相同。如果二者是相同的地址,那么通过计算后,得到的hashCode 值是必然相同的。

当只重写equals方法,不重写hashCode时,违反规定:equals相等的对象必须具有相等的哈希码值


总结: 重写equals 方法

如果两个引用指向的是同一个对象,直接返回 true
如果传参的引用指向的为 null ,或者传入的对象所属的类与比较的类不一致返回 false
按照类的这个属性进行比较,例如 Id, name , sex 等相同,那么认为是同一个学生。
String 类型在比较的时候也是调用了 equals 方法,是比较字符串是否相同,String 也是引用类型嘛

涉及到的 Java语法知识:

1.当发生向上转型之后, 此时通过父类的引用只能访问父类自己的成员,不能访问子类特有的成员
2.向上转型后 子类重写的后的方法, 父类调用该方法,此时是调用子类重写后的方法,发生动态绑定
3.动态绑定: 通过父类引用 引用子类对象重写后的方法
4.发生向上转型之后 如果父类引用想使用子类特有的 成员 ,需要进行向下转型(强制类型转换)
5. 向下转型 不安全只能发生在 向上转型 的类型之间, 且右边的范围(父类)大于 左边(子类)
6. 可以用 instanceof 关键字 判断 向下转型是否安全

注意:equals 只能按照相等进行比较,不能按照大于,小于的方式进行比较。

重写 equals 方法后就可以判断 两个对象是否相同了。


2.2 Comparable 接口类的比较

Comparable接口是Java JDK 提供给用户用来实现对象比较的一个泛型接口,然后覆写该接口的一个compareTo 方法,比较的规则就是在该方法定义的,下面我们来看看这个接口的简介:

public interface Comparable<T> {
    public int compareTo(T o);
}

以上代码可以看到, compareTo 方法的返回值是整型int 数据

返回值 < 0; 表示this 指向的对象小于 o 指向的对象。
返回值 == 0; 表示this 指向的对象等于 o 指向的对象。
返回值 > 0; 表示this 指向的对象大于 o 指向的对象。

我们在面对对自定义类型的时候,如果要想按照大小进行比较时:在定义类时实现Comparable接口即可,然后在类中重写compareTo方法。

如果只是Integer , double 等等基本数据类型之间的比较就可以直接实现该接口然后调用 compareTo() 方法。

以Student 类为例:

public class Student implements Comparable<Student>{
    public int id;      //学号
    public String name; //姓名
    public int age;     //年龄
    public String sex;  //性别

    //构造方法
    public Student(int id, String name, int age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /**
     * 重写 compareTo 方法,博主设计 按照 id 来比较数据从而判断对象的大小
     * @param o
     * @return
     */
    @Override
    public int compareTo(Student o) {
        return this.id - o.id;
    }

    @Override
    public String toString() { //重写toString 方法方便打印展示效果。
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

可以看到,对象之间就可以根据我们的需求来比较对象之间的大小,博主首先设计的是 采用 id 作为 关键字来比较,但是如果我们想要采用姓名 或者是 年龄 等等来作为关键字来比较对象的大小,我们就需要修改compareTo 方法的比较方式。

如果我们采用name 来比较数据:

/**
     * 重写 compareTo 方法,博主设计 按照 name 来比较数据从而判断对象的大小
     * @param o
     * @return
     */
    @Override
    public int compareTo(Student o) {
        if(this.name.compareTo(o.name) == 0) { //字符串的比较 调用 String 的 compareTo 方法
            return 0;
        } else if (this.name.compareTo(o.name) < 0) {
            return -1;
        } else {
            return 1;
        }
         //为了方便理解所以上面写的复杂,按照下面的方式更好
        /*return o1.name.compareTo(o2.name);*/
    }

关于String 类型的对象的比较,我们也可以使用 String 类下的 compareTo 方法我们来看看 JDK 是如何实现这个方法的:

 public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

首先计算两个字符串的长度,转换成字符数组,拿最小的lim字符长度来遍历字符串,在lim 范围内比较,如果字符不相同就返回 两个字符 ASCll 的差值,如果在 lim 的范围内字符都相同,则返回两个字符串长度的差值。


总结:

1.我们想要按照某个关键字来比较对象之间大小的关系,我们可以让实现 Comparable 接口,然后根据实际环境的需要重写compareTo 方法。
2. 使用Comparable 接口来比较对象之间的大小的关系的时候,我们一般是 写死compareTo 方法,无法灵活的更改关键字来达到更改对象比较的方式。比如说我们的通讯录,可以按照我们的姓氏首字母来排序,也可以根据修改日期,可以根据号码排序,我们使用 compareTo 方法比较岂不是每次都要修改源代码?这是不现实的,那怎么办呢,接下来会讲。
3. Comparable 是 Java.lang 中的接口类,可以直接使用。

2.3 Comparator 接口(基于比较器比较)

使用这个接口需要我们自己定义一个比较器类然后实现 Comparator泛型接口,再重写 compare() 方法。

class idComparator implements Comparator<Student>{ //通过学号来比较大小
    @Override
    public int compare(Student o1, Student o2) {
        return o1.id - o2.id;
    }
}

兄弟们注意:Comparator 跟 Comparable 的区别。

class idComparator implements Comparator<Student> { //通过学号来比较大小
    @Override
    public int compare(Student o1, Student o2) {
        return o1.id - o2.id;
    }
}

class nameComparator implements Comparator<Student> { //通过姓名来比较大小
    @Override
    public int compare(Student o1, Student o2) {
        if(o1.name.compareTo(o2.name) == 0) { //字符串的比较 调用 String 的 compareTo 方法
            return 0;
        } else if (o1.name.compareTo(o2.name) < 0) {
            return -1;
        } else {
            return 1;
        }
    }
}

public class Test3 {
    public static void main(String[] args) {
        Student student1 = new Student(202300,"李三",19,"男");
        Student student2 = new Student(202301,"张四",19,"男");

        idComparator idComparator = new idComparator(); //按照 id 来比较
        System.out.println(idComparator.compare(student1, student2));

        nameComparator nameComparator = new nameComparator();
        // l 在 z 的前面嘛,所以返回的是 1;
        System.out.println(nameComparator.compare(student1, student2)); 
    }
}

采用这种方式我们才可能实现关键字随需变换的机制,但是还有一点问题,就是我们我们虽然写了很多比较类,但是我们怎么做到在Student类外传入比较器,从而改变Student类内部的比较方式呢?

如果我们在 Student 类的内部需要比较的形式,要求是通过类外影响Student 类的比较机制。

首先我们在 Student 类的内部既然需要比较那肯定涉及到方法,我们就可以对比较的方法入手,如果将比较器以传参的形式输入,那么类方法就在内部拿到比较器,从而通过传参的比较器从而达到计较的目的。

现在摆在我们眼前的是如何接收比较器对象,因为我们的需求是可以接收多个比较器对象。

以该比较器为例:

class idComparator implements Comparator<Student>{ //通过学号来比较大小
    @Override
    public int compare(Student o1, Student o2) {
        return o1.id - o2.id;
    }
}

我们使得 idComparator 类继承了Comparator<Student> 的泛型接口,然后我们重写了接口的 compare() 方法,两个Student对象参数根据我们设计的某种机制比较大小,返回 int 类型的数据。

所以我们可以采用 Comparator 接口来接收(Student)泛型对象。

接口作为方法的参数,可以接收该接口的所有实现类的对象

接口作为方法的返回值,可以返回该接口的所有实现类的对象

JDK 关于这方面的实现是使用 Comparator<? super T> 来接收比较器对象。

Comparator<? super T> 代表任意T的父类或祖先,Comparator<? super Student>可以表示接受任意一个泛型类型是Student父类的Comparator,比如一个Comparator<Person>可以给所有Person(人)比较,那么自然也可以给Student比较。

? 是通配符表示可传入的类型不确定,通常需要限制一下范围,super T就是限定条件,表示 ?只能接收T类型及其父类型。 T 类型此时是接收了 Student 类型。

? extends T:可以接收T类型或者T类型的子类,泛型的上限

? super T:可以接收E类型或者E的父类型, 泛型的下限

话不多说直接上代码:

 /**
     * 冒泡排序 从小到大排序
     * @param stu
     * @param c
     * @return
     */
    public static <T> T[] bubbleSort(T[] stu, Comparator<? super T> c) {
        if(stu == null || c == null) {
            return stu;
        }
        for (int i = 0; i < stu.length - 1; i++) {
            for (int j = 1; j < stu.length - i; j++) {
                if(c.compare(stu[j - 1],stu[j]) > 0) {
                    T tmp = stu[j - 1];
                    stu[j - 1] = stu[j];
                    stu[j] = tmp;
                }
            }
        }
        return stu;
    }

为了方便理解博主简单的写了一个冒泡排序,然后该方法是静态方法,可以直接使用类名调用,静态的泛型方法需要声明一下类型 <T>。

可以看到非常的成功,如果我们想要根据姓名来排序,直接new 一个我们设计的name类来作为参数即可。

总结:

1. equals 适用于比较两个对象之间的相等于否。
2. Comparable 泛型接口 适用于类的内部比较,如果想要对自定义对象按照某种方式来进行比较,则需要该类需要实现 Comparable 接口,并重写 compareTo() 方法,这 种情况下比较方式基本上是写死的
3. Comparable 泛型接口 适用于自定义类型的灵活比较,需要自己选择比较器对象,提供比较器类,可以在类的内部,也可以在类的外部传参比较器,这个需要根据自己的设计和需求来,使用该接口需要重写 compare() 方法。

至此,Java 的对象的比较博主已经分享完了,希望对大家有所帮助,如有不妥之处欢迎批评指正。

本期收录于博主的专栏——JavaSE,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“JavaSE基础知识”。

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ *★,°*:.☆( ̄▽ ̄)/$:*.°★*

do

cc即撒后看来大家了类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cuda版本,pytorch(GPU)版本的选择和下载

cuda版本&#xff1a; 1.Nvidia控制面板里的cuda版本, 或使用nvidia-smi命令显示的cuda版本 是cuda的driver api版本 2.nvcc -V中的cuda版本&#xff0c;是cuda的runtime api版本&#xff0c;即cudatoolkit的版本 cudatoolkit的版本不能高于cuda driver api的版本&#xff…

23.2.28 Staffing System

员工管理系统功能介绍&#xff1a; 1&#xff09;服务器负责管理所有员工表单&#xff08;以数据库形式&#xff09;&#xff0c;其他客户端可通过网络连接服务器来查询员工表单。 2&#xff09;需要账号密码登陆&#xff0c;其中需要区分管理员账号还是普通用户账号。 3&am…

聚观早报 | 苹果2024年放弃高通;腾讯回应进军类 ChatGPT

今日要闻&#xff1a;苹果2024年放弃高通&#xff1b;腾讯回应进军类 ChatGPT&#xff1b;小米发布无线AR眼镜探索版&#xff1b;50%的美国企业已在使用ChatGPT&#xff1b;Snap推出ChatGPT驱动的聊天机器人 苹果2024年放弃高通 高通公司 CEO 兼总裁克里斯蒂亚诺・安蒙&#xf…

Node.js 是个啥?

趣学 Node.js - 死月 - 掘金小册带你重新体悟 Node.js 之美。「趣学 Node.js」由死月撰写&#xff0c;1923人购买https://s.juejin.cn/ds/SYVvuDw/ 在这里&#xff0c;我们先装作对 Node.js 不了解&#xff0c;从头来过吧。你有没有假装不了解 Node.js 我不知道&#xff0c;但…

界面开发(2)--- 使用PyQt5制作用户登陆界面

使用PyQt5制作用户登陆界面 上篇文章已经介绍了如何配置PyQt5环境&#xff0c;这篇文章在此基础上展开&#xff0c;主要记录一下如何使用 PyQt5 制作用户登陆界面&#xff0c;并对一些基础操作进行介绍。 下面是具体步骤&#xff0c;一起来看看吧&#xff01; 1. 打开 Pychar…

【IoT】2023裁员潮还在继续,构建规划能力也许是一剂良方

今天要分享的主题是华为的市场管理方法论。 市场管理这个词总体来说还是有些抽象&#xff0c;本质上来看或者说从个人的角度来看&#xff0c;其实就是一种规划的能力。 无论是创业&#xff0c;还是作为职场人&#xff0c;规划能力必将是你不可或缺的一种基础能力。 尤其是在这样…

Maven说明

目录 1.说明 2.详细说明 3.Maven模型 4.Maven常用的命令 5.Maven生命周期 6.Maven坐标 7.依赖管理与依赖范围 1.说明 Maven是专门用于管理和构建Java项目的工具&#xff0c;它是基于项目对象模型(POM)的概念&#xff0c;主要功能有&#xff1a; 提供了一套标准化的项目…

Ubuntu 下NGINX 的简单使用

1.NGINX的安装与卸载 1.1.安装NGINX apt-get install nginx1.2.NGINX操作命令 service nginx start #启动 service nginx reload #重新加载配置文件 service nginx restart #重启 service nginx status #查看运行状态 1.3.卸载NGINX apt-get remove nginx nginx-common #…

28 openEuler管理网络-配置主机名

文章目录28 openEuler管理网络-配置主机名28.1 简介28.2 使用hostnamectl配置主机名28.2.1 查看所有主机名28.2.2 设定所有主机名28.2.3 设定特定主机名28.2.4 清除特定主机名28.2.5 远程更改主机名28.3 使用nmcli配置主机名28 openEuler管理网络-配置主机名 28.1 简介 hostn…

XXL-JOB的基本使用

1、执行器 1.1下边配置执行器 下边配置执行器&#xff0c;执行器负责与调度中心通信接收调度中心发起的任务调度请求。 1、首先在媒资管理模块的service工程添加依赖&#xff0c;在项目的父工程已约定了版本2.3.1 XML <dependency><groupId>com.xuxueli</gro…

【Web安全社工篇】——水坑攻击

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a;努力赚钱不是因为爱钱“水坑攻击”&#xff0c;黑客攻…

CVPR 2023 接收结果出炉!再创历史新高!录用2360篇!(附10篇最新论文)

点击下方卡片&#xff0c;关注“CVer”公众号AI/CV重磅干货&#xff0c;第一时间送达点击进入—>【计算机视觉】微信技术交流群2023 年 2 月 28 日凌晨&#xff0c;CVPR 2023 顶会论文接收结果出炉&#xff01;这次没有先放出论文 ID List&#xff0c;而是直接 email 通知作…

【C语言】位段

位段 一.简介 位段和结构体很相似。不同的是&#xff1a; 位段的成员&#xff1a;成员名 : 数字且其成员必须是整型(char、int、unsigned int……) 示例&#xff1a; struct S {char a : 3;char b : 2;char c : 7; };S就是一个位段类型&#xff0c;其成员a为占3个比特位的…

【趣味学Python】Python基础语法讲解

目录 编码 标识符 python保留字 注释 实例(Python 3.0) 实例(Python 3.0) 行与缩进 实例(Python 3.0) 实例 多行语句 数字(Number)类型 字符串(String) 实例(Python 3.0) 空行 等待用户输入 实例(Python 3.0) 同一行显示多条语句 实例(Python 3.0) 多个语句构…

【Day02数据结构 空间复杂度】

最近太忙了都好久没有更新博客了,太难了,抽空水一篇文章,大佬们多多支持. 上篇:时间复杂度分析 目录 前言 一、空间复杂度概念&#xff1f; 二、实例展示 三、.有复杂度要求的算法题练习 1.题目链接&#xff1a;力扣--消失的数字 2.题目链接&#xff1a;力扣--旋转数组 总结: 1…

去课工场成都基地学Java,可行吗?

当然可行&#xff0c;不管是你选择自学Java&#xff0c;还是去培训机构学习都是非常不错的职业选择。选择好赛道能让你的未来收获更多。 2023年了&#xff0c;随着数字经济的发展&#xff0c;互联网已经渗入我们生活工作的方方面面&#xff0c;现在即使是吃个饭点个餐很多时候…

SpringBoot解决跨域方式

跨域是指在 Web 应用中&#xff0c;一个服务器资源或应用访问另一个服务器资源或应用的资源时候。由于浏览器的同源策略&#xff0c;一般情况下同一个域中的网站或应用可以互相访问资源&#xff0c;但跨域访问会被浏览器拒绝。浏览器出于安全考虑&#xff0c;会限制跨域访问&am…

深度学习领域的多任务学习综述

文章目录前言1. 什么是多任务学习&#xff1f;2. 为何要使用多任务学习&#xff1f;3. 多任务学习有哪些类型&#xff1f;3.1 基于硬参数共享的多任务学习3.2 基于软参数共享的多任务学习4. 为什么多任务学习能提升模型的性能&#xff1f;4.1 隐藏数据扩充&#xff08;Implicit…

关于sudo配置

前言这里做一个小补充&#xff0c;主要讲一下关于利用sudo对指令提权以及普通用户无法使用sudo指令的问题。在前面的文章【Linux】一文掌握Linux权限中&#xff0c;我们讲到了关于权限的一些问题。我们知道root身份下&#xff0c;一切畅通无阻&#xff0c;而权限只是用来限制我…

urp SpotLight 衰减方式扩展

背景&#xff1a; 解决默认spotLight 的衰减模式下&#xff0c; 在距离灯光特别近的时候&#xff0c;灯光过爆的情况 解决方法&#xff1a; 修改SpotLight的衰减方式 下图是unity给出的几种衰减模式以及图示&#xff1a; 其中InverseSquare是当前2021.2 unity版本中urp(12.1…