Comparable和Cloneable接口
2019-09-24 17:37 老九君 阅读(563) 评论(0) 收藏 举报Comparable
Java中所有的compareTo方法都源于一个接口,那就是Comparable。这个接口只有一个方法,那就是CompareTo。想要具有比较功能的类,都建议实现这个接口,而非是自己定义这个功能,这是面向对象的概念。
API中对Comparable定义:
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
对于类 C(Collections) 的每一个 e1 (Element)和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2)具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使e.equals(null) 返回 false,e.compareTo(null) 也将抛出 NullPointerException。
实际上,所有实现 Comparable 的 Java 核心类都具有与 equals 一致的自然排序。java.math.BigDecimal 是个例外,它的自然排序将值相等但精确度不同的 BigDecimal 对象(比如 4.0 和 4.00)视为相等。
强烈建议(尽管不要求)comparaTo应该应该与equals保持一致。也就是说,对于两个对象o1和o2,应该确保当且仅当o1.equals(o2)为true时,o1.compareTo(o2) == 0成立。
接口定义如下:

CompareTo方法判断这个对象相对于给定对象o的顺序,并且当这个对象小于、等于或大于给定对象o时,分别返回负整数、0或正整数。
Comparable接口是一个泛型接口。在实现该接口时,泛型类型E被替换成一种具体的类型。Java类库中的许多类实现了Comparable接口以定义对象的自然顺序。Byte、Short、Integer、Long、Float、Double、Chracter、BigInteger、BigDecimal、Calendar、String以及Date类都实现了Comparable接口。
因此,数字是可比较的,字符串是可比较的,日期也是如此。可以使用comparaTo方法来比较两个数组、两个字符串以及两个日期等。例如:


上面我们已经说了Comparable接口类型的实例的话,JAVA API中的Arrays.sort方法就可以使用compareTo方法来对数组的对象进行比较和排序。例如:


再举例,定义一个新的Rectangle(矩形)类来实现Comparable,这个新类就是可以比较的。



Coneable
cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。
Object中clone方法:

native修饰的方法都是空的方法,但是这些方法都是有实现体的(这里也就间接说明了native关键字不能与abstract同时使用。因为abstract修饰的方法与java的接口中的方法类似,他显式的说明了修饰的方法,在当前是没有实现体的,abstract的方法的实现体都由子类重写),只不过native方法调用的实现体,都是非java代码编写的(例如:调用的是在jvm中编写的C的接口),每一个native方法在jvm中都有一个同名的实现体,native方法在逻辑上的判断都是由实现体实现的,另外这种native修饰的方法对返回类型,异常控制等都没有约束。
由此可见,这里判断是否实现cloneable接口,是在调用jvm中的实现体时进行判断的。
深度克隆与浅度克隆
首先,在java中创建对象的方式有四种:
1.是new,通过new关键字在堆中为对象开辟空间,在执行new时,首先会看所要创建的对象的类型,知道了类型,才能知道需要给这个对象分配多大的内存区域,分配内存后,调用对象的构造函数,填充对象中各个变量的值,将对象初始化,然后通过构造方法返回对象的地址;
2.是clone,clone也是首先分配内存,这里分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。
3.另外还有输入输出流,反射构造对象(目前来说,我还不会)
我们先来看看浅克隆(get、set方法不用看):




这里,我们可以看到,clone方法并不是把school1的引用赋予school2,而是在堆中重新开辟了一块空间,将school1复制过去,将新的地址返回给school2。
但是school1中stu的hashcode与school2中stu的hashcode相同,也就是这两个指向了同一个对象,修改school2中的stu会造成school1中stu数据的改变。但是修改school2中的基本数据类型与Stirng类型时,不会造成school1中数据的改变,基本数据类型例如int,在clone的时候会重新开辟一个四个字节的大小的空间,将其赋值。而String则由于String变量的唯一性,如果在school2中改变了String类型的值,则会生成一个新的String对象,对之前的没有影响,这就是浅度克隆。
我们再来看看深克隆:
在上面代码的基础上修改几个地方:
1.让Student实现Cloneable接口,并重写clone方法

2.修改School里的clone方法

3.main方法里,我们尝试修改学生的克隆学校里的学生的年龄和性别

这里我们发现,stu哈希码也不一样了,但是性别哈希码却还是一样的,也就是我们在修改school2.stu的性别时,那么school1.stu的性别也会变。为什么呢?因为Student类里面,sex是StringBuffer类型的,它也是引用类型。在clone的时候将StringBuffer对象的地址传递了过去,而StringBuffer类型没有实现cloneable接口。这种情况,是没有办法实现完全深度克隆的。
解决方案:
1.只实现浅度克隆
2.那么修改school2.stu性别,调用school2.stu.setSex()方法时,这样使用:school2.stu.setSex(new StringBuffer("newString"));


老九学堂会员社群出品
作者:柳成萌
浙公网安备 33010602011771号