硅基计划2.0 学习总结 陆 抽象类与接口

图 (878)



一、抽象类

1. 定义

  • 请你假设这样一个场景,我们定义一个人的类,这个类中我们定义一个成员方法void sleep(),然后我们派生出两个子类Student()类和Teacher()类,然后再在子类中写出各自的sleep()方法,也能实现多态
  • 但是,今天我们要讲的是抽象类和抽象方法,因此在语法上,抽象类指的是被abstract关键字修饰的类,进而抽象方法就指的是被abstract关键字修饰的方法
  • 在抽象类和抽象方法中,没有具体的实现,在人的类中,我们描述这个人的类很虚幻,很宽泛,因而我们的abstract void sleep();方法中并无具体的内容,就单纯是个方法的展示,说明人具有睡觉的习性,而未定义具体怎么睡的
  • 那你可能就会问了,那我在此处不定义,难道在子类中再重新定义吗(即重写),是的,我们创建抽象方法的意义就是为了在子类中重写
  • 那聪明的你也会问了,我都有继承了我为什么还要定义抽象类呢,其实这样主要是为了在子类继承父类的时候,检查你是否有重写父类中的方法,便于校验,以免造成遗漏,而且这样规范性也更高了,进一步提高了代码的稳定性

2. 示例代码

//抽象类Person
public abstract class Person {
    String name;

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

    abstract void sleep();
}
//Student类
public class Student extends Person{
    String classes;
    String stage;

    public Student(String name, String classes,String stage) {
        super(name);
        this.classes = classes;
        this.stage = stage;
    }

    @Override
    void sleep() {
        System.out.println(name+classes+"学生在睡觉");
    }
}
//Teacher类
public class Teacher extends Person{
    String department;
    String subject;

    public Teacher(String name,String department, String subject) {
        super(name);
        this.department = department;
        this.subject = subject;
    }

    @Override
    void sleep() {
        System.out.println(name+department+"老师在睡觉");
    }
}
//Test测试类
public class Test {

    public static void func(Person person){
        person.sleep();
    }

    public static void main(String[] args) {
        Person per1 = new Student("张三","计科二班","大一");
        Person per2 = new Teacher("王五","教务处","计算机科学与技术");
        func(per1);
        func(per2);
    }
}

3. 特性

  1. 可以和普通的类一样拥有成员变量和成员方法,不一定要有抽象方法,但是反之一个类中有抽象方法那么这个类必须为抽象类
    image-20250606153842693
  2. 抽象类不可以被实例化,但是它可以被继承,而且它的主要目的就是继承
  3. 当一个普通的类继承抽象类之后,这个普通类需要去重写抽象类中所有的抽象方法,不过若你不想自己手动写,直接Alt+回车查看,然后点击重写选项即可
    image-20250606154147307
  4. 可以有构造方法,跟普通方法一样,通过子类去初始化抽象类(父类)中的成员变量,因此抽象类中成员变量和构造方法功能与普通的父类近乎没有差别,刚刚的示例代码中就有
  5. 抽象类也可以发生动态绑定、向上转型、多态等,比如示例代码的Test测试类中的func方法
  6. 因抽象类之间的可继承性,若抽象类B继承了抽象类A,那么在抽象类B中可以不用重写抽象类A中的抽象方法,但是当一个普通类C去继承抽象类B的时候,不光要重写抽象类B中的抽象方法,还要重写抽象类A中的抽象方法,因为普通类C间接继承了抽象类A
  7. 抽象方法不能被private,final,static等关键字修饰,因为这样会导致不可被继承,进而就不能在子类中重写抽象方法了

Tips:如果你细心,你会发现抽象类的图标和普通类中的图标不一样


二、接口初识

1. 定义

  • 其实我也不好描述,你就把它理解为多个类的公共准则,即让类具有某些属性,一种引用的数据类型的规范,因此它可被多个类引用,就决定类其高度公有性与多个类之间共性
  1. 跟抽象类一样,接口类指的是被interface关键字修饰的
  2. 成员变量默认都是被public static final修饰,当你写入权限限定符时会显示是冗余的,并且在定义的时候还需要初始化,这一点跟类不一样
    image-20250606164402153
  3. 成员方法默认都是被public abstract,即都是抽象方法,因此接口类方法中不能有方法的具体定义,但是,凡事都有特殊情况,如果被static关键字或defeault关键字修饰后就可以
    image-20250606165030556
  4. 接口跟抽象类一样,它不可以实例化
    image-20250606165127124

2. 命名与语法

  • 接口内部有一套新的语法体系,命名一般是大写字母I+形容词
  • 内部方法与成员变量不能添加任何修饰符比如public、private等

3. 示例代码

  • 我们这里就以我们之前写过的学生类的代码做个演示,整体思路就是借助implement关键字去实现类,并且去重写接口中所有抽象方法
//首先我们定义一个接口IPerosn
public interface IPerson {
    void Attendclass();//定义上课
    void Classisover();//定义下课
}
//其次我们再定义两个子类Teacher和Student
//重写接口中的抽象方法,使其上课和下课的方式各不同
public class Student implements IPerson{

    @Override
    public void Attendclass() {
        System.out.println("学生在上课");
    }

    @Override
    public void Classisover() {
        System.out.println("学生下课离开了");
    }

    void note(){//定义学生特有方法
        System.out.println("记笔记");
    }
}

public class Teacher implements IPerson{
    @Override
    public void Attendclass() {
        System.out.println("老师在上课");
    }

    @Override
    public void Classisover() {
        System.out.println("老师下课离开了");
    }

    void blackboard(){//定义老师特有方法
        System.out.println("粉笔写字");
    }
}
//再定义一个教室类Classroom用于把两个子类集中起来,一边协作
public class Classroom {//这个大类把两个子类集中起来
    void openDoor(){
        System.out.println("打开教室门");
    }

    void closeDoor(){
        System.out.println("关机教室门");
    }

    void learn(IPerson person){//定义协作方法
        person.Attendclass();
        if(person instanceof Student){
            Student stu = (Student) person;
            stu.note();
        }else if(person instanceof Teacher){
            Teacher ter = (Teacher) person;
            ter.blackboard();
        }
        person.Classisover();
    }
}
//我们再定义一个测试类,分别实例化教室类Classroom、学生类Student和老师类Teacher
public class Test {
    public static void main(String[] args) {
        Classroom classroom = new Classroom();
        Student stu = new Student();
        Teacher ter = new Teacher();
        classroom.openDoor();
        classroom.learn(stu);//调用协作方法
        classroom.learn(ter);//调用协作方法
        classroom.closeDoor();
    }
}

我来解释下这些代码,以便让你更加明白

  1. 首先我们在接口中定义两个需要在实现类中重写的两个抽象方法,仅仅声明,并无具体实现方法
  2. 我们再定义两个类StudentTeacher,来实现接口,再重写接口中的抽象方法,做后我们分别定义了当前类中特有方法note()blackboard()
  3. 我们再定义一个教室类Classroom去整合两个类StudentTeacher,在教室类Classroom中先定义两个特有方法openDoor()closeDoor(),再定义一个方法实现当前类Classroom和其他两个类StudentTeacher的协作方法learn,虽然接口不可实例化,但是它也可以引用传参,因此参数列表我们写IPerson person
  4. 此时你想,如果我用向上转型,是不是只能调用被重写的方法,不能调用类中特有的方法(因为你形参是IPerson person,接口引用去引用类对象),那怎么办呢,还记得向下转型吗,是的,我们就用向下转型去访问,但是不是说向下转型有风险吗,你可以看到在转型前我们先是会检查这个类实例化的是谁,即这个接口类引用到底引用的是哪个类,是Student还是Teacher呢,检查完之后我们就可以用向下转型去访问类中特有的方法了
  5. 最后我们再定义个测试类Test,去new了三个对象,再把Student类和Teacher类的对象传给Classroom类中的协作方法,这样我们就完成了接口的实现,是不是很神奇呢,别介,后面还有终极大招等着
    image-20250606173618736

4. 常见特性

  1. 在重写接口方法时候,不可使用默认的权限,要满足重写权限>=原抽象方法,但是呢又因为接口中方法默认是public修饰的,因此你重写后只能用public修饰了,这也能理解,接口毕竟就是为了对外嘛
  2. 之前也说过,在接口中也可以定义变量,一般被隐式指定为public static final
  3. 接口中不可以有代码块类型,譬如静态代码块、构造代码块、普通代码块等
  4. 接口中不可以有构造方法,毕竟你接口中的变量都要定义的时候就初始化,还要构造方法干嘛
  5. 如果你的类没有重写接口中的所有抽象方法,那你当前的类就要设置为抽象类
  6. 即使接口并不是class类,但是编译后还是能产生class文件
  7. 在JDK8之后,可以在接口中定义被default修饰的方法了

5. 多接口实现

  • 原理:接口间也满足继承的关系
  • 痛点分析:继承有个什么痛点,就拿我们之前写过的Person类来举例,我们把Person定义为抽象类,我们写了个抽象方法睡觉,也写了成员变量name,好,我们让两个类StudentTeacher类继承了这个抽象类,重写了sleep()抽象方法,之后再在测试类中去调用即可
  • 但是你想,我们可以把Teacher类和Student类中的特有方法写在抽象类中吗,不可以,学生上课记笔记老师上课一般会记笔记吗,不会,一般是教书,那你如果写进抽象类中,就会导致在Student类和Tracher类中去重写这两个方法,虽然语法上可以,但是逻辑上不可
  • 诶,你是不是想到了把各个特有的方法定义成一个类,再让Teacher类和Student类去继承呗,很遗憾,Java不支持多继承,怎么办呢!!!

解决方法:创建多个接口去实现

  • 我们把特有方法定义在接口中,再让Teacher类和Student类去实现
  • 因此我们可以把记笔记这个特有方法“封装”在一个个的接口,如果想实现多个接口,那么各个接口之间用逗号隔开即可,此时我们实现特定接口,我们再重写接口中的方法即可
  • 同时再在测试类中定义两个特有方法的接口public static void noting(INoteable noteable)public static void blackborading(IBlackboardable blackboardable),此时我们再把对应的对象传给特定的方法,此时你会惊奇的发现,我们无需关心对象到底是什么类型,我们只关心他能做什么,能干什么就好,只要他具备了这些特定方法的功能,我们都可以去调用,不论你是什么类型,比如不管你是学生还是老师,只要你会记笔记,你直接调用特定方法即可,这样就相当于给外界创建了个接口,只要你能连接上,甭管你是什么设备,都可以调用这个接口而无需知道其具体实现内容
  • 以下是示例代码,我们新定义了两个具有特有抽象类方法的接口INoteableIBlackboardable,在Test测试类中,我们定义了两个配合接口的特有方法notingblackborading,我们直接传具有各个特有方法的能力的对象就好noting(stu);
    blackborading(ter);
//其他代码都没变,这里只展示变了的代码
//Noteable接口
public interface INoteable {
    void note();
}
//IBlackboardable接口
public interface IBlackboardable {
    void blackboard();
}
//Test测试类中
public class Test {
    public static void noting(INoteable noteable){
        noteable.note();
    }

    public static void blackborading(IBlackboardable blackboardable){
        blackboardable.blackboard();
    }

    public static void main(String[] args) {//究极大招
        Student stu = new Student();
        Teacher ter = new Teacher();
        noting(stu);
        blackborading(ter);
    }
}

image-20250606184510860

6. 接口的继承

之前我们就讲过接口是可以继承的,具体是怎么样的呢?说白了就是接口的扩展

  1. 比如有一名优秀的学生,他既会讲课又会记笔记,那我们是不是可以再定义一个接口让其具有这两个接口的功能,因此我们用手extends关键字,在刚刚代码中我们要写两个接口的实现,此时我们只需要写一个接口的实现就好了,之后就是跟之前实现接口一样
  2. 此时你会发现因为这个接口兼并了其他接口,因此在子类中实现的时候需要重写所有被继承接口的抽象方法,但是若这个兼并的接口里面还是有其他抽象方法,你还是要在接口实现的类中去重写这个兼并接口中的抽象方法
  3. 代码展示
//定义了IExcellent接口,暂时不写IExcellen类中特有抽象方法
public interface IExcellent extends INoteable,IBlackboardable{
}
//定义了ExcleentStudent方法,可以发现重写了继承来的所有抽象方法
public class ExcleentStudent implements IExcellent {

    @Override
    public void blackboard() {
        System.out.println("优秀学生在讲课");
    }

    @Override
    public void note() {
        System.out.println("优秀学生在记笔记");
    }
}
//我们再在Test测试类中定义一个特殊方法来调用这个接口
public static void excleenting(IExcellent iexcellent){
        iexcellent.blackboard();
        iexcellent.note();
    }
//再在main方法中创建对象,再传对象,大功告成
public static void main(String[] args) {//究极大招
        ExcleentStudent stus = new ExcleentStudent();
        excleenting(stus);
    }

图 (289)


三、Object类初识

  • 我们之前说过,object类是所有类的父类,因此类中有些方法默认继承了Object中的方法

  • 我们可以通过双击shift键输入Object来查看,点击左上角结构(快捷键Alt+7)查看我们Object类中所有方法
    image-20250606212654423

  • 那既然它是所有类的父类,那么说明其是万能的类型,比如之前的代码Student student = new Student();我们就可以改写为Object student = new Student(......);,甚至是int a = 10;改写成object a = 10,既然都这样了为什么不写成object类型呢?

  • 因为其太宽泛了,不好易于区分不同类型数据

1. equals方法

  • 我们给个示例代码,拿我们刚刚演示的代码为例
public static void main(String[] args) {
        Person per1 = new Student("张三","计科二班","大一");
        Person per3 = new Student("张三","计科二班","大一");
        System.out.println(per1==per3);//结果为false
}

为什么结果是false,因为你不同对象即使内容一样,地址却是不一样的,比较的主要是地址,你想我用equals不就行了吗,System.out.println(per1.equals(per3));结果还是false,为什么,你转到源码去看看
image-20250606213755336

你发现返回的就过是当前对象,这不还是跟System.out.println(per1==per3);没有半毛钱区别吗,所以我们需要对equals方法进行重写,我们以后也会经常碰到需要重写的方法

  • 我们可以右键让编译器重写
    image-20250606214358351

  • 也可我们自己重写,这边演示我们自己重写,代码有注释,应该可以看懂

@Override
    public boolean equals(Object obj){
        if(obj == null){
            return false;//如果你传的是空对象直接返回
        }
        if(this == obj){//如果你传的另外个对象是你本身的话
            return true;
        }
        if(!(obj instanceof Person)){//实例化的不是同个类
            return false;
        }
        //此时的情况就是同一个类的情况了,只需要比较内容是否一致就好
        Person temp = (Person) obj;//为什么此时向下转型安全,因为之前已经检查过了
        if(temp.name.equals(this.name)){//假设名字一样就整体相等
            return true;
        }
        return false;
    }

当然也可以简化下代码

@Override
    public boolean equals(Object obj){
        if(obj == null){
            return false;//如果你传的是空对象直接返回
        }
        if(this == obj){//如果你传的另外个对象是你本身的话
            return true;
        }
        if(!(obj instanceof Person)){//实例化的不是同个类
            return false;
        }
        //此时的情况就是同一个类的情况了,只需要比较内容是否一致就好
        Person temp = (Person) obj;//为什么此时向下转型安全,因为之前已经检查过了
        return temp.name.equals(this.name);//简化了这里
    }

2. hascode方法

用于在散列表中获取位置,底层是用C/C++写的,看不到具体实现方法,但是如果你直接用object类的会有问题,通过per1.hashcode()per3.hashcode()不一样,因此我们需要对hashcode进行重写操作,我们直接右键生成,此时返回的是散列码进行比较了,发现其散列码就一样了
image-20250606220200749
image-20250606220239340

  • 你是否会好奇为啥我通过对象直接使用.去访问equals和hashcode方法呢,还记不记得之前说object类是所有类的父类,因此我们默认继承了object类这些方法,难道我们不能访问吗,显然可以访问,毕竟每个类对位object的子类

这里补充一点:我们传对象时候可以直接传new的对象或者是定义一个传对象的数组去传

func(new Dog());
func(new horse());
Person [] person = {new Student(),new Teacher()};

文章难免会有错误,欢迎指正


END
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值