• 三种 常用接口


    三种 常用接口

    1.Comparable

    2.Comparator 比较器

    3.Cloneable

    1.Comparable 接口

    前引 :

    在这里插入图片描述

    熟悉吗? 我 想 大家 应该非常熟悉 .

    对于 数组我们 能通过 Arrays.sort() 来进行 排序 数组 ( 数组 内 部 是 普通 数据类型).

    如果 我们 使用 Arrays.sort() 排序 的 数组 内部 是 对象(对象的 地址 )呢?

    这里 能否 进行 排序 .

    class Student {
        public int age;
        public String name;
        public double score;
    
        public Student(int age,String name,double score) {
            this.age = age;
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    }
    public class Main {
        public static void main(String[] args) {
            Student[] students = new Student[3];
            students[0]  = new Student(12,"小红",98.9);
            students[1]  = new Student(6, "小黄",18.9);
            students[2]  = new Student(18,"小蓝",88.9);
            System.out.println(Arrays.toString(students));
    
            Arrays.sort(students);
    
            System.out.println(Arrays.toString(students));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    在这里插入图片描述

    尝试 之后 会发现 报错 了 ,为啥 呢 ?

    想一想 . 我们 两个 普通的 整数 比较 是不是 可以 直接 比较 如 : 2 > 1 , 这种 大小 关系 就非常明确 , 如果 是 两个 对象 呢 ?

    如: 上面的 学生 类 , 创建 出两个 对象 学生 1 和 学生 2 , 他们 拥有的 属性 有 年龄 , 姓名, 成绩 , 此时 比较学生 1 和 学生 2 , 我们的 sort 就 会 犯了迷糊 , 是 按照 年龄 比较 , 还是 按照 成绩 比较 , 还是 按照 姓名比较 呢 (字母的 ASCII 值 )?

    所以 这里我们 就 报错 了 , 这里 就 需要 我们 自己 指定 比较 规则 .

    下面让我们 进入 sort 的 源码 来了解一下.

    第一步 : 通过 Ctrl 加 鼠标左键 进入 sort 的 源码 . 然后 进入 legacyMergeSort方法.

    在这里插入图片描述


    第二步 进入 mergeSort 方法 中

    在这里插入图片描述


    此时 你就能 看到 我们 要学的 ComparablecompareTo

    在这里插入图片描述

    这 段 代码 的 意思 : 就是 数组 每个 对象 调用 comparable 进行 比较 , 通过 compareTo 的比较 规则 ,进行比较 . 

    (Comparable 可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行)

    知道了 原理 :

    下面我们 就来 尝试 告诉我们的sort 比较规则 来 对 我们的 学生类 进行 比较 .

    第一步: 实现 comparable 接口 .

    comparable 是 一个 接口 , 我们 使用 implements来 实现我们的 comparable 接口 .

    在这里插入图片描述


    此时 你会看到 一个 这 是 一个 泛型 ,T 是 我们的 类名 , 泛型 将在 数据结构 中 讲到 这里 直接 使用 即可 .

    在这里插入图片描述


    当你 实现 完 接口 后 发现报错 了, 下面继续 进入 Comparable 接口 中 ,

    在这里插入图片描述


    你能 发现 这里 会有 一个 方法 compareTo , 之前学过 接口 在 接口中的 方法 默认 是 被public abstract修饰 的 是 抽象 方法, 需要我们 重写.

    在这里插入图片描述

    下面 就来 按照 年龄 来 比较

    class Student implements Comparable<Student>{
        public int age;
        public String name;
        public double score;
    
        public Student(int age,String name,double score) {
            this.age = age;
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    
        @Override
        public int compareTo(Student o) {
            return this.age - o.age;
        }
    }
    public class Main {
        public static void main(String[] args) {
            Student[] students = new Student[3];
            students[0]  = new Student(12,"小红",98.9);
            students[1]  = new Student(6, "小黄",18.9);
            students[2]  = new Student(18,"小蓝",88.9);
            System.out.println(Arrays.toString(students));
    
            Arrays.sort(students);
    
            System.out.println(Arrays.toString(students));
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38


    在这里插入图片描述


    此时 就 按照 年里 进行 了 比较 ,

    在这里插入图片描述

    这里 稍微 讲解 一下 这个 this.age - o.age this 我们 学过 是 调用 当前 对象 , o 是 传入 的 对象, 这里 就 是 通过 当前 对象 的 年龄 减 去 传入 对象的 年龄

    如果当前对象应排在参数对象之前, 返回小于 0 的数字;

    如果当前对象应排在参数对象之后, 返回大于 0 的数字;

    如果当前对象和参数对象不分先后, 返回 0;

    图解:

    在这里插入图片描述


    同样 我们 也可 完成 降序 只需要 更改一下 比较 规则 即可 .

    在这里插入图片描述


    此时 我们就完成 了 降序操作.

    这里 就得出 结论 :如果我们 想要 对 自定义的数据类型,进行大小的比较 需要实现可以比较的接口Comparable。

    知道 了Coparable 下面来学习一下 Comparator 比较器 .

    2. Comparator 比较器


    还是上面 的 这个 代码

    class Student implements Comparable<Student>{
        public int age;
        public String name;
        public double score;
    
        public Student(int age,String name,double score) {
            this.age = age;
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    
        @Override
        public int compareTo(Student o) {
            //升序
    //        return this.age - o.age;
            // 降序
            return o.age - this.age;
        }
    }
    public class Main {
        public static void main(String[] args) {
            Student[] students = new Student[3];
            students[0]  = new Student(12,"小红",98.9);
            students[1]  = new Student(6, "小黄",18.9);
            students[2]  = new Student(18,"小蓝",88.9);
            System.out.println(Arrays.toString(students));
    
            Arrays.sort(students);
    
            System.out.println(Arrays.toString(students));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42


    我们 上面 是 按照年龄 比较 的,如果 这个逻辑 执行了 几个月, 有很多 用户在使用 , 此时 我们 觉得 按照 年龄比较 有点 不妥 ,这里 就改为 按照 成绩比较 , 此时 就 去 改 代码 .

    在这里插入图片描述


    但 你有没有 想过 , 在 这 几个 月的 时间里 , 有 很多 用户 使用这个规则 进行 比较 , 那么 此时 就会出现问题 .

    另外我们 的代码 也 可能会 因为这个 规则 的改变 而出现 很多 问题 .

    这里 就 推出来 了 Comparator 这个 接口 来解决上面的 方法

    所以 这里 我们 就来 学习 一下 我们 的 Comparator 这个 接口 (这个 接口 也称 比较器 )

    1.创建一个比较器,实现我们的 Comparator 接口

    在这里插入图片描述


    2.重写我们的 compare 比较 方法.

    在这里插入图片描述

    在这里插入图片描述


    补充 :

    为啥 Comparator 这个 接口 有那么 多 方法 只重写 了 compare 方法 就 不会 报错了 ?

    在这里插入图片描述


    这里我们在 接口中 讲到 过 , 如果 在接口 中 方法 是被 defaultstatic 修饰 是 可以 不重写 的 , 但 equals 既没有 被 default 也 没有 被

    static 修饰 同样也 不需要重写 是 为什么 呢?

    在这里插入图片描述


    这里 是因为 所有类默认继承Object,所以该类已有了Object的equals方法,相当于重写了equals方法。

    下面 继续 .

    class Student {
        public int age;
        public String name;
        public double score;
    
        public Student(int age, String name, double score) {
            this.age = age;
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    
    
        //创建 比较器 
    class AagComparator implements Comparator<Student>{
    
        @Override
        public int compare(Student o1, Student o2) {
            return o1.age - o2.age;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Student[] students = new Student[3];
            students[0] = new Student(12, "小红", 98.9);
            students[1] = new Student(6, "小黄", 18.9);
            students[2] = new Student(18, "小蓝", 88.9);
            System.out.println(Arrays.toString(students));
    
            // 实例化 比较器 
            AagComparator aagComparator = new AagComparator();
            // 传入 比较器  
            Arrays.sort(students,aagComparator);
    
            System.out.println(Arrays.toString(students));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47


    在这里插入图片描述


    这里 就 通过 比较器 按照 我们 的 年龄 排序 .

    下面 我们 还能 按照 成绩 .
    在这里插入图片描述

    下面 我们 按照 姓名来 排序

    当你 按照 上面的 思路 你会发现 报错了 , 我 只能 说小伙子 太年轻了.

    在这里插入图片描述


    为啥 说 太年轻 了 呢 ?

    想一想 我们的 name是不是 String 类型 , 此时 name 就是 引用 ,里面存放 的 地址 , 你咋能 通过 地址相减 来比较大小 呢 ?


    此时 就有 同学 就会 问了 不能 相减 那么 要咋做 呢?

    很简单我们看看 String 的 源码 . 去里面 找找答案 .

    当你 进入 String 的源码 中 能发线 我们 的 String 也 重写了 compareTo方法, 那么 直接调用 这个 方法 就能 完成我们的 比较了 ,是不是非常方便 .

    在这里插入图片描述


    下面 就来 演示 一下 : 在这里插入图片描述


    这里 我们 就 学完了 Comparable 接口 和 Comparator 接口 , 下面我们就来找一下 两者 的 区别 .

    Comparable Comparator 都是用来实现元素排序的,它们二者的区别如下:

    • Comparable 是“比较”的意思,而 Comparator 是“比较器”的意思;
    • Comparable 是通过重写 compareTo 方法实现排序的,而 Comparator 是通过重写 compare 方法实现排序的;
    • Comparable 必须由自定义类内部实现排序方法,而 Comparator 是外部定义并实现排序的。


    所以用一句话总结二者的区别:``Comparable可以看作是“对内”进行排序接口,而Comparator` 是“对外”进行排序的接口。

    简单 来说: Comparable 对类 的 侵入性高, 一旦写好比较规则,就不能 轻易改变 。

    Cinoarator 对类的 侵入性 低, 写好 后 , 传入 比较器来 进行 比较 即可 , 如果 对 这个 比较 规则 不满意 ,也能重新 写一个 比较器来比较。

    下面 来看一下 我们 常用 接口 的 第三个 Cloneable 接口

    3.Cloneable 接口


    这个 接口 主要用来 对我们 的 对象 进行 克隆 的 , 下面就来 学习 一下 。

    在之前 我们 学过一个 方法 为 clone(), 是 对 数组 进行 克隆,

    在这里插入图片描述


    那么 我们 能不能使用 clone() 这个方法 对 自定义数据类型进行 克隆 呢?

    下面就来 尝试 一下 。

    1.创建 一个 Person 类 , 在 main 创建 对象

    class Person {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "嗷嗷猛 炫");
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            Person person1 = new Person();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15


    2.克隆

    在这里插入图片描述


    可以看到 划红线了 这里 我们 就 进入 这个 clone() 方法的 源码 看看,


    刚进来:我们观察到 这个 方法 的 返回类型 是 Objct 的 (注意:Object 是 所有类的 父类 ), 那么 这里我们 就需要 将这个返回值 转为 我们的 Person 类。

    在这里插入图片描述


    此时 还是 报错 了 。

    在这里插入图片描述


    其实 最根本的 原因 是 一个对象要克隆要产生一个副本 这个引用 ,引用的对象是要可克隆的 (引用的对象要实现Cloneable接口


    实现 Cloneable 接口

    下面 我们就来实现我们的 Cloneable 接口

    在这里插入图片描述


    当我们 实现 了 Cloneable接口 后 ,发现还是 不能完成 我们 的克隆 , 是不是 很难受, 搞这么 多 还是 不能用 搞么呀。

    那么 我们就 进入 Cloneable 的源码 来找 一下答案 。

    在这里插入图片描述


    这里我们 点进 Cloneable 发现它 是一个空接口 ,

    空接口 有啥作用 -》标志接口 -》 代表当前这个类是可以被克隆的。

    这个 地方 我们 需要 重写克隆方法 然后 申明一个 异常 发现 clone 就可以 使用了

    另外:我们 将 鼠标 放在 报错 的地方 ,会出现 下面 的 内容 ,

    在这里插入图片描述


    这里 他 告诉 我们需要抛出 一个 异常 (注意: 异常 会在 后面 学到 ,这里 看看 即可)


    那么 我们就来 重写我们的克隆 方法 , 并声明 异常 。

    1.重写 克隆方法

    在这里插入图片描述


    2.可以看到 虽然 重写了 克隆 方法 ,但是 还是报错了 ,这里 就需要我们 抛出异常。


    第一种通过 throw 将异常抛出

    在这里插入图片描述


    这里我们 有 两种 处理 异常 的 方法 , 一种 就是 上面 将 异常抛出来 ,让 JVM 帮忙 处理(如果在 方法中 抛出 ,此时 会 先让 main函数 处理 ,main 处理不了 ,最后 会抛给 JVM 来 帮我们 处理)、


    第二种 处理 异常 通过 try - catch 来 包裹 异常 。

    在这里插入图片描述


    上面这些 了解 就好 , 这样我们就 成功的解决了 我们 clone()报错 ;

    下面就来 使用

    class Person implements Cloneable {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "嗷嗷猛 炫");
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Person person1 = new Person();
            Person person2 = (Person) person1.clone();
            System.out.println(person1);
            System.out.println(person2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述


    这里 就成功 将 person1 个 克隆 出来 了。

    下面 就来 看看 他的 内存 分布 图 :

    在这里插入图片描述

    此时 就会有一个问题: 如果 我们 对 克隆 出来的 副本 来说,将 原来 的age 改成 99 会 影响 这个拷贝 出来的 age 值吗?

    演示:

    在这里插入图片描述


    可以发现 是 不会 受到影响的 ,这里 就又回到 了 我们 的 深浅拷贝 ,关于 深浅拷贝 在 数组那篇 文章 就 简单 过 了 一遍 ,这里我们 再来 巩固一下。

    深拷贝和浅拷贝

    无论是深拷贝,还是浅拷贝,都是是认为实现的一个方式。

    深拷贝:拷贝完成 后 通过一个引用来修改 拷贝 的数组 ,原来的数组不会受影响 就为 深拷贝

    浅拷贝:拷贝完成 后 通过 一个引用来修改 拷贝的数组,原来的数组一同被 修改就为 浅拷贝

    决定是深拷贝,还是浅拷贝 不是方法的用途,是代码的实现。也就是人为的实现。

    上面的程序,就是 一个 深拷贝

    深浅拷贝

    这里 我们 在 来 定义 一个 类 money (钱) ,钱 这个类 ,在 Person(人) 这个 类 中 实例化 。

    判断 当前 对 money 这个 对象的拷贝 是 深拷贝 还是 浅拷贝 。

    import java.util.Arrays;
    class  Money{
        public double m;
    }
    class Person implements Cloneable {
    
        Money money = new Money();
        public String name;
        public int age;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Person person1 = new Person();
            Person person2 = (Person) person1.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    答案 浅拷贝 , 但对于 age 来说 是 深拷贝 (这里 也印证 了 ,深浅拷贝 都 是 人为 实现 的 )

    在这里插入图片描述


    那么 我们 能不能 改为 深拷贝 呢 ?

    这里 是 可以的 下面就来 演示 一下 :

    第一步 Money变为 可克隆的


    在这里插入图片描述

    第二步 在 Person 的 重写 克隆 方法 中 完成 克隆 。
    在这里插入图片描述


    这里 我们 先 克隆 出 一份 Person 赋值 给 tmp, 然后 让 tmp中 的 money 对象 拿到 ,当前 Person 对象 中 的 money 克隆体。最后 返回 我们 克隆 的 tmp 即可 。

    在这里插入图片描述

    本文 完 下文 预告 : 图书管理小练习(结合之前 学的 类和 对象 , 面向 对象的 三大特征 :封装 ,继承 , 多态 , 还有我们的 抽象类 和 接口 的 一个 小练习 ,将这些 知识点 串联起来)。

  • 相关阅读:
    华为路由器如何配置静态路由
    【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(8 月 25 日论文合集)
    C++常成员函数 - const 关键字
    第二十五章《图书管理系统》第3节:项目完整代码
    【软考】系统集成项目管理工程师(六)项目整体管理【6分】
    【论文笔记】Transformers in Remote Sensing: A Survey 中的相关论文链接
    Python的一些高级用法
    学习路之PHPstorm--使用ftp功能连宝塔报错
    北太天元安装教程 及使用方法
    Android项目实践(二)——日记本APP(V2)
  • 原文地址:https://blog.csdn.net/mu_tong_/article/details/126288028