抽象类 or 接口
1.回顾 前文 要点
学到这里 面向对象 的 三 大特征 我们 已经 学习过 了。
1.封装
2.继承
3.多态
下面我们 就先来复习一下。
1.啥是 封装 ?
封装 就是 将 对象 的 属性 和 实现的 细节 通过 关键字 private
给 隐藏 起来 ,对外 提供 特有的 get 和 set 方法来进行 访问,
2.啥是 继承 ?
继承 就是 通过 一个 已有 的 类的 基础上 派生 处 新类(如: 动物 类 就能 派生出 猫 类 和 狗 类 等等) 子类 继承 父类 的 特征 和 行为 ,使得 子类 对象 拥有 父类 的 实例 域 和 方法,
(简单来说 就是 对 共性 的 抽取, 使 子类 具有 父类 相同的 行为 ) , 使用 关键字 extends
来实现
如: A extends B
A 就 是 子类 , B 就是 父类 属于 A is B 的 关系。
继承的 意义 : 提高 了 代码的 复用 性 。
3.啥是 多态 ?
多态是 一种 思想, 表示的 是 同一个行为具有 多个不同的表现形式 或 形态能力 。
如:黑白打印机和彩色打印机相同的打印行为却有着不同的打印效果,
但 光有 这句话 是 不能行 的 我们 还需要理解 啥是 向上转型,动态绑定 (重写)。
1.向上转型: 父类引用 引用 了 子类 对象。
2.动态绑定:通过 父类引用 调用 子类和 父类 同名 的 覆盖(重写) 方法。
注意: 在发生 动态 绑定时 一定 会 涉及到 重写 , 这个 也需要我们 重点理解.
重写特征 |
---|
1.方法 名 相同 |
2. 参数类型 和 个数 相同, |
3.返回值相同 (特例 : 返回值 构成 父子类 )。 |
重写 注意 事项:
1.static 修饰的 方法 不能 重写 |
---|
2.final 修饰的 方法 不能 重写 |
3. private 修饰的 方法 不能重写 |
4.子类 重写 的 方法 访问 修饰 限定 需要 大于 等于 父类 访问 修饰 限定 (子类方法的访问权限要大于等于父类方法的访问权限) |
动态 除了 上面 这些 比较 重要的 我们 还学习 了
静态 绑定 :通过方法的重载实现的。编译的时候,会根据你调用方法时,所给参数的类型和个数,来决定调用哪个同名方法
向下 转型 : 子类 引用父类 对象 , 因为 不太 安全 所以 不推荐 使用。
注意事项:
1.子类 继承 父类 , 子类 需要 在 构造方法中 加入 super(…) 来显示 调用 父类构造方法 , 帮助 父类 完成 构造 。
2.super 和 this 的 区别 : super 是 针对父类的引用, 而 this 针对 当前 对象的 引用 。
3.重写 和 重载 的 区别 :
重载 | 重写 | |
---|---|---|
相同点: | 1.方法名相同 | 1.方法名相同 |
不同点: | 2.返回值 可以不同 | 2.返回值必须相同 特例:返回值构成父子类 |
不同点: | 3.参数列表不同 数据类型 ,顺序个数都不一样 | 3.参数列表相同 数据类型,顺序个数都相同 |
不同点: | 重载是没有权限要求的 | 5.重写是有权限要求的 子类的 权限要大于等于父类 另外:父类被private修饰不能够重写 |
4.四种方法 权限 :
1.private | 被 private 修饰的 字段 或 方法,只能在 同一个类中使用, 如果 这个 类 被 继承, 被 private 修饰的 方法 或 字段 , 是 被 继承下来的 ,但无法访 |
---|---|
2.default (包访问 权限) | 只要在 同一个包底下 都能够使用。 |
3.protected | 在 同一个包底下 随便使用, 但 其他包中需要是子类 才能 够 使用 |
4.public | 不管是同一个包 还是 不同 的 包 都能 够使用 。 |
注意 | 上面 说的使用 是 被 这几种 修饰限定符 给 修饰的 字段 或 方法。 |
到此我们 就 回顾 完 了 , 下面 我们 就 来 使用 多态 来 引出 我们 接下来 学习 的 抽象类 。
抽象类
先来看 这段 代码 :
class Shape{
public void drow(){
System.out.println("Shape :: drow()");
}
}
// Rect : 矩形
class Rect extends Shape{
@Override
public void drow() {
System.out.println("矩形");
}
}
// Flower : 花
class Flower extends Shape{
@Override
public void drow() {
System.out.println("❀ 花");
}
}
public class Test {
public static void func(Shape shape){
shape.drow();
}
public static void main(String[] args) {
Rect rect = new Rect();
func(rect);
Flower flower = new Flower();
func(flower);
}
}
这里 我们 就 实现了 我们的 多态 , 通过 shape 这 一个 引用 , 表现出 了不同 中 状态 ,如 : 矩形 , 花 。
这里 你 有没有 想过 只要 父类 引用 了 子类 对象 , 调用 drow 方法 ,都会 发生 动态 绑定 ,原本 自己 的 drow 方法 实现的 功能 就没有 用 了 ,我们是否 可以 将 这个 方法 实现 省略 掉 呢?
这里就 可以 将 drow() 这个 方法 设置 为 抽象 方法 ,此时 包含抽象 方法的 类 我们 就称为 抽象 类 。
抽象类语法规则
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用
给出具体的实现体。
abstract class Shape{
abstract public void drow();
}
此时 我们 被 abstract 修饰 的 Shape就 是 抽象类,
而 被 abstract 修饰 的 方法就 是 抽象 方法, 这个 方法就可以事项具体 类容。
下面我们 来 对比一下 抽象 类 普通类 的 区别:
1.抽象类 不能实例化
2.抽象类 与 普通 类 一样 可以 定义 普通 方法 和 成员变量。
这我们 虽然 不能 通过 new 这个 类来 创建 对象,但 抽象类 ,发明出来 就是 用来 继承的 , 我们 可以 通过 继承 , 来 调用 这些 普通 方法 或 成员变量。
如:
当我们尝试 继承 后 会发现 ,报红线 了 ,不要着急,这里 是因为 我们没有 重写 抽象 方法 。
注意: 当 继承 抽象类 的同时 我们 需要 重写 所有的抽象 方法。
也挺好想 :我们抽象 方法本来 什么 就没实现 ,如果 你不重写 ,别人 调用 这个 方法 有什么 用 呢? 还不如 不写。
重写 完 就没有 报错了 ,下面 继续 。
通过 子类 访问 ,父类 (父类 为 抽象 类 )的 普通 方法 :
abstract class Shape{
abstract public void drow();
public int a;
public String b;
public void func(){
System.out.println("测试 抽象类 是否能 定义普通方法");
}
}
class A extends Shape{
@Override
public void drow() {
System.out.println("重写 的 抽象 方法");
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
a.func();
a.drow();
}
}
同样 的 我们 的 抽象类 ,也 是 可以 发生 向上绑定 ,和 多态的 , 这里 就 来 看一下。
1.向上 转型:
2.多态:
abstract class Shape{
abstract public void drow();
public int a;
public String b;
public void func(){
System.out.println("测试 抽象类 是否能 定义普通方法");
}
}
class A extends Shape{
@Override
public void drow() {
System.out.println(" A 中 重写 的 抽象 方法");
}
}
class B extends Shape{
@Override
public void drow() {
System.out.println(" B 中 重写 的 抽象 方法");
}
}
public class Test {
public static void func(Shape shape){
shape.drow();
}
public static void main(String[] args) {
A a = new A();
B b = new B();
func(a);
func(b);
}
}
另外 : 我们 一个 抽象类 继承 一个 抽象 类 可以 不重写 抽象 方法 。
如: 抽象类 B 继承 了 抽象 类 A 此时 抽象 类 B 是 不需要 重写 抽象 A 的 抽象方法 (对比 一个 普通类 C 继承 抽象类 A ,就必须 重写抽象方法, 要不然 报错)
俗话 说的 父债子还 , 如果此时 一个 普通的 类 继承 了 这个抽象 类 B ,此时 这个子类 不仅 需要 重写 抽象类 A 的 抽象 方法 , 还需要 重写抽象 类B的 抽象方法。
1.只重写 了 抽象类 B 的 抽象 方法
2.重写了 抽象类 A 和 抽象 类 B 的 抽象 方法 。
如果 我们 在 拿一个 抽象类 继承 呢 ?
这样 就会 形成 套娃 ,这个 类 也 不需要 重写 父类 的 抽象 方法 ,知道 有 一个 普通类 继承 了 ,那么 就得 全部 重写 ,
在 来 一个 注意事项:
抽象 类是不能 被 final修饰的
之前我们 学过final
,说过 其中 的 一种用法 , 被 final 修饰 的 类 是 不能 被 继承 的 ,我们的 抽象类 本 身就是 用来 被继承的 ,
final 就好比 与 火 , 而 抽象类 就好比水 , 两者 本身 就不 合 , 强行 放在 一起 肯定 会出问题。
既然 抽象 类 不能 被 final 修饰 ,那么 我们的抽象 方法 同理 也是 不能 被 final修饰的, 之前 还 提过 被final修饰的 方法 是不能 重写的 ,这样与 抽象方法 必须 重写 ,又有 冲突 。
看完上面这些 内容 你 能 总结 出什么 ?
下面 就 直接 抛出 总结。
总结 :
1、包含抽象方法的类,叫做抽象类。
2、什么是抽象类,一个没有具体实现方法,被abstract修饰
3、抽象类是不可以被实例化的。不能 new
4、因为不能被实例化,所以,这个抽象类,其实只能被继承
5、抽象类当中,也可包含和普通类一样的成员和方法。
6、一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写这个抽象类的所有的抽象方法。
7.抽象类的最大的作用,就是为了被继承
8、一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法。
9、结合第8点,当A类再次被一个普通类继承后,那么A和B这两个抽象类当中的抽象方法,必须重写
10、抽象类不能被final修饰,抽象方法也不可以被 final 修饰
同理 抽象 方法 不能 被 private
和 static
修饰 (导致 这个方法无法被重写)。
抽象类的 作用
抽象类 最大 的意义 就是为了 继承 ,
抽象类本身不能被实例化, 要想使用, 只能创建该抽象的子类. 然后让子类重写抽象类中的抽象方法.
有些小伙伴 可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法
呢?
其实 使用 抽象类 相当于多了一重编译器的校验(如果你没有重写抽象方法,编译器会报错,提醒你)
我们 充分利用编译器的校验, 在实际开发中是非常有意义的。
抽象类 完了,接下来 学习 我们的 接口 。
接口
什么是 接口 ?
答: 接口是 对 公共的 行为规范 标准 (再实现 接口 时, 只要 符合 规范 标准 , 就可以 通用)。
再java 中 ,接口 可以看成 是 :多个类 的公共规范,是 一种 引用 数据类型 。
看完 上面 这些 概述 是不是 很 懵逼 下面 就通过 例子 来 解释 一下。
笔记本 上的 USB 接口 , 电源插座等 。
电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备
当满足 满足 这两个 接口 的规范 协议 ,就 能够 插入 其中使用 , 如果 你 强行 插入当我没说,
如: 有线鼠标 键盘 , 如果 他们的 线 的 插头 与 电脑 规定 的 USB接口 相同 此时 就能够 使用 ,不同那么 就不能使用。这里 就是 接口 规定了标准 范围,而我们的 鼠标 和 键盘 就 满足 这种 规范 ,就可以使用 这个 接口 。
知道 了 概念 下面就来 了解 一下 语法 规则 。
接口的 语法 规则
使用 关键字 interface
修饰 的类 就 是 接口
创建 完 了 接口,来 了解 一下我们的 接口 。
1.接口 当中 ,不能创建普通 方法
如果这 非要 创建 普通方法 需要 再 方法 前 加上 关键字 default
,表示 该方法 是此 接口 的 默认 方法
注意 : 在 JDK 1.8 开始 才 允许 接口 可以 实现 方法 , 但 必须是 被关键字 default
修饰的 方法 。
另外 : 在 接口 中 是 可以 存在 静态 方法 的 但 静态 方法 不能 被重写
除了 默认方法 , 静态 方法 , 接口 还 包括 抽象方法 ,
接口 与 抽象类 相比 之下, 接口 只能 包括 抽象 方法 或 接口 默认方法 , 字段 只能 包括 静态 常量, 而 抽象类 除了 抽象方法 , 还可以包含非抽象方法, 和字段. , 所以 说 接口 相比 抽象类 更进 一步 。
观察 上面 几张配图 ,有没有 发现 , 我们 创建 的 方法 中 的 关键字 public
都是 灰色 的 ,这里 意味 这 在 接口中 所有的 方法 ,都是默认 被 public
修饰 的。
2.在 接口当中所有 方法 默认 都是 被 public
修饰的 ,抽象方法 默认 是 被 public abstract
修饰的
尝试 使用 其他 访问 修饰 限定符 :
可以 看到 只有 public 修饰 , 或者 不写 (此时 会默认 为 是 public 修饰 )才能 够不报错 。
3.接口 同样不能实例 化对象
刚刚 说过 ,接口 是 抽象 类 的更进一步 , 抽象类 不能 实例化对象, 接口 也应该 不能 实例化对象。
下面演示 :
下面 就来 使用 一下我们的接口。
4.类 与 接口 之间 使用关键字 implements
来实现
模拟 场景: 皮卡丘使用 技能 A
1.通过 implements
实现 接口 ,同样需要 将 接口 中 所有的 抽象类 方法, 全部重写 ,(注意:默认方法,或 static 修饰的 方法 就 不需要 重写, 但 默认 方法 是 可以按照 自己的 意愿选着 重写 的 ,而static 修饰 的方法 无法被 重写)。
重写 完 drow 方法。
// 技能 A
interface A {
void drow();
}
// 技能 B
interface B {
void drow();
}
// 皮卡丘
class Pikachu implements A {
@Override
public void drow() {
System.out.println("皮卡丘使用十万伏特");
}
}
// 喷火龙
class Charizard {
}
public class Test {
public static void main(String[] args) {
Pikachu pikachu = new Pikachu();
pikachu.drow();
}
}
此时 就完成 了 我们 的 场景 模拟 。
同样我们 的接口 是可以 完成 我们 的向上 转型 的 , 既然 有 了 向上 转型 (父类 引用 引用 了 子类 对象) ,那么 子类 发生 重写 ,调用 重写 的方法 ,发生 动态绑定 ,最后 也 能够 完成 我们 的 多态 。
1.向上转型 和 动态绑定:
2.多态:
// 技能 A
interface A {
void drow();
}
// 技能 B
interface B {
void Skill();
}
// 皮卡丘
class Pikachu implements A {
@Override
public void drow() {
System.out.println("皮卡丘使用十万伏特");
}
}
// 喷火龙
class Charizard implements A,B {
@Override
public void drow() {
System.out.println("喷火龙使用了 撞击 ");
}
@Override
public void Skill() {
System.out.println("喷火龙 使用了 吐息");
}
}
public class Test {
public static void func(A a){
a.drow();
}
public static void main(String[] args) {
Pikachu pikachu = new Pikachu();
Charizard charizard = new Charizard();
func(pikachu);
func(charizard);
}
}
5.一个类 是 可以 拥有 多个 接口 的
另外 : 在 上面 代码 中我们 的喷火龙 是 实现了 两个接口 的 ,这里 我们 通过 implements
实现 接口 多个 接口时 , implements
只需要 一个 , 接口之间使用 , 逗号
隔开 即可,
上面 一直 将的 是 方法, 在 接口 中 同样 是 包含字段 的 只不过这个 字段 必须是 静态常量 static final
修饰的 变量 。
6.接口 中 的 字段 只能 是 被 static final修饰 。
7. 接口 之间 是 可以 继承的
相比 于 类 继承 类 , 类 继承 抽象类, 类 虽然 继承不了我们的 接口 ,但 是 可以实现 多个 接口 , 但 我们的 接口 之间 是 可以 继承。
此时 我们的 接口 B 就 继承 了 接口 A , 当一个 普通类, 实现 了 这个 B 接口 就需要 重写 ,A 和 B 的 抽象方法 ,(这里 B继承 A 此时 就先当与 B接口 扩展 了 A 接口 的功能)。
interface A{
int a = 10;
void drow();
}
interface C {
int c = 30;
void func();
}
interface B extends A{
int b = 20;
void swap();
}
class Test2 implements B{
@Override
public void swap() {
System.out.println("重写 B 的 方法");
}
@Override
public void drow() {
System.out.println("重写 A 的 方法");
}
}
下面 我们 就来 整点 好玩的 。
以前我们 说过 一个类 只能 继承 一个 类 或 抽象 方法 。
此时 让我们的 接口去 继承 多个 接口 试试
总结:
接口与接口之间,可以使用 extends来 操作它们的关系,此时,extends 在这里意为 拓展。(一个接口A 通过 extends 来拓展 另一个接口B的功能,此时当一个类 通过 implements 实现 接口A,此时重写的方法,不仅仅是 A 的抽象方法,还有从B接口,拓展来的功能【方法】)
最后 我们 将 上面 所说的来一次 大总结 :
总结:
1.使用interface来定义一个接口,例:interface IA{},这就是一个接口
2.接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字 default 来修饰 这个方法。
3.接口当中,可以有static的方法。
4.接口 中 所有方法都是public的。
5.抽象方法,默认是 public abstract 的。
6.接口是不可以被实例化的(不可以被new的)。
7.类和接口之间的关系是通过 implements(工具)实现的。
8.当一个类 实现了 一个接口,就必须要重写接口当中的抽象方法。
9.在调用的 接口的 时候 可以创建一个接口的引用 (如 实现接口的 类 ), 对应到一个子类的实例
10.接口当中的字段/属性/成员变量,默认是public static final
修饰的。
11. 当一个类实现接口之后,重写抽象方法的时候,重写方法的前面必须加上public,因为接口中方法默认都是public的,而且重写方法的访问权限,必须要大等于父类当中方法的访问权限
补充 : 之前没演示这里 演示 一下:
12.一个类可以通过关键字extends继承一个抽象类或者普通类。但是只能继承一个类。同时也可以通过implements实现多个接口。接口之间使用逗号隔开。
13.接口与接口之间,可以使用 extends来 操作它们的关系,此时,extends 在这里意为 拓展,而不是继承,(一个接口 通过 extends 来拓展 另一个接口的功能)
本文 完 : 下文 预告 三种 常用 接口 。