JavaComparable、Comparator、Cloneable接口解析,深拷贝浅拷贝详细解析

📅 2026/8/13 9:23:38
JavaComparable、Comparator、Cloneable接口解析,深拷贝浅拷贝详细解析
前言❤️❤️hello hello这里是洋不写bug~欢迎大家点赞关注收藏Comparable、Comparator、Cloneable 都是 Java 当中十分高频的内置接口比较接口用于实现对象之间的大小对比Cloneable 用来完成对象拷贝这些知识点建立在接口、继承的基础知识之上。熟练掌握之后可以灵活实现自定义排序、对象复制方便我们日常开发。本篇博文就从基础部分解析这几个知识点一些前置知识点在之前接口和抽象类当中有解析大家可以在专栏中跳转往期文章复习一下个人主页洋不写bug的博客所属专栏Java学习之旅从入门到进阶铁汁们对于Java的各种常用核心语法不太常用的也有都可以在上面的Java专栏学习专栏正在持续更新中有问题可以写在评论区或者私信我哦~1Comparable接口写一个学生类在main方法中创建两个学生对象如果想要比较这两个学生对象那肯定不能直接比较需要建立一套比较规则packagepackage7;publicclassStudent{publicintid;publicStringname;publicdoublescore;publicStudent(intid,doublescore,Stringname){this.idid;this.scorescore;this.namename;}}假如规则是先按照成绩比较成绩相同的再去比较学号要实现这个规则Student类就要实现Java官方的Comparable接口要在中指定当前要排序的实体类重写compareTo方法publicclassStudentimplementsComparableStudent{publicintid;publicStringname;publicdoublescore;publicStudent(intid,doublescore,Stringname){this.idid;this.scorescore;this.namename;}OverridepublicintcompareTo(Studento){return0;}}在compareTo方法中制定规则时this表示当前对象o就表示另一个要比较的对象o表示other那这里我们就制订这样的比较规则如果this比o大就返回0的整数这里我们返回-1如果this比o小就返回0的正数这里我们返回1如果this与o相等就返回0这里先比较成绩如果成绩相同再比较id只有成绩和id都相等才返回0理论上学生的id不会相等所以是不会返回0的重写方法如下所示OverridepublicintcompareTo(Studento){if(this.scoreo.score){return-1;}elseif(this.scoreo.score){return1;}else{if(this.ido.id){return-1;}elseif(this.ido.id){return1;}}return0;}在main方法中测试一下Mike的分数比Will低比较出的结果就是-1(这里铁汁们也可以多创建几组对象比较一下试一试看是否符合约定的规则)packagepackage7;publicclassTest{publicstaticvoidmain(String[]args){Students1newStudent(1,90,Mike);Students2newStudent(2,98,Will);System.out.println(s1.compareTo(s2));}}在C中运算符是可以重载的就是让运算符可以用来比较我们自己创建的类对象但是这个重载语法特性比较容易被滥用因此Java中是不支持的运算符重载就是像下面这样进行比较我们自己定义一个接口用来进行比较也不是不行但是最好还是用这些官方标准库提供的接口这样有一个很大的好处就是可以无缝的和标准库其他类对接在标准库中的很多类会用到Comparable类中的compareTo方法例如数组类的sort方法Java标准库提供了两个进行比较的接口一个是Comparable一个是Comparator2Comparator接口前面的Comparable接口是直接让被比较的类Student类实现接口重写方法而Comparator类则是需要创建一个单独的类来实现这个Comparator接口在新的类中访问被比较类的属性需要get方法因此被比较类中也必须写上get方法这里单独创建的类命名为StudentComparator实现Comparator接口重写compare方法这里因为不是在被比较类内部写的了参数就不是this和other了而是o1和o2代码如下所示packagepackage7;publicclassStudent{publicintid;publicStringname;publicdoublescore;publicStudent(intid,doublescore,Stringname){this.idid;this.scorescore;this.namename;}publicintgetId(){returnid;}publicStringgetName(){returnname;}publicdoublegetScore(){returnscore;}}packagepackage7;importjava.util.Comparator;publicclassStudentComparatorimplementsComparatorStudent{Overridepublicintcompare(Studento1,Studento2){if(o1.getScore()o2.getScore()){return-1;}elseif(o1.getScore()o2.getScore()){return1;}else{if(o1.getId()o2.getId()){return-1;}elseif(o1.getId()o2.getId()){return1;}}return0;}}这里有个小技巧写完后直接按alt enter键再按enter就会自动生成重写方法生成的方法的参数不是o1和o2我们可以改一下使用Comparator接口在main方法中进行比较时肯定就不能直接用对象调用比较方法了因为比较方法并没有写到被比较类中就需要先创建一个比较器对象再调用这个比较器对象的方法去进行判断packagepackage7;publicclassTest{publicstaticvoidmain(String[]args){Students1newStudent(1,90,Mike);Students2newStudent(2,98,Will);StudentComparatorcomparatornewStudentComparator();System.out.println(comparator.compare(s1,s2));}}那在什么情况下用Comparable什么情况下用Comparator呢如下如果比较规则只有一种并且可以随意修改被比较的类的代码时优先考虑Comparable使用Comparable的话比较规则的代码就包含在类的内部内聚性更好如果比较规则有多种例如发奖学金用成绩比较挑选礼仪队成员按身高比较或者被比较的类的源码不能修改也就是别人写了一个类让你来使用这个类你是不能随意改动的甚至你看不到这个类3Clonable接口当我们需要克隆一个对象时应该怎么办下面这种写法完成克隆了吗publicclassTest{publicstaticvoidmain(String[]args){Students1newStudent(1,90,Mike);Students2s1;}}这个其实在前面类和对象的博客中有提到过如下图并不算是真正的克隆只是搞了个引用指向堆空间中相同的位置真正的克隆起码应该两个引用修改自己的属性不会相互影响也就是克隆时在堆空间上再创建一片区域要完成真正的克隆就需要Student类继承Clonable接口这个接口也是Java标准库中的重写clone方法继承这个接口后就算没有重写clone方法编译器这时候也不会报错这是为什么呢按住ctrl键再点击Cloneable看一下源码就会很奇怪的发现这竟然是一个空接口那既然是空接口需要重写的clone方法是从哪来的呢其实这个clone方法不是Cloneable接口提供的而是Object类提供的Object类是Java中所有类直接或者间接的父类因此无论是否实现Cloneable接口我们都可以重写clone方法那既然clone接口是个空接口我们就先不实现这个接口看下最后是否能正常完成克隆在IDEA中按alt insert键选择Override Methods按enter键接着就会发现其中有个clone方法这个克隆方法来自Object类接下来生成的方法就是这样的这里抛出了个异常如果还没学习异常的铁汁们可以先不用管后面博客会进行解析这里使用的是父类Object类的clone方法所以方法前面加上super可能有的铁汁就会说了这里没有继承Cloneable接口也能重写而且Cloneable又是一个空的接口那到底有啥用呢这里我们先不实现Cloneable接口看能不能正常运行这里我们稍微改下默认生成的clone方法改成public返回Student类型这个clone方法就重写好了OverridepublicStudentclone()throwsCloneNotSupportedException{return(Student)super.clone();}但是在main方法中想要使用需要抛出异常后面博客会解析还没学过的铁汁可以先忽略这里验证下看s1和s2对象是不是不同的引用指向同一块空间了有没有真的克隆成功packagepackage7;publicclassTest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Students1newStudent(1,90,Mike);Students2s1.clone();System.out.println(s2.getName());System.out.println(s2.getId());System.out.println(s2.getScore());s1.setScore(100);System.out.println(s2.getScore());}}运行完却报错了就是因为刚刚Student类没有实现Cloneable接口Cloneable接口具有标记作用也就是标记这个类可以进行克隆拷贝运行时JVM会检查有无标记接口Cloneable如果被比较类没有实现这个接口那JVM就会报错这里我们让Student类实现Cloneable接口后再执行程序就不会报错了4浅拷贝和深拷贝当类中的属性都是内置类型基础数据类型例如int,long,float时就不涉及深浅拷贝的问题如果涉及到引用类型那就要区分深浅拷贝了除去基础内置数据类型之外剩下所有可以 new 出来的全部都是引用类型示例如下Money类类中的属性为amountPerson类里面的属性有id也有Money对象的引用这里注意getMoney和setMoney方法实际上读取和改变的是amount的值想要对Person类进行克隆就要在Person类中实现一下Cloneable接口再重写下clone方法publicclassMoney{privatedoubleamount1000;publicdoublegetAmount(){returnamount;}publicvoidsetAmount(doubleamount){this.amountamount;}}packagepackage8;publicclassPersonimplementsCloneable{privateintid;privateMoneymoneynewMoney();OverridepublicPersonclone()throwsCloneNotSupportedException{return(Person)super.clone();}publicintgetId(){returnid;}publicvoidsetId(intid){this.idid;}publicdoublegetMoney(){returnmoney.getAmount();}publicvoidsetMoney(doubleamount){money.setAmount(amount);}}接着在main方法中测试一下看拷贝的效果如何packagepackage8;publicclassTest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personp1newPerson();p1.setId(1);Personp2p1.clone();System.out.println(p2.getId());System.out.println(p2.getMoney());p1.setId(3);System.out.println(p2.getId());p1.setMoney(2000);System.out.println(p1.getMoney());System.out.println(p2.getMoney());}}结果如下就出现问题了p1修改id对p2没有影响但p1修改money的account的值p2就受到影响了也就是说这里的拷贝没有进行引用类型真正的拷贝p1和p2还是共用了一份引用内存这种拷贝就称为浅拷贝浅拷贝内存图如下虽然p1和p2指向的是两个不同的对象但是p1.money和p2.money仍然是同一个对象深拷贝就是不光把Person对象本身进行拷贝还要把Person内部持有的引用类型的属性也进行拷贝引用类型中如果还有引用类型就要递归的进行拷贝保证所有内部持有的引用类型都对应拷贝一份新的Money对象也要进行拷贝那Money类就需要实现Cloneable接口重写clone方法Person类里面重写的clone方法要加上对Money对象的拷贝代码如下packagepackage8;publicclassMoneyimplementsCloneable{privatedoubleamount1000;publicdoublegetAmount(){returnamount;}publicvoidsetAmount(doubleamount){this.amountamount;}OverridepublicMoneyclone()throwsCloneNotSupportedException{return(Money)super.clone();}}packagepackage8;publicclassPersonimplementsCloneable{privateintid;privateMoneymoneynewMoney();OverridepublicPersonclone()throwsCloneNotSupportedException{Personp(Person)super.clone();p.moneyp.money.clone();returnp;}publicintgetId(){returnid;}publicvoidsetId(intid){this.idid;}publicdoublegetMoney(){returnmoney.getAmount();}publicvoidsetMoney(doubleamount){money.setAmount(amount);}}3再执行下main方法这时候更改Money对象的amount值就不会相互影响了总结到这里接口和抽象类的基础知识已经基本上解析完了总结图如下所示到这里JavaSE的内容的基础语法部分大部分已经解析完了下篇博客会解析下Java内部类和Object类学完后就可以写基础的项目了下下篇博客会详细解析一个基础的图书管理系统初学Java的铁汁可以拿来练手以上就是今天的所有内容啦完结撒花