⭐️前面的话⭐️
本篇文章带大家认识Java常用的接口——Comparable,Comparator,Cloneable接口,其中Comparable,Comparator接口是比较器,可以用来对对象进行排序,Cloneable接口是克隆接口,用来生成一个对象的副本。
📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2022年1月2日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《Java核心技术》,📚《Java编程思想》,📚《Effective Java》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
📌导航小助手📌
1.Comparable接口
1.1引子:数组排序
我们知道
Arrays
类中的·
sort
方法可以对数组进行排序,比如对整型数组进行排序:
importjava.util.Arrays;publicclassTest{publicstaticvoidmain(String[] args){int[] arr ={21,3,6,1,42,13,18,85,68};Arrays.sort(arr);System.out.println(Arrays.toString(arr));}}
显然我们会得到一个从小到大排好序的数组,但是实际中往往需要对自定义类型的数组进行排序,比如我定义一个
Person
类:
classPerson{publicint age;publicString name;publicint score;publicPerson(int age,String name,int score){this.age = age;this.name = name;this.score = score;}@OverridepublicStringtoString(){return"Person{"+"age="+ age +", name='"+ name +'\''+", score="+ score +'}';}}
我们用同样的方法来排序试一试:
报错了,因为编译器不知道你需要根据对象中哪个元素来进行排序,这时候就需要用到
Comparable
这个接口来实现对自定义类型的排序,其实有一点像C语言中的qsort函数的用法。
1.2Camparable接口的使用
首先我们来看看调用sort方法时发生异常处的源码:
我们发现
Person
进行排序时需要强转
Comparable
并且调用
compareTo
方法,这就意味着我们需要对
Person
类实现
Comparable
接口,并重写该接口中的
compareTo
方法。
publicinterfaceComparable<T>{publicintcompareTo(T o);}
我们发现
Comparable
接口中只有一个方法
compareTo
方法,还有在接口后面多了一个
<T>
,这个是泛型的意思,由于泛型概念及其地抽象,晦涩难懂,这里知道他是泛型即可,它相当于方法的参数一样,可以理解为对一个类型进行传参,在实现
Comparable
接口时,把类型当做方法参数一样使用
<>
传入就可以了,像这样:
classPersonimplementsComparable<Person>{publicint age;publicString name;publicint score;publicPerson(int age,String name,int score){this.age = age;this.name = name;this.score = score;}@OverridepublicStringtoString(){return"Person{"+"age="+ age +", name='"+ name +'\''+", score="+ score +'}';}@OverridepublicintcompareTo(Person o){returnthis.age - o.age;}}
泛型会在后续博文中由浅到深多次介绍,这里知道简单地使用即可。
对于其中的
compareTo
方法,谁调用它谁就是
this
,如果返回的值大于0,表示
this
大于
o
,等于0,两者相等,小于0表示
this
小于
o
。如果需要降序排列,将
this
与
o
的位置换一换即可。
@Override//升序publicintcompareTo(Person o){returnthis.age - o.age;}@Override//降序publicintcompareTo(Person o){returnthis.age - o.age;}
如果要进行字符串或者其他类的比较,需要调用类中所提供的
compareTo
方法进行比较,比如字符串:
@OverridepublicintcompareTo(Person o){returnthis.name.compareTo(o.name);}
同理,降序将
this
与
o
反过来。
实现了
Comparable
接口,我们再来尝试一下能不能排序成功。
根据年龄
age
进行排序:
importjava.util.Arrays;classPersonimplementsComparable<Person>{publicint age;publicString name;publicint score;publicPerson(int age,String name,int score){this.age = age;this.name = name;this.score = score;}@OverridepublicStringtoString(){return"Person{"+"age="+ age +", name='"+ name +'\''+", score="+ score +'}';}@OverridepublicintcompareTo(Person o){returnthis.age - o.age;}}publicclassTest1{publicstaticvoidmain(String[] args){Person[] people =newPerson[3];
people[0]=newPerson(32,"001",92);
people[1]=newPerson(18,"002",78);
people[2]=newPerson(12,"003",90);Arrays.sort(people);System.out.println(Arrays.toString(people));}}
再来试一试姓名
name
排序:
importjava.util.Arrays;classPersonimplementsComparable<Person>{publicint age;publicString name;publicint score;publicPerson(int age,String name,int score){this.age = age;this.name = name;this.score = score;}@OverridepublicStringtoString(){return"Person{"+"age="+ age +", name='"+ name +'\''+", score="+ score +'}';}@OverridepublicintcompareTo(Person o){returnthis.name.compareTo(o.name);}}publicclassTest1{publicstaticvoidmain(String[] args){Person[] people =newPerson[3];
people[0]=newPerson(32,"zhang",92);
people[1]=newPerson(18,"wang",78);
people[2]=newPerson(12,"chen",90);Arrays.sort(people);System.out.println(Arrays.toString(people));}}
好了,上面这些就是
Comparable
接口的用法,可见该接口有个很大的缺点,那就是对类的侵入性极强,不小心改了
compareTo
方法后,程序出现bug还不报错,很难排查,因此实际当中常常使用比较器
Comparator
,下面就来聊一聊
Comparator
。
2.Comparator比较器
在
Arrays
类中实现了许多的
sort
方法能够对不同的数据类型进行比较,使用
Comparator
接口就能实现这种形式,你需要哪一个就调用哪一个比较器。
publicinterfaceComparator<T>{intcompare(T o1,T o2);...}
只要重写该
compare
方法就可以实现数据类型大小的比较。
classAgeComparatorimplementsComparator<Person>{@Overridepublicintcompare(Person o1,Person o2){return o1.age - o2.age;}}classNameComparatorimplementsComparator<Person>{@Overridepublicintcompare(Person o1,Person o2){return o1.name.compareTo(o2.name);}}classScoreComparatorimplementsComparator<Person>{@Overridepublicintcompare(Person o1,Person o2){return o1.score - o2.score;}}
如果需要降序排列,同样的道理,将
o1
与
o2
交换一下位置就好了。那怎么用呢?很简单,实例化比较器对象后,在
sort
方法中多加一个比较器对象就可以了,因为在源码中有这样一个
sort
的重载方法:
publicstatic<T>voidsort(T[] a,Comparator<?superT> c){if(c ==null){sort(a);}else{if(LegacyMergeSort.userRequested)legacyMergeSort(a, c);elseTimSort.sort(a,0, a.length, c,null,0,0);}}
不用看懂该方法的实现方式,只需要知道该方法的第二个参数是比较器就可以了,下面就来实践看看。
publicclassTest{publicstaticvoidmain(String[] args){Person[] people =newPerson[3];
people[0]=newPerson(32,"wang",92);
people[1]=newPerson(18,"zhang",78);
people[2]=newPerson(12,"chen",90);AgeComparator ageCmp =newAgeComparator();NameComparator nameCmp =newNameComparator();ScoreComparator scoreCmp =newScoreComparator();Arrays.sort(people, ageCmp);System.out.println("按年龄排序:");System.out.println(Arrays.toString(people));Arrays.sort(people, nameCmp);System.out.println("按姓名排序:");System.out.println(Arrays.toString(people));Arrays.sort(people, scoreCmp);System.out.println("按分数排序:");System.out.println(Arrays.toString(people));}}
没有问题,成功排序了,
Comparator
比起
Comparable
,对类的侵入性就非常的小了,而且更加的直观。这两个接口是Java当中很常见的比较器工具,在后续数据结构的实现肯定会再次见到他们。最后再来说一说对象的拷贝吧,
Cloneable
接口是实现拷贝最常用的工具。
3.Cloneable克隆接口
3.1Cloneable的使用
首先,该接口的作用能够帮助生成一个对象的副本,也就是拷贝一个对象,其实其拷贝作用的是
clone
方法,该接口的作用其实是标志一个对象能够被克隆(拷贝),为什么这么说呢?我们去看看这个接口的源码你就知道了,嘻嘻!
publicinterfaceCloneable{}
我的天啊,它竟然是一个空接口,面试的时候常常会问空接口的作用是什么?空接口的作用是起到一个标志的作用,比如这个
Cloneable
接口,它是一个空接口,对象实现该接口,这个对象(类)就被标记了,表示该对象(类)能够被克隆。
我们再来看一看真正起作用的
clone
方法:
protectednativeObjectclone()throwsCloneNotSupportedException;
该方法被关键字
native
修饰,说明它是使用C++代码实现的,并且使用它的时候需要接收异常。
按理说,既然
Cloneable
接口是一个空接口,实现该接口时不需要重写
clone
方法,在这里,有点特殊,虽然该接口是空的,也需要实现
clone
方法,实现方式很简单,调用
Object
(该类是所有类的父类)类中的
clone
方法即可。
classPersonimplementsCloneable{int age;publicPerson(int age){this.age = age;}@OverridepublicStringtoString(){return"Person{"+"age="+ age +'}';}@OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}}publicclassTest{publicstaticvoidmain(String[] args){Person p1 =newPerson(18);try{Person p2 =(Person) p1.clone();System.out.println(p1);System.out.println(p2);}catch(CloneNotSupportedException e){
e.printStackTrace();System.out.println("克隆异常!");}}}
拷贝成功了,上面这些就是
Cloneable
接口的使用方法。
3.2深拷贝与浅拷贝
深拷贝:当拷贝一个对象时,会新生成一个一模一样的对象,并且如果对象里面还有其他对象,也会新生成一个一模一样的对象,这就是深拷贝。
浅拷贝:当拷贝一个对象时,会新生成一个一模一样的对象,但是如果该对象里面还有其他对象,只会拷贝这个对象的地址,并不会新生成一个对象。
首先深浅拷贝是对代码实现方面来说的,方法的拷贝并没有深浅拷贝这一个说法,如果硬是要有个说法,Java中给出的拷贝方法都是浅拷贝,虽然对于简单数据类型拷贝可以说是深拷贝,是因为拷贝简单数据类型并不能体现出来它到底是浅拷贝还是深拷贝,当去拷贝引用类型(比如数组,字符串)的时候,只是拷贝了对象的地址,并没有重新生成一个对象,所以说单论方法的拷贝,可以理解为浅拷贝。
所以说,对于一个拷贝到底是深拷贝还是浅拷贝,在于程序员对代码的实现,到底是一个深拷贝还是浅拷贝。
这个深浅拷贝就简单说一下吧,就不举例子了,如果有不懂的欢迎与博主交流~~。
⭐️后面的话⭐️
博主正在参加2021【博客之星】,小伙伴们能否给博主一个五星呢?投票地址:
https://bbs.csdn.net/topics/603955252
投我以桃,报之以李,对于每一个评价,小博主都定当相报!
最后,祝各位小伙伴新年快乐,心想事成,没有烦恼!
下一篇文章是有关集合框架的哦!我们要开始数据结构之旅了!
觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!
版权归原作者 未见花闻 所有, 如有侵权,请联系我们删除。