硅基计划2.0 学习总结 陆 抽象类与接口
文章目录
- 一、抽象类
- 1. 定义
- 2. 示例代码
- 3. 特性
- 二、接口初识
- 1. 定义
- 2. 命名与语法
- 3. 示例代码
- 4. 常见特性
- 5. 多接口实现
- 6. 接口的继承
- 三、Object类初识
- 1. equals方法
- 2. hascode方法
一、抽象类
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;}@Overridevoid 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;}@Overridevoid 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. 特性
- 可以和普通的类一样拥有成员变量和成员方法,不一定要有抽象方法,但是反之一个类中有抽象方法那么这个类必须为抽象类
- 抽象类不可以被实例化,但是它可以被继承,而且它的主要目的就是继承
- 当一个普通的类继承抽象类之后,这个普通类需要去重写抽象类中所有的抽象方法,不过若你不想自己手动写,直接
Alt+回车
查看,然后点击重写选项即可
- 可以有构造方法,跟普通方法一样,通过子类去初始化抽象类(父类)中的成员变量,因此抽象类中成员变量和构造方法功能与普通的父类近乎没有差别,刚刚的示例代码中就有
- 抽象类也可以发生动态绑定、向上转型、多态等,比如示例代码的Test测试类中的func方法
- 因抽象类之间的可继承性,若抽象类B继承了抽象类A,那么在抽象类B中可以不用重写抽象类A中的抽象方法,但是当一个普通类C去继承抽象类B的时候,不光要重写抽象类B中的抽象方法,还要重写抽象类A中的抽象方法,因为普通类C间接继承了抽象类A
- 抽象方法不能被
private,final,static
等关键字修饰,因为这样会导致不可被继承,进而就不能在子类中重写抽象方法了
Tips:如果你细心,你会发现抽象类的图标和普通类中的图标不一样
二、接口初识
1. 定义
- 其实我也不好描述,你就把它理解为多个类的公共准则,即让类具有某些属性,一种引用的数据类型的规范,因此它可被多个类引用,就决定类其高度公有性与多个类之间共性
- 跟抽象类一样,接口类指的是被interface关键字修饰的
- 成员变量默认都是被
public static final
修饰,当你写入权限限定符时会显示是冗余的,并且在定义的时候还需要初始化,这一点跟类不一样
- 成员方法默认都是被
public abstract
,即都是抽象方法,因此接口类方法中不能有方法的具体定义,但是,凡事都有特殊情况,如果被static
关键字或defeault
关键字修饰后就可以
- 接口跟抽象类一样,它不可以实例化
2. 命名与语法
- 接口内部有一套新的语法体系,命名一般是大写字母I+形容词
- 内部方法与成员变量不能添加任何修饰符比如public、private等
3. 示例代码
- 我们这里就以我们之前写过的学生类的代码做个演示,整体思路就是借助
implement
关键字去实现类,并且去重写接口中所有抽象方法
//首先我们定义一个接口IPerosn
public interface IPerson {void Attendclass();//定义上课void Classisover();//定义下课
}
//其次我们再定义两个子类Teacher和Student
//重写接口中的抽象方法,使其上课和下课的方式各不同
public class Student implements IPerson{@Overridepublic void Attendclass() {System.out.println("学生在上课");}@Overridepublic void Classisover() {System.out.println("学生下课离开了");}void note(){//定义学生特有方法System.out.println("记笔记");}
}public class Teacher implements IPerson{@Overridepublic void Attendclass() {System.out.println("老师在上课");}@Overridepublic 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();}
}
我来解释下这些代码,以便让你更加明白
- 首先我们在接口中定义两个需要在实现类中重写的两个抽象方法,仅仅声明,并无具体实现方法
- 我们再定义两个类
Student
和Teacher
,来实现接口,再重写接口中的抽象方法,做后我们分别定义了当前类中特有方法note()
和blackboard()
- 我们再定义一个教室类
Classroom
去整合两个类Student
和Teacher
,在教室类Classroom
中先定义两个特有方法openDoor()
和closeDoor()
,再定义一个方法实现当前类Classroom
和其他两个类Student
和Teacher
的协作方法learn
,虽然接口不可实例化,但是它也可以引用传参,因此参数列表我们写IPerson person
- 此时你想,如果我用向上转型,是不是只能调用被重写的方法,不能调用类中特有的方法(因为你形参是
IPerson person
,接口引用去引用类对象),那怎么办呢,还记得向下转型吗,是的,我们就用向下转型去访问,但是不是说向下转型有风险吗,你可以看到在转型前我们先是会检查这个类实例化的是谁,即这个接口类引用到底引用的是哪个类,是Student
还是Teacher
呢,检查完之后我们就可以用向下转型去访问类中特有的方法了- 最后我们再定义个测试类
Test
,去new了三个对象,再把Student
类和Teacher
类的对象传给Classroom
类中的协作方法,这样我们就完成了接口的实现,是不是很神奇呢,别介,后面还有终极大招等着
4. 常见特性
- 在重写接口方法时候,不可使用默认的权限,要满足重写权限>=原抽象方法,但是呢又因为接口中方法默认是public修饰的,因此你重写后只能用public修饰了,这也能理解,接口毕竟就是为了对外嘛
- 之前也说过,在接口中也可以定义变量,一般被隐式指定为
public static final
- 接口中不可以有代码块类型,譬如静态代码块、构造代码块、普通代码块等
- 接口中不可以有构造方法,毕竟你接口中的变量都要定义的时候就初始化,还要构造方法干嘛
- 如果你的类没有重写接口中的所有抽象方法,那你当前的类就要设置为抽象类
- 即使接口并不是class类,但是编译后还是能产生class文件
- 在JDK8之后,可以在接口中定义被default修饰的方法了
5. 多接口实现
- 原理:接口间也满足继承的关系
- 痛点分析:继承有个什么痛点,就拿我们之前写过的Person类来举例,我们把Person定义为抽象类,我们写了个抽象方法睡觉,也写了成员变量name,好,我们让两个类
Student
和Teacher
类继承了这个抽象类,重写了sleep()
抽象方法,之后再在测试类中去调用即可 - 但是你想,我们可以把
Teacher
类和Student
类中的特有方法写在抽象类中吗,不可以,学生上课记笔记老师上课一般会记笔记吗,不会,一般是教书,那你如果写进抽象类中,就会导致在Student
类和Tracher
类中去重写这两个方法,虽然语法上可以,但是逻辑上不可 - 诶,你是不是想到了把各个特有的方法定义成一个类,再让
Teacher
类和Student
类去继承呗,很遗憾,Java不支持多继承,怎么办呢!!!
解决方法:创建多个接口去实现
- 我们把特有方法定义在接口中,再让
Teacher
类和Student
类去实现 - 因此我们可以把记笔记这个特有方法“封装”在一个个的接口,如果想实现多个接口,那么各个接口之间用逗号隔开即可,此时我们实现特定接口,我们再重写接口中的方法即可
- 同时再在测试类中定义两个特有方法的接口
public static void noting(INoteable noteable)
和public static void blackborading(IBlackboardable blackboardable)
,此时我们再把对应的对象传给特定的方法,此时你会惊奇的发现,我们无需关心对象到底是什么类型,我们只关心他能做什么,能干什么就好,只要他具备了这些特定方法的功能,我们都可以去调用,不论你是什么类型,比如不管你是学生还是老师,只要你会记笔记,你直接调用特定方法即可,这样就相当于给外界创建了个接口,只要你能连接上,甭管你是什么设备,都可以调用这个接口而无需知道其具体实现内容 - 以下是示例代码,我们新定义了两个具有特有抽象类方法的接口
INoteable
和IBlackboardable
,在Test测试类中,我们定义了两个配合接口的特有方法noting
,blackborading
,我们直接传具有各个特有方法的能力的对象就好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);}
}
6. 接口的继承
之前我们就讲过接口是可以继承的,具体是怎么样的呢?说白了就是接口的扩展
- 比如有一名优秀的学生,他既会讲课又会记笔记,那我们是不是可以再定义一个接口让其具有这两个接口的功能,因此我们用手
extends
关键字,在刚刚代码中我们要写两个接口的实现,此时我们只需要写一个接口的实现就好了,之后就是跟之前实现接口一样 - 此时你会发现因为这个接口兼并了其他接口,因此在子类中实现的时候需要重写所有被继承接口的抽象方法,但是若这个兼并的接口里面还是有其他抽象方法,你还是要在接口实现的类中去重写这个兼并接口中的抽象方法
- 代码展示
//定义了IExcellent接口,暂时不写IExcellen类中特有抽象方法
public interface IExcellent extends INoteable,IBlackboardable{
}
//定义了ExcleentStudent方法,可以发现重写了继承来的所有抽象方法
public class ExcleentStudent implements IExcellent {@Overridepublic void blackboard() {System.out.println("优秀学生在讲课");}@Overridepublic 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);}
三、Object类初识
-
我们之前说过,object类是所有类的父类,因此类中有些方法默认继承了Object中的方法
-
我们可以通过双击shift键输入
Object
来查看,点击左上角结构(快捷键Alt+7)查看我们Object
类中所有方法
-
那既然它是所有类的父类,那么说明其是万能的类型,比如之前的代码
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,为什么,你转到源码去看看
你发现返回的就过是当前对象,这不还是跟System.out.println(per1==per3);
没有半毛钱区别吗,所以我们需要对equals方法进行重写,我们以后也会经常碰到需要重写的方法
-
我们可以右键让编译器重写
-
也可我们自己重写,这边演示我们自己重写,代码有注释,应该可以看懂
@Overridepublic 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;}
当然也可以简化下代码
@Overridepublic 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进行重写操作,我们直接右键生成,此时返回的是散列码进行比较了,发现其散列码就一样了
- 你是否会好奇为啥我通过对象直接使用
.
去访问equals和hashcode方法呢,还记不记得之前说object类是所有类的父类,因此我们默认继承了object类这些方法,难道我们不能访问吗,显然可以访问,毕竟每个类对位object的子类
这里补充一点:我们传对象时候可以直接传new的对象或者是定义一个传对象的数组去传
func(new Dog());
func(new horse());
Person [] person = {new Student(),new Teacher()};