下面的三个案例,都是需要实现接口,才能进行的操作。
目录
1.比较对象大小
2.给对象排序
3.深浅拷贝
1.比较对象大小
1.1引入
(1)普通类型比较
(2)引入类型比较
发现报错,因为在Java中,引用类型不能直接比较大小,引用类型指向的是对象。在比较的时候,编译器不清楚你要比较对象中的什么内容,所以不允许使用大于小于号来比较引用类型的大小。
所以需要借助一个方法进行比较:comparaTo()
(3)使用comparaTo()比较
我们发现,当String类型的引用使用该方法去比较时,是允许的;但是Student类型的引用去比较却是不可以的。说明String类可以使用该方法,那我们下面就去了解该方法
(4)String中的comparaTo方法
我们就可以看到,在String内部,是有这个类的。
然后我们发现,是String类实现了Comparable接口,然后重写了comparaTo方法
所以String类可以调用comparaTo方法,Student类不行是因为其没有实现该接口。
(5)结论
所以,一个自定义类型(引用类型),需要比较两个对象大小的时候,就需要实现Comparable接口,并且重写comparaTo方法
1.2.实现接口、重写方法
(1)实现接口
报错的原因是没有重写接口中的方法
(2)重写方法
(3)修改重写方法
按照年龄比较:
返回大于0的数字,说明第一个对象比较大
按照姓名比较:
返回小于0的数,说明第二个对象比较大
class Student implements Comparable<Student> {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
public class Test2 {
public static void main(String[] args) {
Student s1 = new Student("张三",19);
Student s2 = new Student("李四",18);
System.out.println(s1.compareTo(s2));
}
}
2.给对象排序
2.1.引入
(1)给一个数组排序
我们可以发现,可以给数组排序,并且还能打印出来。
(2)给自定义类型排序
程序一允许,发现报错了。
原因是:类型转化异常。而且Students类是自定义类型,里面的元素不可比较大小,也就无法完成排序。
做法:和上面一样,需要实现Comparable接口,并且重写comparaTo方法,指定比较的具体对象才行。
2.2.实现接口、重写方法
(1)实现接口
(2)重写方法
(3)自定义比较
(4)排序效果
(5)完整代码
class Students implements Comparable<Students>{
public String name;
public int age;
public Students(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Students o) {
return this.age - o.age;
}
}
public class Test3 {
public static void main(String[] args) {
Students s1 = new Students("a",17);
Students s2 = new Students("d",16);
Students s3 = new Students("b",20);
Students[] students = {s1,s2,s3};
Arrays.sort(students);
}
}
排序之后是按照年龄从小到大排序。
如果这个时候又想去按照姓名去排序呢?难道是直接修改comparaTo方法吗?那样的话就非常的麻烦和不方便了,所以接下来就介绍一个比较器。
2.3.实现比较器
(1)什么是比较器
比较器,本身是一个单独的类,这个类实现了Comparator接口,并且内部重写了compare方法
(2)举例
1)实现接口
2)重写方法
3)指定年姓名排序
4)简单比较
显而易见,是第二个名字比较大
(3)使用比较器排序
1)使用Arrays.sort对自定义类型排序,可以看到参数是支持一个比较器的
2)传参
3)运行结果
排序前:
排序后:
所以说,我们可以自定义比较器,就可以自己选择排序方式;如果是在待排序内部定义,则写死了,如果需要按照其他的方式进行排序,则需要重写写。
3.深浅拷贝
什么叫深拷贝、浅拷贝,其实是代码层面上的一种实现方式。拷贝也称为克隆,就是将对象多复制出来一份,复制后的对象是一个独立的空间,和旧的对象完全独立,只是内容一样。
3.1.浅拷贝
浅拷贝就是普通的对象克隆,在这里介绍什么是对象克隆,和怎么样去克隆对象。
(1)克隆普通对象
1)对该类的对象进行克隆
下面是克隆的语法。让对象调用克隆方法即可,但是下面报错了。
报错的原因:因为父类的clone方法是被保护起来的,在不同包中去访问,需要通过sper关键字去访问。
解决方法:在Data类中重写该方法
但是仍然报错,但是报错原因不一样,现在的原因是:父类当中的方法是声明了异常,而这里没有,所以解决方法就是统一格式:
上述没有报错,程序运行后:
抛了个异常,显示克隆异常,什么原因呢?原因就是这个类不支持克隆,解决方法:需要实现克隆接口:
克隆后:
普通类克隆的总结:
3.2.深拷贝
(1)引入
下面是两个类,通过组合的方式进行组织
克隆后:
上述的效果不明显,我们通过代码层面观察。
运行结果:
我们发现,通过克隆后的对象去修改,最终原对象中的值也被修改了,这很不符合克隆的要求。例如,A羊复制出B羊,此时有两个羊;然后将克隆羊B的一条腿砍了,A羊此时的一条腿也会跟着没,这是不符合的,因此这种称为浅拷贝。要想做到深拷贝,需要在代码上进行修改。
(2)浅拷贝对象的内存指向
1)实际指向
下面这种结果,new Son()对象是共享的,通过其中一个对象修改,势必会影响到第二个对象。
2)目标指向
所以要做到上面的效果,就需要去修改代码
(3)实现深拷贝
1)分析原有的克隆方法
2)需要将t1中指向的对象也克隆一份
t1指向的对象要想被克隆,那么它本身也是要支持克隆的
实现深克隆:
3)深拷贝完整代码:
class Son implements Cloneable{
public int data = 9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Data implements Cloneable{
public int data1;
public int data2;
public Son data3;
public Data(int data1, int data2) {
this.data1 = data1;
this.data2 = data2;
data3 = new Son();
}
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
Data tmp = (Data) super.clone();//浅拷贝
tmp.data3 = (Son) this.data3.clone();//克隆对象的对象
return tmp;
}
}
public class Test4 {
public static void main(String[] args) throws CloneNotSupportedException {
Data t1 = new Data(11,22);
Data t2 = (Data) t1.clone();
System.out.println("修改之前:"+t1.data3.data);
System.out.println("修改之前:"+t2.data3.data);
t2.data3.data = 999999;//通过克隆得到的对象去修改
System.out.println("修改之后:"+t1.data3.data);
System.out.println("修改之后:"+t2.data3.data);
}
}
运行结果:
4)深拷贝过程分析
第一步:整体克隆(浅拷贝)
第二步: 克隆对象的对象(深拷贝)
第三步:赋值
第四步:返回接收
直至,完成了深拷贝的实现,这样对t2中的data3进行任意修改,都不会影响t1中的。
所以,深拷贝:克隆出一份完全独立对象的对象。也就是要将对象的对象也克隆一份。