【JavaSE】对象的比较

news/2024/4/30 12:47:03/文章来源:https://blog.csdn.net/weixin_67603503/article/details/129245934

哈喽,大家好!我是保护小周ღ,本期为大家带来的是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;}@Overridepublic 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*/@Overridepublic int compareTo(Student o) {return this.id - o.id;}@Overridepublic String toString() { //重写toString 方法方便打印展示效果。return "Student{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}
}

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

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

/*** 重写 compareTo 方法,博主设计 按照 name 来比较数据从而判断对象的大小* @param o* @return*/@Overridepublic 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>{ //通过学号来比较大小@Overridepublic int compare(Student o1, Student o2) {return o1.id - o2.id;}
}

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

class idComparator implements Comparator<Student> { //通过学号来比较大小@Overridepublic int compare(Student o1, Student o2) {return o1.id - o2.id;}
}class nameComparator implements Comparator<Student> { //通过姓名来比较大小@Overridepublic 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>{ //通过学号来比较大小@Overridepublic 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.luyixian.cn/news_show_75790.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 自带按键驱动

目录 一、内核检查 二、驱动文件 三、设备树 四、验证 一、内核检查 内核一般默认已经使能了 KEY 驱动&#xff0c;但是还是要检查一下。按照如下路径找到相应的配置选项&#xff1a; Device Drivers -> Input device support -> Generic in…

Laravel-admin之自定义操作日志

laravel-admin是封装性极好的框架&#xff0c;自带的就有操作日志的记录&#xff0c;但是对于非开发人员可能看不懂这个日志&#xff0c;所以就想着给修改一下&#xff0c;以谁修改了什么&#xff0c;谁删除了什么&#xff0c;谁审核了什么&#xff0c;谁添加了什么类似&#x…

Java数据结构LinkedList单链表和双链表模拟实现及相关OJ题秒AC总结知识点

本篇文章主要讲述LinkedList链表中从初识到深入相关总结&#xff0c;常见OJ题秒AC&#xff0c;望各位大佬喜欢 一、单链表 1.1链表的概念及结构 1.2无头单向非循环链表模拟实现 1.3测试模拟代码 1.4链表相关面试OJ题 1.4.1 删除链表中等于给定值 val 的所有节点 1.4.2 反转…

vue实现table表格树结构-使用懒加载时-解决子节点增删改后,不刷新子节点数据问题

问题发现 在使用element-ui的table组件时&#xff0c;使用树形结构&#xff0c;并使用了懒加载&#xff0c;可出现了一个问题&#xff0c;在对当前节点添加一个子节点数据&#xff0c;或删除一个子节点数据时&#xff0c;当前节点的子节点数据并不自动刷新出来。element-ui官方…

korean doll likeness模型|Japanese-doll-likeness模型获取及使用

1.模型 之前给大家写了Mac安装stable-diffusion-webui绘制AI妹子保姆级教程&#xff0c;教程在下面 【奶奶看了也不会】AI绘画 Mac安装stable-diffusion-webui绘制AI妹子保姆级教程 今天一早起来打开C站&#xff0c;发现之前热门的几个doll模型都没有了&#xff0c;猜测是某…

2023年湖北助理工程师(初级职称)怎么评?需要什么资料?启程别

2023年湖北助理工程师&#xff08;初级职称&#xff09;怎么评&#xff1f;需要什么资料&#xff1f;启程别 助理工程师主要是指初级工程技术人员的职务名称&#xff0c;他是通过相关考试和相关部门评审通过之后所获得的相应名称&#xff0c;想要了解职称更多相关资料可以咨询启…

pyechart绘制多图(三图及以上)的overlap叠加

pyechart github页面&#xff1a;https://github.com/pyecharts/pyecharts 首先要明确多图叠加到一个图的规则&#xff0c;即多个图只能有一个公共的轴&#xff1a; 比如&#xff0c;横坐标含义相同&#xff08;如时间维度&#xff09;或者&#xff0c;纵坐标取值含义相同 文…

分页与分段

前面我们分析了虚拟地址和物理地址 我们这里进行一个简单的分析 这个是程序运行时的地址映射 那么这些碎片&#xff0c;我们现在的操作系统究竟如何处理呢&#xff1f; 我们再引入一个实际问题 我们如何把右边的进程p塞入左边的内存空间里面 有一种方法将p5kill掉&#xff…

简易计算器-课后程序(JAVA基础案例教程-黑马程序员编著-第十一章-课后作业)

【案例11-2】 简易计算器 【案例介绍】 1.案例描述 本案例要求利用Java Swing 图形组件开发一个可以进行简单的四则运算的图形化计算器。 2.运行结果 运行结果 【案例分析】 要制作一个计算器&#xff0c;首先要知道它由哪些部分组成&#xff0c;如下图所示&#xff1a; 一…

【原创】java+swing+mysql校园订餐管理系统设计与实现

校园订餐管理系统&#xff0c;主要是为了方便广大学生点餐使用&#xff0c;以往的大多数的校园订餐系统基本使用bs架构&#xff0c;也就是网页系统&#xff0c;但是我们今天不用javaweb&#xff0c;我们主要介绍javaswing同样可以去实现一个校园订餐管理系统。 功能分析&#…

「TCG 规范解读」规范结构

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

软测入门(一)测试理念及基础知识

软测入门理念 软件的分类 按层次划分&#xff1a;系统软件、应用软件按组织划分&#xff1a;商业软件、开源软件按结构划分&#xff1a;单机软件、 软件缺陷 由来 Grace Hopper发明Cobol计算机语言&#xff0c;也是找出电脑程序中第一个bug的女程序员 BugDefect 定义 软…

带你掌握webSocket 和 socket.io的基本用法

两者的作用和区别 作用&#xff1a;使得前后端可以随时地相互沟通。什么是互相沟通呢&#xff1f;像网络请求这种就是客户端向服务端的单向的沟通&#xff0c;当然&#xff0c;网络请求也可以实现双向的沟通&#xff0c;比如ajax 轮询&#xff0c;就是浏览器开个定时器不断的发…

【Java】Java进阶学习笔记(四)—— 抽象类与接口

【Java】Java进阶学习笔记&#xff08;四&#xff09;—— 抽象类与接口一、抽象类1、抽象类的概念抽象类的定义格式2、抽象类的注意点抽象方法的介绍3、抽象类的具体作用4、抽象类实例二、接口&#xff08;一&#xff09;、接口的概念1、接口与类的区别2、接口特性3、抽象类和…

如何实现云原生?推荐的几个实用工具

云原生是一种软件开发和部署的方法&#xff0c;它依赖于容器、微服务和自动化运维。它能使应用更高效、可靠和可扩展&#xff0c;并适用于不同的云平台。 如果要更直接、更通俗地解释上述概念的话。 云的本源更准确地说是一种文化&#xff0c;一种潮流&#xff0c;它必然是云…

此网站可能不支持TLS1.2协议

问题描述 火狐浏览器版本&#xff1a;“97.0.1 (64 位)”&#xff0c;打开360网神设备Web管理地址时出现&#xff1a;“此网站可能不支持TLS1.2协议&#xff0c;而这是Firefox支持的最低版本。”&#xff0c;如下图所示。 原本是默认使用https协议打开的&#xff0c;看起来出问…

蓝桥杯每日一题:不同路径数(dfs深度优先)

给定一个 nm的二维矩阵&#xff0c;其中的每个元素都是一个 [1,9] 之间的正整数。 从矩阵中的任意位置出发&#xff0c;每次可以沿上下左右四个方向前进一步&#xff0c;走过的位置可以重复走。 走了 k 次后&#xff0c;经过的元素会构成一个 (k1) 位数。 请求出一共可以走出…

零基础机器学习做游戏辅助第十五课--原神自动钓鱼(五)完整效果

一、先上效果二、整理思路我们现在已经具备了所有需要的技术&#xff0c;我们梳理出所有技术的流程。判断当前钓鱼状态&#xff08;未抛竿、已抛竿、上鱼中&#xff09;。未抛竿&#xff0c;截图并识别图中所有鱼类&#xff0c;选择其中一个种类。根据以选择鱼类选择对应鱼饵。…

从一个实例配置引入Prometheus的PromQL语法

1. PromQL介绍 PromQL提供对时间序列数据进行逻辑运算、过滤、聚合的支持。应用于数据查询、可视化、告警处理 2. 基本用法 2.1 查询时间序列 点击Prometheus图标,进行查询页面。可以点击地图图标查看有哪些metrics name。输入要查询的metrics name和过滤条件,然后点击执行…

2023年功能测试还值得入行吗?

前言 鉴于笔者从13年入行IT行业&#xff0c;经历了只有开发没有测试的阶段&#xff0c;经历了14年只要会基本的功能测试在一线就能薪资过万的阶段&#xff0c;经历了17年只要会一点自动化&#xff0c;会一点性能就能蒙骗过面试官的阶段&#xff0c;更经历了19年所有面试官对于…