【Java语法】常用接口介绍

60 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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));
       }
}

image-20220911155052572

此时编译器并不知道到底是按姓名还是年龄进行排序,当sort方法对对象所属的类进行排序时,对象所属的类必须实现Comparable接口,通过参考文档可知,Comparable接口中仅有一个抽象方法。

image-20220911160102647

image-20220911160810541

那么我们就可以实现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");
        }
    }
​
}

此时可以得到按年龄进行排序的结果:

image-20220911161433815

我们知道在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接口时,可以使用两个参数重载的方法实现排序,包含一个比较器类型的参数

image-20220911202547167

首先通过参考文档了解Comparator接口,我们需要重写的是compare()方法

image-20220911201659964

所以就像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 的值。

image-20220911210732338

所以我们需要重写Object类中的clone方法进行克隆,在使用clone方法之前,需要实现Cloneable接口

image-20220911212305289

image-20220911212346096

由源码和参考文档可知,Cloneable是一个空接口即标记接口,如果有其他类继承该接口,说明该类的对象是可以被克隆的。

  • 要克隆的这个对象的类必须实现 Cloneable 接口
  • 类中重写 Objectclone() 方法
  • 处理重写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 的内容。此时我们就成功实现了对象的拷贝

image-20220912111356795

4.4 浅拷贝与深拷贝
  • 浅拷贝

根据上边Cloneable接口使用介绍我们已经详细了解了,此时我们提出了一个问题,如果在Student类当中再定义一个引用类型,那么又该如何拷贝呢?

image-20220912164054998

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 的地址,并未重新复制一块内存出来,此种拷贝就叫做浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 , 因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

image-20220912165518193

  • 深拷贝

刚刚我们通过实现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);
​
    }
}
​

image-20220912170805533

此时的内存结构图为:image-20220912203740841

上面的拷贝就把引用变量teacher所指向的Teacher类的对象也在堆中拷贝了一份,这就是深拷贝, 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

深拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,无论该成员属性是值类型的还是引用类型,都复制独立的一份,引用类型也会复制该引用类型所指向的对象。