Java中常用的比较器和克隆接口
⭐️前面的话⭐️
本篇文章带大家认识Java常用的接口——Comparable,Comparator,Cloneable接口,其中Comparable,Comparator接口是比较器,可以用来对对象进行排序,Cloneable接口是克隆接口,用来生成一个对象的副本。
📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创!
📆华为云首发时间:🌴2022年5月31日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《Java核心技术卷1》,📚《数据结构》,📚《Java编程思想》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
1.Comparable接口
1.1引子:数组排序
我们知道Arrays
类中的·sort
方法可以对数组进行排序,比如对整型数组进行排序:
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr = {21,3,6,1,42,13,18,85,68};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
显然我们会得到一个从小到大排好序的数组,但是实际中往往需要对自定义类型的数组进行排序,比如我定义一个Person
类:
class Person {
public int age;
public String name;
public int score;
public Person(int age, String name,int score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
我们用同样的方法来排序试一试:
报错了,因为编译器不知道你需要根据对象中哪个元素来进行排序,这时候就需要用到Comparable
这个接口来实现对自定义类型的排序,其实有一点像C语言中的qsort函数的用法。
1.2Camparable接口的使用
首先我们来看看调用sort方法时发生异常处的源码:
我们发现Person
进行排序时需要强转Comparable
并且调用compareTo
方法,这就意味着我们需要对Person
类实现Comparable
接口,并重写该接口中的compareTo
方法。
public interface Comparable<T> {
public int compareTo(T o);
}
我们发现Comparable
接口中只有一个方法compareTo
方法,还有在接口后面多了一个<T>
,这个是泛型的意思,由于泛型概念及其地抽象,晦涩难懂,这里知道他是泛型即可,它相当于方法的参数一样,可以理解为对一个类型进行传参,在实现Comparable
接口时,把类型当做方法参数一样使用<>
传入就可以了,像这样:
class Person implements Comparable<Person>{
public int age;
public String name;
public int score;
public Person(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
}
泛型会在后续博文中由浅到深多次介绍,这里知道简单地使用即可。
对于其中的compareTo
方法,谁调用它谁就是this
,如果返回的值大于0,表示this
大于o
,等于0,两者相等,小于0表示this
小于o
。如果需要降序排列,将this
与o
的位置换一换即可。
@Override //升序
public int compareTo(Person o) {
return this.age - o.age;
}
@Override //降序
public int compareTo(Person o) {
return this.age - o.age;
}
如果要进行字符串或者其他类的比较,需要调用类中所提供的compareTo
方法进行比较,比如字符串:
@Override
public int compareTo(Person o) {
return this.name.compareTo(o.name);
}
同理,降序将this
与o
反过来。
实现了Comparable
接口,我们再来尝试一下能不能排序成功。
根据年龄age
进行排序:
import java.util.Arrays;
class Person implements Comparable<Person>{
public int age;
public String name;
public int score;
public Person(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
}
public class Test1 {
public static void main(String[] args) {
Person[] people = new Person[3];
people[0] = new Person(32, "001", 92);
people[1] = new Person(18, "002", 78);
people[2] = new Person(12, "003", 90);
Arrays.sort(people);
System.out.println(Arrays.toString(people));
}
}
再来试一试姓名name
排序:
import java.util.Arrays;
class Person implements Comparable<Person>{
public int age;
public String name;
public int score;
public Person(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Person o) {
return this.name.compareTo(o.name);
}
}
public class Test1 {
public static void main(String[] args) {
Person[] people = new Person[3];
people[0] = new Person(32, "zhang", 92);
people[1] = new Person(18, "wang", 78);
people[2] = new Person(12, "chen", 90);
Arrays.sort(people);
System.out.println(Arrays.toString(people));
}
}
好了,上面这些就是Comparable
接口的用法,可见该接口有个很大的缺点,那就是对类的侵入性极强,不小心改了compareTo
方法后,程序出现bug还不报错,很难排查,因此实际当中常常使用比较器Comparator
,下面就来聊一聊Comparator
。
2.Comparator比较器
在Arrays
类中实现了许多的sort
方法能够对不同的数据类型进行比较,使用Comparator
接口就能实现这种形式,你需要哪一个就调用哪一个比较器。
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
只要重写该compare
方法就可以实现数据类型大小的比较。
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
}
class NameComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.name.compareTo(o2.name);
}
}
class ScoreComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.score - o2.score;
}
}
如果需要降序排列,同样的道理,将o1
与o2
交换一下位置就好了。那怎么用呢?很简单,实例化比较器对象后,在sort
方法中多加一个比较器对象就可以了,因为在源码中有这样一个sort
的重载方法:
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
不用看懂该方法的实现方式,只需要知道该方法的第二个参数是比较器就可以了,下面就来实践看看。
public class Test {
public static void main(String[] args) {
Person[] people = new Person[3];
people[0] = new Person(32, "wang", 92);
people[1] = new Person(18, "zhang", 78);
people[2] = new Person(12, "chen", 90);
AgeComparator ageCmp = new AgeComparator();
NameComparator nameCmp = new NameComparator();
ScoreComparator scoreCmp = new ScoreComparator();
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
方法,该接口的作用其实是标志一个对象能够被克隆(拷贝),为什么这么说呢?我们去看看这个接口的源码你就知道了,嘻嘻!
public interface Cloneable {
}
我的天啊,它竟然是一个空接口,面试的时候常常会问空接口的作用是什么?空接口的作用是起到一个标志的作用,比如这个Cloneable
接口,它是一个空接口,对象实现该接口,这个对象(类)就被标记了,表示该对象(类)能够被克隆。
我们再来看一看真正起作用的clone
方法:
protected native Object clone() throws CloneNotSupportedException;
该方法被关键字native
修饰,说明它是使用C++代码实现的,并且使用它的时候需要接收异常。
按理说,既然Cloneable
接口是一个空接口,实现该接口时不需要重写clone
方法,在这里,有点特殊,虽然该接口是空的,也需要实现clone
方法,实现方式很简单,调用Object
(该类是所有类的父类)类中的clone
方法即可。
class Person implements Cloneable {
int age;
public Person(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person(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中给出的拷贝方法都是浅拷贝,虽然对于简单数据类型拷贝可以说是深拷贝,是因为拷贝简单数据类型并不能体现出来它到底是浅拷贝还是深拷贝,当去拷贝引用类型(比如数组,字符串)的时候,只是拷贝了对象的地址,并没有重新生成一个对象,所以说单论方法的拷贝,可以理解为浅拷贝。
所以说,对于一个拷贝到底是深拷贝还是浅拷贝,在于程序员对代码的实现,到底是一个深拷贝还是浅拷贝。
- 点赞
- 收藏
- 关注作者
评论(0)