尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java对象比较与克隆:Comparable、Comparator接口与深拷贝实现
接口使用实例以及Object类中的equals,hashcode方法1.Comparable 接口实现2.Comparator 接口实现3.Clonable 接口和深拷贝4.对象比较equals方法5.hashcode方法1.Comparable 接口实现实现该接口时需要加上一个泛型类比 C的模板Class Student implements ComparableStudent,我们比较什么类型就填什么类型在我们了解了 Comparable 接口之后会知道里面存在一个方法compareTo()因此compareTo()需要我们去重写这其实就是一个比较规则需要怎么比较类型可以由我们来完成我们举出一个例子如下classStudentimplementsComparableStudent{publicStringname;publicintage;publicStudent(Stringname,intage){this.namename;this.ageage;}OverridepublicintcompareTo(Studento){//通过年龄比较returnthis.age-o.age;//通过名字比较//return this.name.compareTo(o.name);}}publicclassTest{publicstaticvoidmain(String[]args){Studentstd1newStudent(小李,20);Studentstd2newStudent(小刘,19);System.out.println(std1.compareTo(std2));}}//运行结果为 1有人会好奇为什么用名字比较会这么写return this.name.compareTo(o.name);可以发现String 类型中也有 compareTo()方法因此我们直接调用即可当我们将用到数组对对象进行排序时中间的对比过程正是运用了类似这个步骤classStudent/*implements ComparableStudent*/{publicStringname;publicintage;publicStudent(Stringname,intage){this.namename;this.ageage;}OverridepublicStringtoString(){returnStudent {namename, ageage};}/*Override public int compareTo(Student o) { return this.age - o.age; //return this.name.compareTo(o.name); }*/}publicclassTest{publicstaticvoidmain(String[]args){Studentstd[]newStudent[3];std[0]newStudent(小李,20);std[1]newStudent(小刘,19);std[2]newStudent(小新,18);Arrays.sort(std);System.out.println(Arrays.toString(std));}}我们继续使用上面那个例子将compareTo()方法以及接口屏蔽 会发现运行到Arrays.sort(std);这里的时候发生异常ClassCastException报错信息中 为类型转换异常class demo3.Student cannot be cast to class java.lang.Comparable也就是说 Student 不能被转换为 Comparable 型图片中的意思是这是报错信息中的一个方法Object[] a 是我们传 Student std[] 的形参这是原本正确运行需要进行的但是 我们现在连 Comparable 接口都还没有实现怎么能强转为 Comparable 类型呢更不用说调用 compareTo()方法了因此当我们放开 Comparable 实现和 compareTo()方法重写那么 Arrays.sort()就会按照我们是按名字还是按年龄写的 compareTo()方法去排序。实际上也就是把比较规则放出来别人才能拿来比较那么我们得到的结论就是只要是自定义的类型涉及到了大小比较那就得一定实现 Comparable 接口至于Arrays.sort()内部是怎样实现的我给出一个大概的模拟实现实际内部更复杂类似冒泡排序的排序方法publicstaticvoidmySort(Comparable[]comparables){for(inti0;icomparables.length-1;i){for(intj0;jcomparables.length-1-i;j){if(comparables[j].compareTo(comparables[j1])0){Comparabletmpcomparables[j];comparables[j]comparables[j1];comparables[j1]tmp;}}}}为什么要用Comparable[] comparables作为形参呢因为就是我们前面说的向上转型一样转为Comparable 类型我们就可以直接使用这个接口中的方法。再整体看一下这段代码classStudentimplementsComparableStudent{publicStringname;publicintage;publicStudent(Stringname,intage){this.namename;this.ageage;}OverridepublicStringtoString(){returnStudent {namename, ageage};}OverridepublicintcompareTo(Studento){//方法二将this和o调换位置也可以变为降序returnthis.age-o.age;//return this.name.compareTo(o.name);}}publicclassTest1{publicstaticvoidmySort(Comparable[]comparables){for(inti0;icomparables.length-1;i){for(intj0;jcomparables.length-1-i;j){//方法一这里大于零做的是一个升序的排序改为小于零就是降序了if(comparables[j].compareTo(comparables[j1])0){//swap(comparables, j, j1);Comparabletmpcomparables[j];comparables[j]comparables[j1];comparables[j1]tmp;}}}}// public static T void swap(T[] arr, int i, int j) {// T tmp arr[i];// arr[i] arr[j];// arr[j] tmp;// }publicstaticvoidmain(String[]args){Studentstd[]newStudent[3];std[0]newStudent(小李,20);std[1]newStudent(小刘,19);std[2]newStudent(小新,18);mySort(std);System.out.println(Arrays.toString(std));//System.out.println(std1.compareTo(std2));}}但是这个接口的方法存在一定的局限性就是我有时候想用年龄去排可以但我有时候又想用名字去排但这个方法不能共存只能一个compareTo()方法比较器/也就是比较规则补充建议其实像上面提到的用年龄比较OverridepublicintcompareTo(Studento){//通过年龄比较returnthis.age-o.age;}我们实际上还可以用它类型自带的compare()方法下面我们举一个例子让compareTo()按score 从高到低排序如果分数相同则按年龄从小到大排序。OverridepublicintcompareTo(Studento){intcmpDouble.compare(o.score,this.score);if(cmp0){cmpInteger.compare(this.age,o.age);}returncmp;}这里我们就用到了整型自带的Integer.compare()方法和 double 型自带的Double.compare()方法为什么建议用类型自带的方法呢而不用以下这种OverridepublicintcompareTo(Studento){if(this.scoreo.score){return1;// 分数低的排在后面}elseif(this.scoreo.score){return-1;// 分数高的排在前面}else{returno.age-this.age;// 分数相同时年龄大的排前}}问题compareTo 的返回值逻辑不够直观 那样写更安全避免浮点数误差问题 逻辑清晰可读性高 不会因为比较浮点数出问题2.Comparator 接口实现我们可以创建一个新的类NameComparatorimportjava.util.Comparator;publicclassNameComparatorimplementsComparatorStudent{Overridepublicintcompare(Studento1,Studento2){returno1.name.compareTo(o2.name);}}以及另外一个新的类AgeComparatorimportjava.util.Comparator;publicclassAgeComparatorimplementsComparatorStudent{Overridepublicintcompare(Studento1,Studento2){//按照降序排returno1.age-o2.age;}}这个方法不需要依赖 Student类 去实现任何接口AgeComparatorageComparatornewAgeComparator();System.out.println(ageComparator.compare(std[0],std[1]));直接实例化调用如果需要升序降序我们可以再去分类写几个升序降序的类即可即我们可以同时存在多个类似地比较方法只需要放在不同类去实现接口重写 compare()方法就行3.Clonable 接口和深拷贝当我们先了解到下面要讲的 Object 类之后可以再回头看看Object 类中有一个 clone 方法这个方法的作用就是字面意思克隆一个对象举一个例子publicclassPersonimplementsCloneable{publicintage;OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}OverridepublicStringtoString(){returnPerson{ageage};}}使用步骤创建 Person 类然后实例化一个 person1接着再克隆给对象 person22.重写 Object 父类的 clone() 方法因为该方法在 Object 中是 protected 访问权 [AltIns 键 选择重写克隆 clone()方法即可]4.注意返回值是父类 Object所以我们需要向下转型为 Person5.在 Person类 中实现 Cloneable 接口6.抛去异常处理public static void main(String[] args) throws CloneNotSupportedException补充一下Cloneable 是空接口空接口也叫标记接口表示当前类可以被克隆publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personperson1newPerson();person1.age19;Personperson2(Person)person1.clone();System.out.println(person2.toString());}我们再拓展一下新增一个 Money 类classMoney{publicdoublemoney19.9;}publicclassPersonimplementsCloneable{publicintage;publicStringname;publicPerson(intage,Stringname){this.ageage;this.namename;}MoneymnewMoney();OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}OverridepublicStringtoString(){returnPerson{ageage, namename\, moneym.money};}}此时在栈和堆中是这样的引用同一个 m 对象我们因此称之为浅拷贝如果 m 对象也被拷贝了那就是深拷贝所以我们就进行如下操作变为深拷贝吧先让 Money 能够被克隆classMoneyimplementsCloneable{publicdoublemoney19.9;OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}}然后再修改一下克隆方法protectedObjectclone()throwsCloneNotSupportedException{Persontmp(Person)super.clone();tmp.m(Money)m.clone();returntmp;}我们再进行测试一下即可publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personperson1newPerson(19,小李);person1.age19;Personperson2(Person)person1.clone();System.out.println(修改前person1.toString());System.out.println(修改前person2.toString());person2.m.money100;System.out.println(修改后person1.toString());System.out.println(修改后person2.toString());}返回的结果修改前Person{age19, name‘小李’, money19.9}修改前Person{age19, name‘小李’, money19.9}修改后Person{age19, name‘小李’, money19.9}修改后Person{age19, name‘小李’, money100.0}此时的 Person2 的 m 对象是另外一个独立的对象与 Person 引用的 m 不是同一个4.对象比较equals方法在Java中进行比较时a.如果左右两侧是基本类型变量比较的是变量中值是否相同b.如果左右两侧是引用类型变量比较的是引用变量地址是否相同c.如果要比较对象中内容必须重写Object中的equals方法因为equals方法默认也是按照地址比较的// Object类中的equals方法publicbooleanequals(Objectobj){return(thisobj);// 使用引用中的地址直接来进行比较}我们自己写一个比较引用类型中的基本类型比较(eg:Person类中的name和age)publicbooleanequals(Objectobj){if(objnull){returnfalse;}if(thisobj){returntrue;}// 不是Person类对象if(!(objinstanceofPerson)){returnfalse;}Personperson(Person)obj;// 向下转型比较属性值returnthis.name.equals(person.name)this.ageperson.age;}结论比较对象中内容是否相等的时候一定要重写equals方法。5.hashcode方法我们回忆一下Object中的toString方法// Object类中的toString()方法实现publicStringtoString(){returngetClass().getName()Integer.toHexString(hashCode());}我们看到了hashCode()这个方法他帮我算了一个具体的对象位置这里面涉及数据结构但是我们还没学数据结构没法讲述所以我们只能说它是个内存地址。然后调用Integer.toHexString()方法将这个地址以16进制输出。hashcode方法源码publicnativeinthashCode();该方法是一个native方法底层是由C/C编写无法观察到我们认为两个名字相同年龄相同的对象将存储在同一个位置如果不重写hashcode()方法我们可以来看示例代码classPerson{publicStringname;publicintage;publicPerson(Stringname,intage){this.namename;this.ageage;}}publicclassTestDemo4{publicstaticvoidmain(String[]args){Personper1newPerson(Sirens,20);Personper2newPerson(Sirens,20);System.out.println(per1.hashCode());System.out.println(per2.hashCode());}}//执行结果4601419581163157884注意事项两个对象的hash值不一样。像重写equals方法一样我们也可以重写hashcode()方法。此时我们再来看看。classPerson{publicStringname;publicintage;publicPerson(Stringname,intage){this.namename;this.ageage;}OverridepublicinthashCode(){returnObjects.hash(name,age);}}publicclassTestDemo4{publicstaticvoidmain(String[]args){Personper1newPerson(Sirens,20);Personper2newPerson(Sirens,20);System.out.println(per1.hashCode());System.out.println(per2.hashCode());}}//执行结果460141958460141958注意事项哈希值一样。结论1、hashcode方法用来确定对象在内存中存储的位置是否相同2、事实上hashCode() 在散列表中才有用在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码进而确定该对象在散列表中的位置。以上是我关于Java的笔记分享感谢你读到这里这也是我学习路上的一个小小记录。希望以后回头看时能看到自己的成长~
RELATED

相关推荐

内部开发者工具箱发布(一):统一脚手架的设计理念与架构演进

内部开发者工具箱发布(一):统一脚手架的设计理念与架构演进

内部开发者工具箱发布(一):统一脚手架的设计理念与架构演进在百人以上规模的技术团队中,研发痛点往往不是缺少工具,而是“工具林立且割裂”:后端有一套基于 Shell 的部署脚本,前端维护着一套 No…

📅 2026/9/27 8:39:28
定制 LangChain 嵌入模型包装器:生产级落地全景总结

定制 LangChain 嵌入模型包装器:生产级落地全景总结

定制 LangChain 嵌入模型包装器:生产级落地全景总结在大语言模型(LLM)与 RAG(检索增强生成)系统的数据入库与实时问答链路中,Embeddings(向量嵌入模型组件) 是承载全系统数据流转最频…

📅 2026/9/27 8:39:28
cryptography 常见问题完全指南:从安装排错到 PEM 解析的实战 FAQ

cryptography 常见问题完全指南:从安装排错到 PEM 解析的实战 FAQ

密码学 【免费下载链接】cryptography cryptography is a package designed to expose cryptographic primitives and recipes to Python developers. 项目地址: https://gitcode.com/gh_mirrors/cr/cryptography 点击查看 免费下载 cryptography 是 Python 生态中…

📅 2026/9/27 8:39:28
MORE NEWS

更多资讯

📰

LinuxKit 中的 GCE 元数据服务 Go 工具库:`cloud.google.com/go/compute/metadata` 使用与源码剖析

操作系统云原生容器运行时 【免费下载链接】linuxkit A toolkit for building secure, portable and lean operating systems for containers 项目地址: https://gitcode.com/gh_mirrors/li/linuxkit 点击查看 免费下载 本篇技术指南围绕 LinuxKit 仓库内 vendored…

📰

Puppet V3 Facts HTTP API 详解:节点事实上报、Schema 约束与间接层实现原理

运维DevOpsIaC 【免费下载链接】puppet Server automation framework and application 项目地址: https://gitcode.com/gh_mirrors/pu/puppet 点击查看 免费下载 导读 facts 端点是 Puppet V3 HTTP API 中用于**按节点名写入(保存)事实&…

📰

NodeMCU WS2801 驱动模块完全指南:从接线到源码级原理

物联网嵌入式 【免费下载链接】nodemcu-firmware Lua based interactive firmware for ESP8266, ESP8285 and ESP32 项目地址: https://gitcode.com/gh_mirrors/no/nodemcu-firmware 点击查看 免费下载 本文基于 docs/modules/ws2801.md 整理,并结合 ap…

📰

Humanizer InDate.Six 详解:用流式 API 计算 6 天/6 周/6 月/6 年后的日期

开发工具 【免费下载链接】Humanizer Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities 项目地址: https://gitcode.com/gh_mirrors/hu/Humanizer 点击查看 免费下载 本篇技…

📰

不用再交 Wand 专业版年费:用 Wand-Enhancer 一次本地补丁解锁 Pro 功能

不用再交 Wand 专业版年费:用 Wand-Enhancer 一次本地补丁解锁 Pro 功能 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 如果 Wand 专业…

📰

正则如何驱动索引查询?深入剖析tgrep的QueryPlan分解与Bloom过滤技巧

正则如何驱动索引查询?深入剖析tgrep的QueryPlan分解与Bloom过滤技巧 【免费下载链接】tgrep Trigram-indexed grep with a client/server architecture for fast regex search in large codebases locally 项目地址: https://gitcode.com/gh_mirrors/tg/tgrep …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬