1. 接口实例
当我们定义一个学生类时如何给其对象数组排列呢?
再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).
Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
};
按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?
Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
此时我们点击发生异常去看源码可以发现
可以发现这个比较的对象要满足Comparable类型和[runHi++]).compareTo(a[lo]) < 0 能实现compareTo方法,产生返回值的条件
仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
此时我们再 ctrl 选中 compareTo如何实现可以发现
所以我们可以将学生类改为
类实现 Comparable 接口
实现其中的 compareTo 方法(根据年龄来比较)
在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0
再次执行程序, 结果就符合预期了.
// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]
注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.参数时可以比较的数组
public static void sort(Comparable[] array) {
for (int bound = 0; bound < array.length; bound++) {
for (int cur = array.length - 1; cur > bound; cur--) {
if (array[cur - 1].compareTo(array[cur]) > 0) {
// 说明顺序不符合要求, 交换两个变量的位置
Comparable tmp = array[cur - 1];
array[cur - 1] = array[cur];
array[cur] = tmp;
}
}
}
}
为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)
再次执行代码
sort(students);
System.out.println(Arrays.toString(students));
// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]
我们这样实现了一个根据学生年龄排序的方法,那下次我们要根据分数排序呢?是不是又要改代码,这会十分困难。所以我们可以使用比较器Comparator
我们先正常定义一个类
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age -o2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score - o2.score);
}
}
然后根据需要比较的属性定义比较器,
这样我们new 一个比较器的对象 通过调用其的方法就可以比较其大小了
2.Clonable接口
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 但是要想合法调用 clone 方法, 必须要
先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
例如我们定义一个类
此时内存的情况应该为
我们现在要做的是要把它所指的对象clone一份
此时会抛出一个异常
当我Ctrl选中clone查看可以看到
其父类方法是protected修饰的 不同包的子类才能访问 而任何类都默认继承Object类 所以我们的Student类是Object类的子类要想使用clone需要使用super和重写该方法(此时子类只调用了父类的方法)
此时编译器又报了一个异常的报错
此时我们需要在主函数接一个异常
此时又报了一个错误
这里是因为调用的是父类Object的方法 而我们是接收的是子类所以这时我们需要一个向下转型将其转为子类Student
此时我们运行程序发现又报错了
所以我们要把这个类实现一个Cloneable接口
此时就能正常运行代码了
当我们Ctrl Cloneable可以发现
发现该接口什么都没有,为空接口,为什么?作用是什么?
这个接口被叫做标记接口,实现了该接口,证明当前的类时可以被克隆的!
此时内存的情况为
3. 深拷贝
什么是深拷贝?在了解深拷贝之前先了解浅拷贝。
例如 :定义了一个Money类
class Money{
public double money;
}
其Student类包含有
class Student implements Cloneable{
public int age;
public Money m = new Money();
public Student(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
'}';
}
}
Student类中包含有Money类的初始化和age两个成员变量
在主函数中有
public static void main(String[] args) throws CloneNotSupportedException{
Student student = new Student(10);
student.m.money = 19.9;
Student student1 = (Student) student.clone();
System.out.println(student.m.money);
System.out.println(student1.m.money);
System.out.println("===========");
student.m.money = 30;
System.out.println(student.m.money);
System.out.println(student1.m.money);
}
我们令student1 clone student 此时我们修改 student对象m类里的成员变量会发现 student1 对象的值也会跟着改变?这是为什么呢?不是说clone是对其拷贝了吗?
在这里其在内存的情况是这样的
所以两个student 和student1 都是指向同一个 money对象 此时修改一个的money另一个也会改变,此时Money并没有拷贝,就称该为浅拷贝。
所以,当我们把Money拷贝了也就是深拷贝了,深拷贝和浅拷贝只是代码实现上不一样,原理上也不一样,深拷贝就是把对象上的所有对象都拷贝了。
那我们该如何实现深拷贝呢?
第一步我们首先让Money实现Cloneable接口
class Money implements Cloneable{
public double money;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
第二步让完善Student类的clone方法
此时我们定义一个Student类的tmp对象让其拷贝需要拷贝的对象,在让其对象的m进行拷贝一份,在clone中返回的都是Object类要进行类型转换
然后就完成了对象的所有拷贝也就是深拷贝。
在内存中大概是
最后返回引用tmp clone对象接收 tmp局部变量消失就完成了深拷贝