一、抽象类
① 抽象类的概念
在上一篇文章中,我们学习了" 多态 ",它允许在相同方法的情况下处理不同的对象,即通过父类引用指向子类对象,并调用相同的方法,通过不同的子类调用该方法,进而产生不同的结果。而抽象类也和" 多态 "有着很大的关联。
📌 抽象类的定义:如果一个类中没有包含足够的信息来描述一个具体的对象,那么这样的类就是抽象类。
📕 抽象类不能被实例化,只能作为其他类的父类或供其他类继承。
📕 抽象类可以包含抽象方法和普通方法。
📚 那么抽象类应该如何使用呢?抽象方法又是什么呢:
通过这个图我们可以看到,Animal的sound()方法被Dog,Cat,Duck继承后,调用方法后结果都各不相同,这也是" 多态 "的体现。但我们注意:
在抽象类的使用下,Animal的sound()方法并不像上篇文章中所学,是有具体实现的,而是一个没有方法体的方法,正因为它没有方法体,所以它才称之为"抽象";而在这张图片中"Animal"中的"sound"方法就是一个抽象方法,而"Animal"就是一个抽象类~
所以我们刚刚说:抽象类也和" 多态 "有着很大的关联。
这是因为多态可以通过抽象类实现;抽象类可以包含抽象方法,这些方法没有具体的实现,只有方法的声明。子类继承抽象类时必须!!!实现抽象方法,这样就可以通过父类引用调用子类的实现。这样一来,同一个方法名可以根据具体的子类对象而有不同的表现形式,实现了多态。
📚注意:(不用抽象类时是"重写",本质是覆盖;而用抽象类时是"实现",两者本质不同)
② 抽象类的实现
当我们想要创建一个抽象类时,这个类就必须要被"abstract"修饰,如此才能被称作抽象类。
📚 那么让我们试着实现一下上面图片中的例子:
📕 抽象类"Animal":
📕 继承"Animal"的子类们:
可以看到,所有继承了抽象类"Animal"的子类们都实现了sound()方法,这是因为:
📚 继承了抽象类的子类必须实现其中的抽象方法,因为(抽象类无法实例化),而继承了抽象类的子类如果不实现抽象方法,便也算是(抽象类),为了防止它被实例化,所以会报错:
可以看到,这时就报错了~
正常运行:
③ 抽象类的特征
📚 抽象类的特征有如下几点:
📕 抽象类不能直接实例化对象:
📕 抽象方法不能是 private 的:
📕 抽象方法不能被 final 和 static 修饰,因为需要被子类实现:
📕 继承抽象类的子类必须重写父类中的抽象方法,否则子类也要是抽象类:
如果子类不重写父类抽象方法:
如果抽象类子类不重写父类抽象方法:
📕 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
📕 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
二、接口
① 接口的概念
接口,这个词大家听起来不陌生吧~接口在我们印象中可能就是:USB接口呀,充电插口呀~之类的。没错,它们都可以叫做接口,而且它们都有一种统一的性质:
📕 智能产品可以通过USB接口,来开启信号的权限,就相当于被赋予某种功能(信号)
📕 家用电器可以通过电源插孔,开开启充电的权限,就相当于被赋予某种功能(充电)
由此可以得出结论:
📚 接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
② 接口的实现
接口的定义格式与定义类的格式基本相同
将class关键字换成 interface 关键字,就定义了一个接口。
📚 代码格式:
java">public interface 接口名称{// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();abstract void method3();void method4();
}
需要注意的是,接口里面的方法也都是待实现的抽象方法;
接口中可以声明方法,但是不能包含方法的具体实现。接口中的方法默认为public abstract,可以省略这些修饰符。接口也可以包含常量的声明,这些常量默认为public static final,同样可以省略这些修饰符。
📚 代码实例:
📌 "接口"的相关知识还是比较模糊的,既然学到了,就让我们做一个小练习来试一试:
📚 实现笔记本电脑使用USB鼠标、USB键盘的例子:
1. USB接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
3. 鼠标类:实现USB接口,并具备点击功能
4. 键盘类:实现USB接口,并具备输入功能
其实大部分功能都是可以通过之前所学到的基础知识"if","else","switch"等选择语句去实现的,但是使用"接口",会使代码更加具有规范性和可读性,可修改性,是我们学习的必经之路,并且相当重要~
让我们先来仔细地思考一下,这些东西应该如何去一一实现:
📕 理清这几种类与接口的关系:
📕 然后按照理清后的关系图,依次进行接口和类的创建:
📌 USB 接口:
(由于整理得到:USB接口带有[打开设备][关闭设备]两种功能,所以我们写上~)
(我写上public abstrac 不是因为一定要写 而是因为我觉得我是个小白...应该多多练习,大家如果是初学者的话也要这样哦~等我们也成为大佬了,自然也就不用了~[大佬不用看...])
📌 鼠标类:
📌 键盘类:
📌 笔记本类:
📚 运行演示:
③ 接口的特性
📚 接口的特性有以下几点:
📕 接口是一种抽象的引用类型,它不能直接实例化对象:
📕 接口可以定义方法,这些方法默认为 public abstract (使用其他修饰符会报错):
📕 接口可以包含常量,这些常量默认为 public static final,即常量的值在接口中被固定且不能被修改:
📕 重写接口中方法时,不能使用默认的访问权限:
📕 一个类只能继承一个父类,但可以实现多个接口:
📕 接口之间可以通过扩展(extends)实现接口的继承,一个接口可以扩展多个接口:
④ 接口的使用
那么学习了"接口",改如何体现它的重要作用呢?让我们通过一个案例来看看:
在"类与对象"的多篇文章学习中,我们举了很多很多次" 学生类 ":
当我们把四个学生类放进一个数组中后,便不自觉地想到...我们之前学过一个Arrays.sort()方法,那么我们能否用Arrays.sort()方法对学生类数组进行排序呢:
看来是没办法做到的,其实也不难理解,毕竟平时我们传进去的数组都有其自带的类型,int型呀,long型呀,double型呀他们也都有自己比较大小的规范,所以能够排序;
而我们自己定义出来的Student,并没有规定的比较规范,所以sort方法也不知道,我们需要的排序到底是用"int",还是用"String",还是"double"...
这时我们就可以使用"接口"来实现这种"学生类的自定义排序":
(实现Comparable接口,然后实现其中的compareTo方法:)
此时我们已经重写好了"compareTo"方法,然后我们再重写一下toString()方法,以便之后展示~
然后我们回到Main中:
此时我们在使用Arrays.sort()就能够对学生类进行排序啦~
当然,毕竟是我们自己规定排序方法,所以除了用age进行排序,我们也可以用其他的进行排序:
这样就能够对(成绩(double))进行排序啦~(compareTo的返回值需要是int)
如果怕(int)转换可能导致数据丢失,从而对比不准确,也可以这样:
而为了加深对" compareTo "和" Arrays.sort "方法的使用,我们可以自己实现一个sort函数(我们使用的是冒泡排序,当然Java底层不是冒泡排序,而是一种速度非常快的"快速排序")
仍然是可以运行的~
⑤ 深拷贝和浅拷贝
Java中内置了一些很有用的接口,Clonable就是其中一个:
当我们直接使用clone()(克隆)方法时,我们发现是不能够做到的。
其实clone()方法我们并没有对其定义,也没有对其实现,所以它不在我们的子类当中。但是我们要知道,所有的类都有一个固定的,隐藏的父类->object类~而clone方法是在object类中的,所以我们没办法使用子类或子类对象去调用clone。
📚 当我们把" Clonable "接口接入Student类后,我们就可以尝试使用clone方法:
1.浅拷贝
顾名思义,就是"浅层次的拷贝",其意就是:
浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
📌 我们在此基础上创建一个Book类来辅助实验:
然后我们观察两者的books值的变化:
两者怎么都变成10了呢?这是因为:
进行的一次clone()虽然出现了新的Student对象,但是我们只clone()了Student对象,而Book是另一个类,并非完全属于Student,所以无法将books也独立的拷贝出来,而是"两者共用原来的books"。
2.深拷贝
" 深层次的拷贝 ";
📚 深拷贝把要复制的对象所引用的对象都复制了一遍。
直接上图~
那么这篇关于(抽象类与接口)的知识,就暂时为大家分享到这里啦,作者能力有限,如果有哪里说的不够清楚或者不够简洁,还请大家在评论区多多指出,那么我们下期再见啦~