持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
常用接口介绍
1.1 Comparable接口(比较)
在学习数组时,Arrays类中的sort方法可以对对象数组进行排序 , 那下面的对象数组能不能用Arrays.sort排序呢?
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
public class test4 {
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("zhangsan", 13),
new Student("lisi", 23),
new Student("able", 17),
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
此时编译器并不知道到底是按姓名还是年龄进行排序,当sort方法对对象所属的类进行排序时,对象所属的类必须实现Comparable接口,通过参考文档可知,Comparable接口中仅有一个抽象方法。
那么我们就可以实现Comparable接口,并实现和重写compareTo方法
class Student implements Comparable<Student>{
public int age;
public String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
//重写compareTo方法
@Override
public int compareTo(Student o) {
if (this.age - o.age > 0)
return 1;
else
if (this.age - o.age < 0)
return -1;
else
return 0;
}
public static void main1(String[] args) {
Student student = new Student(16, "liba");
Student student1 = new Student(13, "zhangsan");
System.out.println(student.toString());
System.out.println(student1.toString());
if (student.compareTo(student1) > 0) {
System.out.println("student > student1");
} else {
System.out.println("student < student1");
}
}
}
此时可以得到按年龄进行排序的结果:
我们知道在Arrays.sort(students); 中是传了一个学生对象数组,在调用Arrays对对象数组排序的时候,其实就调用了我们的Comparable接口中的compareTo方法对数组的每个元素进行了排序和比较,在Java中对于引用数据类型的比较或者排序,一般都要用到使用Comparable接口中的compareTo() 方法
按姓名排序时,重写的compareTo方法
@Override
public int compareTo(Student o) { // this.代表对当前对象的引用,o.代表对参数对的引用
if (this.name.compareTo(o.name) > 0)//String类中重写了compareTo方法,可直接使用
return 1;
else if (this.name.compareTo(o.name) < 0)
return -1;
else
return 0;
}
//如果当前对象应排在参数对象之前, 返回小于 0 的数字;
//如果当前对象应排在参数对象之后, 返回大于 0 的数字;
//如果当前对象和参数对象不分先后, 返回 0;
🎈 缺点:一旦重写了comparable()方法,那么就只能对一种参数类型进行比较,把方法写死了,此时就需要使用Comparator接口 ❗
1.2 Comparator接口(比较)
这里是Arrays.sort中只有一个参数的方法
当实现Comparator接口时,可以使用两个参数重载的方法实现排序,包含一个比较器类型的参数
首先通过参考文档了解Comparator接口,我们需要重写的是compare()方法
所以就像Comparable 接口一样,我们只要实现了Comparator接口,并重写Comparator里的compare方法就可以实现对学生对象数组的排序
比如我们上面的年龄比较就可以写成这样
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.age + "]";
}
}
// 实现Comparator接口中的compare方法
class AgeComparator implements Comparator<Student> { // 年龄比较器
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
// 反正返回的也是数字,当o1.age>o2.age时返回大于零的数,即o1对象排在o2对象的后面,升序排列,我们之前用Comparable接口时也可以这样简写
}
}
public class test4 {
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zhangsan", 13),
new Student("lisi", 23),
new Student("able", 17),
};
AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students, ageComparator);
// 用类Arrays.sort对students数组进行排序,这里传了两个参数(学生对象和所对应的年龄比较器)
System.out.println(Arrays.toString(students));
}
}
同样,当我们按照姓名进行排序时,也可以使用此接口
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.age + "]";
}
}
class NameComparator implements Comparator<Student> { // 姓名比较器
// 实现Comparator接口中的compare方法
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
// 因为name是String类型,也是一个引用类型,也要用到compareTo方法,此时的compareTo方法是String类里重写的方法
}
}
public class test4 {
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zhangsan", 13),
new Student("lisi", 23),
new Student("able", 17),
};
NameComparator nameComparator = new NameComparator();
Arrays.sort(students, nameComparator);
System.out.println(Arrays.toString(students));
}
}
Comparable接口和Comparator接口都是Java中用来比较和排序引用类型数据的接口,要实现比较,就需要实现他们所各种对应的compareTo方法或者compare方法。
✅Comparator使用起来更加灵活,所以我更倾向于使用比较器:Comparator
1.3 Cloneable接口(拷贝)
- 对象在内存当中的存储
class Student {
public int age = 15;
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
public class test3 {
public static void main(String[] args) {
Student student1 = new Student();
System.out.println(student1);
}
}
此时如果在堆内存中对student1对象拷贝一份,如果使用
Student student2 = student1;
这只是我们在栈上重新定义了一个引用变量student2,并指向了堆上的student1对象,并没有对我们的student1实现拷贝,改变student2.age会影响student.age 的值。
所以我们需要重写Object类中的clone方法进行克隆,在使用clone方法之前,需要实现Cloneable接口
由源码和参考文档可知,Cloneable是一个空接口即标记接口,如果有其他类继承该接口,说明该类的对象是可以被克隆的。
- 要克隆的这个对象的类必须实现 Cloneable 接口
- 类中重写 Object 的 clone() 方法
- 处理重写clone方法时的异常情况
- clone方法需要进行强转(比较特殊,先记住就好)
class Student implements Cloneable{
public int age = 10;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
public class Demo2 {
public static void main(String[] args) throws CloneNotSupportedException{
Student student = new Student();
Student student2 = (Student)student.clone(); //返回值为Object需要进行强制类型转换
System.out.println(student.age);
System.out.println(student2.age);
student2.age = 18;
System.out.println(student.age);
System.out.println(student2.age);
}
}
此时在内存当中就是这样,student1和student2 中的两个age是相互独立的,student2的age发生改变不会影响student1 的内容。此时我们就成功实现了对象的拷贝
4.4 浅拷贝与深拷贝
- 浅拷贝
根据上边Cloneable接口使用介绍我们已经详细了解了,此时我们提出了一个问题,如果在Student类当中再定义一个引用类型,那么又该如何拷贝呢?
class Teacher{
int number = 20;
}
class Student implements Cloneable{
public int age = 10;
Teacher teacher = new Teacher();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
public class Demo2 {
public static void main(String[] args) throws CloneNotSupportedException{
Student student = new Student();
Student student2 = (Student)student.clone(); //返回值为Object需要进行强制类型转换
System.out.println(student.teacher.number);
System.out.println(student2.teacher.number);
student.teacher.number = 100;
System.out.println(student.teacher.number);
System.out.println(student2.teacher.number);
}
}
此时,student 中teacher的改变也引起了 student2中地址的改变,此种拷贝就好像只拷贝了student.teacher.number 的地址,并未重新复制一块内存出来,此种拷贝就叫做浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 , 因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝
刚刚我们通过实现Cloneable接口、重写clone方法对Student类实现了拷贝,那么同理我们也可以用这样的办法对Teacher类对象进行拷贝.
class Teacher implements Cloneable{
int number = 20;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student implements Cloneable{
public int age = 10;
public Teacher teacher = new Teacher();
@Override
protected Object clone() throws CloneNotSupportedException {
// 此时我们在进行 “(Student) student.clone();” 操作,
// 我们在堆上对student克隆拷贝出来一个新对象,并让引用变量tmp指向新对象
Student tmp = (Student) super.clone();
// 用this.teacher.clone()对引用变量teacher所指向的Teacher类对象进行克隆
tmp.teacher = (Teacher) this.teacher.clone();
return tmp;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
public class Demo2 {
public static void main(String[] args) throws CloneNotSupportedException{
Student student = new Student();
// 此时的student.clone返回Student类对象的引用tmp,student2 就指向了原来tmp所指向的对象
Student student2 = (Student)student.clone();
System.out.println(student.teacher.number);
System.out.println(student2.teacher.number);
student.teacher.number = 100;
System.out.println(student.teacher.number);
System.out.println(student2.teacher.number);
}
}
此时的内存结构图为:
上面的拷贝就把引用变量teacher所指向的Teacher类的对象也在堆中拷贝了一份,这就是深拷贝, 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,无论该成员属性是值类型的还是引用类型,都复制独立的一份,引用类型也会复制该引用类型所指向的对象。