三种 常用接口

三种 常用接口

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));

    }
}

在这里插入图片描述

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

想一想 . 我们 两个 普通的 整数 比较 是不是 可以 直接 比较 如 : 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));

    }


在这里插入图片描述


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

在这里插入图片描述

这里 稍微 讲解 一下 这个 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));

    }
}


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

在这里插入图片描述


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

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

这里 就 推出来 了 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));

    }
}


在这里插入图片描述


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

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

下面 我们 按照 姓名来 排序

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

在这里插入图片描述


为啥 说 太年轻 了 呢 ?

想一想 我们的 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();
    }
}


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);
    }
}

在这里插入图片描述


这里 就成功 将 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();
    }
}

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

在这里插入图片描述


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

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

第一步 Money变为 可克隆的


在这里插入图片描述

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


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

在这里插入图片描述

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

  • 21
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值