【Java】抽象类和接口

抽象类

抽象类的概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:
在这里插入图片描述
这里的车就可以看作一个抽象类,他没有足够的信息来描绘一个具体的对象

公交车 大卡车 摩托车 小轿车都是车 所以他们之间是继承关系

在这里插入图片描述
像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)

抽象类语法

在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

public abstract class Shape {
    public abstract void draw();
    //抽象方法不能写具体的实现
    public static void area(){
        System.out.println("面积");
    }
    //抽象类也是类 内部可以定义普通方法和属性甚至是构造方法
}

抽象类的注意事项

  1. 抽象类和抽象方法都是使用abstract修饰的
  2. 抽象类不能进行实例化 但是普通类可以
  3. 抽象类中不一定包含抽象方法,但包含抽象方法的类 一定是抽象类
  4. 抽象类中可以定义成员变量和成员方法、
  5. 当一个普通类继承了抽象类 此时再普通类中必须重写抽象类中的抽象方法
  6. 抽象类最大的意义就是被继承
  7. 当一个抽象类B继承一个抽象类A,此时又有一个普通类C继承抽象类B,此时在C中必须重写抽象类A和B中所有的抽象方法
  8. 满足重写的要求
  9. 抽象类中可以存在构造方法,在子类实例化的的时候,会帮助父类成员进行初始化

抽象类的作用

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.

但是有一个疑问 普通的类也可以被继承 普通的方法也可以被重写 为什么必须使用抽象类和抽想法呢???

确实如此. 但是使用抽象类相当于多了一重编译器的校验

使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.

接口

接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备
通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口

public interface 接口名称{
// 抽象方法
	public abstract void method1(); // public abstract 是固定搭配,可以不写
	public void method2();
	abstract void method3();
	void method4();
	// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}

接口使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。

public class 类名称 implements 接口名称{
	// ...
}

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

举个例子

public interface USB {
    void openDevice();
    void closeDevice();
}//接口

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

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }
    public void click(){
        System.out.println("鼠标点击");
    }
}//鼠标类

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

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }
    public void inPut(){
        System.out.println("键盘输入");
    }
}//键盘类

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

    public void powerOff() {
        System.out.println("关闭电脑");
    }

    public void usbDevice(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 TestComputer {
    public static void main(String[] args) {
        Computer computer = new Computer();

        computer.powerOn();
        System.out.println("==========================");

        //使用鼠标设备
        computer.usbDevice(new Mouse());

        System.out.println("==========================");
        //使用键盘设备
        computer.usbDevice(new keyBoard());
        System.out.println("==========================");

        computer.powerOff();
    }
}//测试类

运行结果
在这里插入图片描述

接口注意

接口其实就是对一个标准的规范 可以看作是抽象类的进一步抽象

  1. 接口是interface定义的
  2. 接口中不可以有被实现的方法,但是有两种方法需要注意 一个是静态方法可以被实现 另一个是default关键字修饰的也可以被实现
  3. 接口中的方法默认是public abstract修饰的
  4. 接口中的成员变量默认是 public static final修饰的
  5. 接口也不可以通过new实例化
  6. 类和接口之间用关键字implements进行关联 看作是类实现了接口
  7. 当一个类实现一个接口后 这个类必须重写接口中的抽象方法
  8. 当借口中存在default方法 可以选择重写 也可以不重写
  9. 不管是接口还是抽象类 仍然可以发生向上转型
  10. 子类实现接口方法时 方法一定是被public修饰的
  11. 接口中不能有构造方法和代码块
  12. 一个类如果不想实现接口的方法可以定义为抽象类

实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物.

public abstract class Animals {
    protected String name;

    public Animals(String name) {
        this.name = name;
    }
}//父类
public interface IFlying {
    void fly();
}
public interface ISwimming {
    void swim();
}
public interface IRunning {
    void run();
}//接口们
public class Cat extends Animals implements IRunning{//猫是一个动物且会跑
    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("猫正在用四条腿跑步");
    }
}
public class Fish extends Animals implements ISwimming{//鱼是一个动物且会游泳
    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println("鱼正在游泳");
    }
}
public class Dog extends Animals implements ISwimming,IRunning{//狗是一个动物且会游泳和跑步
    @Override
    public void swim() {
        System.out.println("狗正在狗刨式游泳");
    }

    @Override
    public void run() {
        System.out.println("狗正在用四条腿跑");
    }

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

接口间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

public interface IAmphibious extends IRunning,ISwimming {
//两栖类接口继承跑和游泳的接口
}

//此时狗这个方法就可以不用实现游泳和跑的接口 直接实现两栖类的接口即可
public class Dog extends Animals implements IAmphibious{
    @Override
    public void swim() {
        System.out.println("狗正在狗刨式游泳");
    }

    @Override
    public void run() {
        System.out.println("狗正在用四条腿跑");
    }

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

接口使用实例

给对象数组排序

public class Student {//学生类
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

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


public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhansgan",85);
        students[1] = new Student("lisi",70);
        students[2] = new Student("wangwu",90);
        Arrays.sort(students);//这里能够给数组排序吗?
    }
}

我们可以想到这样排序是不现实的 因为我们并没有告诉编译器我们应该是按照名字排序还是按照成绩来排序 所以我们预测会抛出异常

运行结果
在这里插入图片描述
解决办法就是让Student类实现 Comparable接口 并实现其中的compareTo方法

public class Student implements Comparable{//实现Comparable接口
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

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

    @Override
    public int compareTo(Object o) {
        Student s = (Student)o;
//        if (this.score > s.score) {
//            return -1;
//        } else if (this.score < s.score) {
//            return 1;
//        } else {
//            return 0;
//        }
//可以使用注释掉的if else方法也可以使用下面的return返回的方法
        return s.score - this.score;
    }
}

运行结果
在这里插入图片描述
可以看到确实以成绩进行了排序

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.然后比较当前对象和参数对象的大小关系(按分数来算).

为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)

public class Test {
    public static void sort(Student[] students) {
        for (int i = 0; i < students.length - 1; i++) {
            for (int j = 0; j < students.length - 1 - i; j++) {
                if (students[j].compareTo(students[j+1]) > 0){
                //这里是student[j]调用的compareTo方法所以this.就是student[j];
                //参数是student[j + 1] 所以Object o接收的就是student[j + 1]
                    Student temp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhansgan", 85);
        students[1] = new Student("lisi", 70);
        students[2] = new Student("wangwu", 90);
        sort(students);//调用自己实现的冒泡排序
        System.out.println(Arrays.toString(students));
    }
}

Clonable 接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一.

Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要
先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.

public class Student {//实现一个学生类
    private String name ;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

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

public class test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan",85);
        Student student2 = student1.clone();//这里会报错
    }
}

我们写出上述代码发现会报错
在这里插入图片描述
编译器提示clone方法是被protected修饰的
我们可以跳转到clone方法的实现
在这里插入图片描述
protected修饰的在不同包中的子类去访问通常要使用super
我们在这里可以重写这个方法
在这里插入图片描述
编译器默认生成了这段代码 他其实什么都没有干 只是调用了父类的clone方法
在这里插入图片描述
此时就是异常的问题了
在这里插入图片描述
我们只需要把异常写到main方法后面就可以解决
在这里插入图片描述
此时报错信息变为类型的问题 编译器提示我们这里需要一个Student类型但是我们提供了一个Object的类型 我们只需要强制类型转换
在这里插入图片描述
我们发现这里没有报错信息表示可以运行
在这里插入图片描述
当我们运行时发现又报错了 提示我们克隆不支持异常
在这里插入图片描述
我们只需要实现Cloneable接口 这个接口又是什么呢?
在这里插入图片描述
我们可以看到这个接口什么也没有 这叫做标记接口
当我们实现了这个接口后 发现没有报错了

public class Student implements Cloneable{
    private String name ;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student("zhangsan", 85);
        Student student2 = (Student) student1.clone();
        System.out.println(student1.toString());
        System.out.println(student2.toString());
    }
}

在这里插入图片描述
运行结果
在这里插入图片描述

浅拷贝

我们在原有代码的基础上新增一个Money类 并在Student中实例化

我们想让student2在克隆student1时将Money类实例化的也克隆进去

我们首先想到刚才的操作 第一步实现Cloneable接口 第二步重写clone方法

class Money implements Cloneable{
    public int money;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Student implements Cloneable {
    private String name;
    private int score;

    public Money m = new Money();

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

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

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

public class testStudent {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student("zhangsan",85);
        Student student2 = (Student) student1.clone();
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
        student1.m.money = 55;
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
    }
}

运行结果
在这里插入图片描述
我们发现在修改student1时 student2也发生了改变 这明显是不符合逻辑的
**重新思考我们的代码 我们只是给Money做了克隆的前置任务 实际上并没有克隆 **
在这里插入图片描述
如图所示 我们只是复制了Student 中的所有 包括我们实例化Money的地址 意味着即使我们复制后student2也是保存的同一个m的地址 所以我们改变student1的m 通过student2调用m调用的是同一个m 就出现了运行时的结果 这也就是浅拷贝

深拷贝

通过对浅拷贝的认识 深拷贝的理解也就很简单

还是上面这个例子 我们不想要student1和student2都调用同一个m 该如何实现

我们只需要在上面那个代码重写clone方法做一点改动

改动前
在这里插入图片描述

改动后

在这里插入图片描述
在这里插入图片描述
这样就是深拷贝 将所有类全部复制一份

抽象类和接口的区别

在这里插入图片描述
主要认清抽象类和接口的意义

抽象类表示是什么 例如猫属于动物 大卡车属于车

接口表示什么会干什么 例如猫会跑 狗既会跑也会游泳

这也可以体现出为什么只能单继承 但是却可以实现多个接口 因为一个类一定不是只会干一种事情 抽象类的意义就是被继承

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沉着的码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值