不就是Java吗 之 接口

2.1 接口的概念

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

比如:

image-20220530223453419

电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备

image-20220530223515101

电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备

2.2 语法

使用关键字interface来修饰

interface IShape {
    
}

注意:接口的命名习惯第一个字母为大写I开头

  1. 成员方法:接口当中的成员方法,都只能是抽象方法,所有的方法都是默认public abstract修饰的

    interface IShape {
        //成员方法
        public abstract void func();
    }
    

    我们可以看到,public abstract是灰色的,这代表可以不用写,直接写void func()即可

    image-20220530224032376

    虽然可以不写,但是这不代表可以不这样写。如果我们故意写错访问权限修饰符(public写成protected),还是会报错

    image-20220530224206378

  2. 成员变量:接口当中的成员变量,默认都是public static final的,且必须要进行初始化

    interface IShape {
        //成员变量
        public static final int a;
    }
    

    image-20220530224348490

    正确写法:

    interface IShape {
        //成员变量
        public static final int a = 10;
    }
    

    image-20220530224535803

    同样,我们看到public static final也是标灰的,这代表我们也可以直接写int a的形式。

  3. 接口里面的方法,默认是不能够进行实现的

    interface IShape {
        //成员方法
        public abstract void func() {
            System.out.println("接口里面的方法是实现不了的");
        }
    }
    

    image-20220530224727027

    如果我们想要去实现接口里面的方法的话,我们需要借助default关键字

    interface IShape {
        //成员方法
        //注意:这个时候应该把public abstract删掉,default才能好使
        default void func() {
            System.out.println("加了default之后,接口里面的方法可以实现了");
        }
    }
    

    image-20220530224942012

  4. 接口里面的静态方法也可以有具体实现

    interface IShape {
    
        public static void staticFunc() {
            System.out.println("接口里面的静态方法是可以实现的");
        }
    }
    

    image-20220530225139778

  5. 接口不能进行实例化(即不能new 接口名())

    interface IShape {
        //成员变量
        public static final int a = 10;
        //成员方法
        default void func() {
            System.out.println("加了default之后,接口里面的方法可以实现了");
        }
    
        public static void staticFunc() {
            System.out.println("接口里面的静态方法是可以实现的");
        }
    }
    public class TestDemo {
        public static void main(String[] args) {
            IShape iShape = new IShape();
        }
    }
    

    image-20220530225345370

  6. 接口里面不能有静态代码块、实例代码块以及构造方法

    interface IShape {
        //接口里面不能有静态代码块
        static {
    
        }
    
        //接口里面不能有实例代码块
        {
    
        }
    
        //接口里面不能有构造方法
        public IShape() {
    
        }
    }
    

    image-20220530230559101

  7. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

    image-20220531220543793

  8. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类(即如果你不想实现接口的方法的话,那你就在这个类前面加abstract)

2.3 接口的使用

一个普通的类,可以通过关键字implement来实现这个接口,这个普通的类需要重写接口里面的抽象方法

interface IShape {
    //成员变量
    public static final int a = 10;
    //成员方法
    default void func() {
        System.out.println("加了default之后,接口里面的方法可以实现了");
    }

    public static void staticFunc() {
        System.out.println("接口里面的静态方法是可以实现的");
    }

    void draw();//没写public abstract,默认是抽象方法,需要重写
}

class A implements IShape {
    @Override
    public void draw() {
        System.out.println("重写了接口IShape里面的抽象方法");
    }
}

其实我们也可以重写func()方法,这里我们就不重写了

注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系

那么main方法中我们虽然不能直接实例化,但是我们可以通过向上转型的方式进行实例化,看例子

public class TestDemo {
    public static void main(String[] args) {
        IShape iShape = new A();//向上转型:不过ishape还是由Ishape(父类)创建的,还是只能调用父类的成员
        iShape.draw();//可以调用抽象方法
        iShape.func();//可以调用default修饰的默认的方法
        
        IShape.staticFunc();//静态方法的调用:类名.的方式
    }
}

那么我们通过接口的方式再来实现一遍之前的Shape类吧

interface Ishape {
    void draw();
}

class Cycle implements Ishape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}

class Rect implements Ishape {
    @Override
    public void draw() {
        System.out.println("♦");
    }
}

class Triangle implements Ishape {
    @Override
    public void draw() {
        System.out.println("▲");
    }
}

public class TestDemo {
    //主函数也还可以使用多态的思想
    public static void draw(Ishape ishape) {
        ishape.draw();
    }
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Triangle triangle = new Triangle();
        
        draw(cycle);
        draw(rect);
        draw(triangle);
    }
}

image-20220530231500818

第二个例子:

请实现笔记本电脑使用USB鼠标、USB键盘的例子

  1. USB接口:包含打开设备、关闭设备功能
  2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
  3. 鼠标类:实现USB接口,并具备点击功能
  4. 键盘类:实现USB接口,并具备输入功能
interface USB {
    //抽象方法:省略了public abstract
    void openDevice();
    void closeDevice();
}

class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    //还可以有自己的方法
    public void click() {
        System.out.println("点击鼠标");
    }
}

class KeyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void input() {
        System.out.println("输入内容");
    }
}

class Computer {
    public void powerOn() {
        System.out.println("开机");
    }

    public void powerOff() {
        System.out.println("关机");
    }

    public void useDevice(USB usb) {
        usb.openDevice();
        if(usb instanceof Mouse) {//如果传过来的是鼠标,那么我们就向下转型成鼠标
            Mouse mouse = (Mouse)usb;
            mouse.click();
        } else if (usb instanceof KeyBoard) {//如果传过来的是键盘,那么我们就向下转型成键盘
            KeyBoard keyBoard = (KeyBoard)usb;
            keyBoard.input();
        }
        usb.closeDevice();
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();//开机

        computer.useDevice(new Mouse());
        computer.useDevice(new KeyBoard());

        computer.powerOff();
    }
}

image-20220530233321511

2.4 实现多个接口

一个类,不仅可以继承抽象类,还可以实现多个接口,接口之间用,隔开

先继承,后实现接口

博客分隔符-笑脸

那么我们来思考这样一个场景:我们有一个动物类Animal

class Animal {
    public String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(this.name + "正在吃");
    }
}

如果我们还想增加一些动作,比如飞、跑、游泳,那我可以在Animal这个类里面实现方法吗?

这显然是不行的。假如狗继承了Animal这个类,那么狗也不会飞啊。

那么我们可以实现飞、跑、游泳这三个对应的类啊,然后让狗继承这三个类

当然是不行的,Java不支持多继承,但是我们之前提到过,Java中用接口完美地替代了这个问题

那么我们可以实现这三个接口

interface IFlying {
    void fly();//接口里面的方法是不能实现的
}

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

那么我们的狗类就可以继承Animal类,然后与飞、跑、游泳这三个类实现接口

class Dog extends Animal implements IRunning,ISwimming {
    
    //1.先要构造父类
    public Dog(String name) {
        super(name);
    }

    //2.要重写接口的方法
    @Override
    public void run() {
        System.out.println(this.name + "is a dog,正在跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is a dog,正在游泳");
    }
}

image-20220531221933196

我再添加一个Cat类与Duck类,最终代码如下

class Animal {
    public String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(this.name + "正在吃");
    }
}

interface IFlying {
    void fly();//接口里面的方法是不能实现的
}

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

class Dog extends Animal implements IRunning,ISwimming {

    //1.先要构造父类
    public Dog(String name) {
        super(name);
    }

    //2.要重写接口的方法
    @Override
    public void run() {
        System.out.println(this.name + "is a dog,正在跑步");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is a dog,正在游泳");
    }
}

class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "is a cat,正在吃饭");
    }

    @Override
    public void run() {
        System.out.println(this.name + "is a cat,正在跑步");
    }
}

class Duck extends Animal implements IRunning,ISwimming,IFlying {

    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name + "is a duck,正在飞翔");
    }

    @Override
    public void run() {
        System.out.println(this.name + "is a duck,正在跑步");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is a duck,正在游泳");
    }
}

因为狗,猫,鸭子既继承了Animal,又实现了不同的接口,我们的main方法就可以实现向上转型的操作

public static void main(String[] args) {
        //父类引用 引用 子类对象
        Animal animal1 = new Cat("小猫");
        Animal animal2 = new Dog("小狗");
        Animal animal3 = new Duck("小鸭");

        //接口也可以实现向上转型,代表这个动物也具备这种功能
        IRunning iRunning = new Cat("小猫");
        IRunning iRunning1 = new Dog("小狗");
        IRunning iRunning2 = new Duck("小鸭");

        ISwimming iSwimming = new Dog("小狗");
        ISwimming iSwimming1 = new Duck("小鸭");

        IFlying iFlying = new Duck("小鸭");
    }

还可以进行多态

public class TestDemo {
    
    public static void run(IRunning iRunning) {
        iRunning.run();
    }
    
    public static void swim(ISwimming iSwimming) {
        iSwimming.swim();
    }
    
    public static void fly(IFlying iFlying) {
        iFlying.fly();
    } 
    
    public static void main(String[] args) {
        Dog dog = new Dog("Timi");
        run(dog);
        
        //还可以这样写
        run(new Cat("杰西卡"));
        run(new Duck("布鲁斯"));
        
    }
}

那么此时,run这个方法只要是能跑的,就可以传过来进行多态(即传过来的对象在类的实现阶段就对IRunning接口进行实现的,就可以实现多态),我们还可以在创建一个机器人类,让他对IRunning进行接口,虽然他不是动物,但是他实现了IRunning接口就可以进行多态

class Robot implements IRunning {
    @Override
    public void run() {
        System.out.println("机器人在跑步");
    }
}
public static void run(IRunning iRunning) {
        iRunning.run();
    }

public static void main(String[] args) {
    Dog dog = new Dog("Timi");
    run(dog);

    //还可以这样写
    run(new Cat("杰西卡"));
    run(new Duck("布鲁斯"));

    //机器人虽然没有继承Animal类,但是他实现了IRunning接口,就可以进行多态
    run(new Robot());

}

那么没有实现对应接口的,那么他就完成不了对应接口进行的多态

public class TestDemo {

    public static void fly(IFlying iFlying) {
        iFlying.fly();
    }

    public static void main(String[] args) {
        fly(new Dog("不会飞的狗狗"));
    }

image-20220531224355748

2.5 接口之间的继承

类和接口之间是implements,接口和接口之间是extends,在这里为扩展的意思

interface A {
    void funcA();
}

interface B {
    void funcB();
}

//代表C具备了A B的功能
interface C extends A,B {
    void funcC();
}

那么我们要是用一个普通的类去实现C接口,就需要重写A,B,C的功能(因为C实现了A,B接口,具备了他们俩的功能)

我们有一个快捷键:定位在错误行按下Alt+回车,编译器会帮我们重写所有需要重写的方法

class AA implements C {

    @Override
    public void funcA() {
        
    }

    @Override
    public void funcB() {

    }

    @Override
    public void funcC() {

    }
}

2.6 三个重要的接口

我们之前学习了Arrays.sort()这个操作,这个操作是用来对数组排序的

import java.util.Arrays;

public class TestDemo1 {
    public static void main(String[] args) {
        int[] array = {1,34,43,22,2};
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }
}

那么假如我们的数组不仅仅是个简单的数组呢,比如我们创建了一个学生数组,我们想要去进行比较

import java.util.Arrays;

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 +
                '}';
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("叶澜依",20,98.5);
        students[1] = new Student("齐妃",39,79.4);
        students[2] = new Student("华妃",19,89.4);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220531232926682

这里报错了,因为我们根本没指定我们到底要比较什么,所以默认会比较地址,但是在Java中是不支持引用比较的,所以就会报错。

比如这样:

public static void main(String[] args) {
        Student student1 = new Student("张三",20,39);
        Student student2 = new Student("李四",30,49);

        //Java里面不支持这样的大于小于比较
        if(student1 > student2) {
            
        }
    }

image-20220531234537201

我们可以看见报错信息里面有一个Comparable,这是什么呢,我们查询,就知道了,这是个构造器

image-20220531233252474

那么接口就会有他的抽象方法

image-20220531233320335

接下来,我们就深入学习构造器

2.6.1 构造器(Comparable 接口)

构造器的使用方法是

  1. 在类的后面加上implements Comparable<T>T代表的是比较对象(即前面的类名)

    class Student implements Comparable<student> {
        
    }
    
  2. 要重写comparaTo方法

    我们ctrl+单击Comparable这个接口

    image-20220531233741618

    往下翻,我们会发现Comparable有个抽象方法comparaTo,这代表我们继承Comparable接口就需要重写comparaTo这个方法

    image-20220531233824753

那么,针对于刚才的例子,我们现在把类实现了构造类接口,并把里面的方法重写了

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        
    }
}

现在,假如我们需要比较学生的年龄,我们就可以重写compareTo方法

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //返回大于0的数表示this.age>o.age
        //返回小于0的数表示o.age>this.age
        //返回等于0的数表示this.age = o.age
        return this.age - o.age;
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("叶澜依",20,98.5);
        students[1] = new Student("齐妃",39,79.4);
        students[2] = new Student("华妃",19,89.4);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220531235649374

如果我们想要实现从大到小,那我们可以return o.age-this.age

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return o.age - this.age;
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("叶澜依",20,98.5);
        students[1] = new Student("齐妃",39,79.4);
        students[2] = new Student("华妃",19,89.4);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220531235836356

当我们只有两个对象比较的时候,我们还可以这么写

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        Student student1 = new Student("张三",20,39);
        Student student2 = new Student("李四",30,49);

        if(student1.compareTo(student2) > 0) {
            System.out.println(student1.name + "年龄大");
        } else {
            System.out.println(student2.name + "年龄大");
        }
    }
}

image-20220601000116826

小猫分隔符

那么比较姓名以及比较分数同理,就不再详细讲解了

比较姓名

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);//字符串的比较要用compareTo方法
    }
}

public class TestDemo1 {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan",20,98.5);
        students[1] = new Student("lisi",39,79.4);
        students[2] = new Student("wangwu",19,89.4);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220601000900658

比较成绩

class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return (int)(this.score - o.score);//注意要强转成int,因为compareTo的源码就是int类型的
    }
}

public class TestDemo1 {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan",20,98.5);
        students[1] = new Student("lisi",39,79.4);
        students[2] = new Student("wangwu",19,89.4);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220601000933828

如果我们比较的成绩假如都是98.x这种的,就会排序失败

image-20220601121058540

我们就需要对方法重写里面实现的逻辑进行更改

import java.util.Arrays;

class Student implements Comparable<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 +
                '}';
    }

    /*@Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }*/

    /*@Override
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);//字符串的比较要用compareTo方法
    }*/

    @Override
    public int compareTo(Student o) {
        if(this.score - o.score > 0) {
            return 1;
        } else if(this.score - o.score < 0) {
            return -1;
        } else {
            return 0;
        }
        //return (int)(this.score - o.score);//注意要强转成int
    }
}

public class TestDemo1 {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan",20,98.5);
        students[1] = new Student("lisi",39,98.4);
        students[2] = new Student("wangwu",19,98.6);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

image-20220601121206072

要注意的是,构造器只能同时比较一种对象,比如比较成绩的时候就比较不了年龄,那么我们想要既比较成绩,又比较年龄的话,那么我们就需要使用到比较器

2.6.2 比较器(Comparator 接口)

比较器的使用方法是:

定义一个类,在类的后面加上implements Comparator<T>T为要比较的类

class AgeComparator implements Comparator<Student> {
    
}

我们ctrl+单击 Comparator可以看见他的抽象方法

image-20220601093522223

image-20220601093635817

所以,我们重写之后的代码是这样的

import java.util.Arrays;
import java.util.Comparator;

class Student2 {
    public String name;
    public int age;
    public double score;

    public Student2(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

class AgeComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        return o1.age - o2.age;
    }
}

然后在main方法,我们需要创建一个比较器对象,通过Arrays.sort(要排序的数组,比较器)进行比较(Arrays.sort可以有第二个参数的)

public class TestDemo2 {
    public static void main(String[] args) {
        Student2[] student2 = new Student2[3];
        student2[0] = new Student2("zhangsan",20,87.6);
        student2[1] = new Student2("lisi",30,67.5);
        student2[2] = new Student2("wangwu",40,98.3);

        //创建一个比较器对象
        AgeComparator ageComparator = new AgeComparator();
        //Arrays.sort也可以有两个参数,一个是要排序的数组,一个是比较器
        Arrays.sort(student2,ageComparator);

        System.out.println(Arrays.toString(student2));
    }
}

image-20220601094801290

那么这样,我们还可以创建成绩比较器、姓名比较器,只需要针对不同的比较实现不同的重写方法即可

import java.util.Arrays;
import java.util.Comparator;

class Student2 {
    public String name;
    public int age;
    public double score;

    public Student2(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

class AgeComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        return o1.age - o2.age;
    }
}

class ScoreComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        return (int)(o1.score - o2.score);
    }

}

class NameComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        return o1.name.compareTo(o2.name);
    }
}

public class TestDemo2 {
    public static void main(String[] args) {
        Student2[] student2 = new Student2[3];
        student2[0] = new Student2("zhangsan",20,87.6);
        student2[1] = new Student2("lisi",30,67.5);
        student2[2] = new Student2("wangwu",40,98.3);

        //AgeComparator ageComparator = new AgeComparator();
        //ScoreComparator scoreComparator = new ScoreComparator();
        NameComparator nameComparator = new NameComparator();

        //Arrays.sort(student2,ageComparator);
        //Arrays.sort(student2,scoreComparator);
        Arrays.sort(student2,nameComparator);

        System.out.println(Arrays.toString(student2));
    }
}

运行截图:

image-20220601100031858

image-20220601100155610

2.6.3 Clonable接口和深拷贝

在讲解这个知识点之前,我们先引出个例子

class Person {
    public int id = 1234;

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
       Person person1 = new Person();
    }
}

我们来画一下他的内存分配图

image-20220601225633746

现在我们想拷贝这个引用所指向的对象,就需要进行克隆

class Person {
    public int id = 1234;

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
       Person person1 = new Person();
       Person person2 = person1.clone();//克隆必须调用克隆方法
       //Person person2 = new Person();//这样是不行的
    }
}

image-20220601225903490

那么接下来我来给大家介绍一下满足克隆需要两个前提:

  1. 这个对象是可以被克隆的

    自定义类型想要克隆,类名后面需要加上implements Cloneable

    那么我们加上之后,

    class Person implements Cloneable {
        public int id = 1234;
    
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    '}';
        }
    }
    
    

    image-20220601230208047

    那么我们就看一看Cloneable到底是怎么回事,ctrl+单击 Cloneable,我们可以发现里面有一个空接口

    image-20220601230328030

    这个接口里面只有一个空接口,没有任何一个方法。这个空接口(也叫标记接口)代表当前类可以被克隆,但是目前还不能被克隆

    image-20220601230351379

    我们还需要在Person类里面重写Objectclone方法

  2. 重写Object的克隆方法

    image-20220601230717374

    image-20220601230746437

    image-20220601230826035

    image-20220601231026413

    image-20220601231109911

    image-20220601231206674

    我们还要注意的是,代码自动为我们抛出了异常,我们暂时不需要去关注,知道有这么一回事就行了

    image-20220604202233734

    代码如下:

    class Person implements Cloneable {
        public int id = 1234;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();//调用父类的clone方法->帮助我们克隆
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    '}';
        }
    }
    

    那么这时候,我们再去.,就可以了

    image-20220601231426813

    那么怎么解决报错呢?

    image-20220601231608835

    image-20220601231632577

    image-20220601231712022

    这里大家就记下来吧

    image-20220601232034062

    class Person implements Cloneable {
        public int id = 1234;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    '}';
        }
    }
    
    public class TestDemo1 {
        public static void main(String[] args) throws CloneNotSupportedException {
           Person person1 = new Person();
           Person person2 = (Person)person1.clone();
    
            System.out.println(person2);
        }
    }
    
    

    运行结果:

    image-20220601232158668

    内存分配图:

    image-20220601232554574

博客分隔符-笑脸

那么我们上面讲解的是基本数据类型,那涉及到引用数据类型呢?怎么解决

我们来对刚才的那个类进行修改

class Money {
    public double money = 19.9;
}
class Person implements Cloneable {
    public int id = 1234;
    public Money m = new Money();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}

public class TestDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person)person1.clone();

        System.out.println(person1.m.money);
        System.out.println(person2.m.money);

        System.out.println("------------------");

        person2.m.money = 99.9;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

我们新增加了一个Money类,作为Person类的一个数据成员,这就代表了Money类还包括了引用成员。在通过clone之后,我们先来看一下是否克隆成功以及person1 person2都有多少钱,这时候我们修改Person2的钱,再去打印他们俩的钱

image-20220601233406778

这其实就是浅拷贝,我们画一下内存分配图大家就理解了

image-20220601234103189

其实就是调用clone方法,把中间部分照抄一遍,发现里面的引用变量m里面的地址没换,还是0x55,还是连接到了Person1money上了

小猫分隔符

那么接下来,给大家讲解一下深拷贝。

深拷贝的目的就是:

Person2.m.money = 99.9,让这条语句只能去修改克隆出来的person2,而不去修改Person1

要想达到深拷贝,我们也需要把money(最右边的圈)拷贝一份,所以我们也需要让Money类去继承Cloneable接口

class Money implements Cloneable {
    public double money = 19.9;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

那么我们运行程序,发现还是浅拷贝

image-20220602094851656

image-20220604204639778

因为Person类里面的clone方法返回的还是super.clone(),我们需要在这里改变一下返回的策略

@Override
    protected Object clone() throws CloneNotSupportedException {
        Person tmp = (Person)super.clone();//先把拷贝出来的放到一个tmp中(把中间的方框先保存到tmp中)
        tmp.m = (Money)this.m.clone();//中间方框里面的框框以及后面的圆圈也拷贝到tmp中
        return tmp;//返回tmp这个临时的空间,用person2接收
    }

整个代码如下:

class Money implements Cloneable {
    public double money = 19.9;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable {
    public int id = 1234;
    public Money m = new Money();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person tmp = (Person)super.clone();//先把拷贝出来的放到一个tmp中(把中间的方框先保存到tmp中)
        tmp.m = (Money)this.m.clone();//中间方框里面的框框以及后面的圆圈也拷贝到tmp中
        return tmp;//返回tmp这个临时的空间,用person2接收
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}

public class TestDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person)person1.clone();

        System.out.println(person1.m.money);
        System.out.println(person2.m.money);

        System.out.println("------------------");

        person2.m.money = 99.9;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

内存分配如下:

无标题2

那么我们来分析:深拷贝,是从代码层次上来实现的,而不是说某个方法是深拷贝

要想达到深拷贝,那么每个对象中,如果有引用的话,那么这个对象的引用所指的对象也需要进行拷贝。

拷贝完成之后,通过这个引用修改其指向的数据,原来的不会发生改变,这就是深拷贝

深拷贝要考虑的两个方向:

  1. 要拷贝的数据类型(简单类型)
  2. 拷贝的代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加勒比海涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值