目录
1 抽象类
抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,除非子类也是抽象类,必须要使用 abstract 修饰,当又再次被继承时需要重写其抽象方法
抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
抽象类本身不能被实例化,要想使用只能创建该抽象类的子类,让子类重写抽象类中的抽象方法
2 接口
在 Java 中,接口可以看成是:多个类的公共规范,是一种引用数据类型
2.1 接口特性
① 接口是使用 interface 方法来修饰的
接口的定义格式与定义类的格式基本相同
将 class 关键字换成 interface 关键字,就定义了一个接口
提示
- 创建接口时, 接口的命名一般以大写字母 I 开头
- 接口的命名一般使用 "形容词" 词性的单词
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
② 接口当中的成员变量默认都是 public static final 修饰的
接口里面的是常量,常量一般大写
③ 接口当中的成员方法默认都是 public abstract 修饰的
④ 接口不能进行实例化
接口类型是一种引用类型,但是不能直接 new 接口的对象
⑤ 类和接口之间的关系,可以使用 implements 来进行关联
光实现接口类会报错,要么变为 abstract 类,要么重写接口里面的方法
变为 abstract 类,这样就不用重写接口类里面的 draw 方法了
重写 draw 方法
接口也可以实现向上转型
接口不能实例化自己,但接口可实例化引用一个具体的对象,这个过程相当于发生了向上转型(将子类给到父类,向上转型语法格式:父类类型 对象名 = new 子类类型())
动态绑定后实现多态
也可以这样实现动态绑定实现多态
完整代码
interface Shape{
void draw();
}
class Rect implements Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
class Flower implements Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public class Test3 {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Rect();
Shape shape2 = new Flower();
drawMap(shape1);
drawMap(shape2);
System.out.println("================");
Shape[] shapes = {new Rect(),new Flower(),new Rect()};
for (Shape shape : shapes) {
shape.draw();
}
}
}
运行结果
⑥ 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
有两种修改方式,要么被 static 修饰,要么被 default 修饰
被 static 修饰的方法不能被重写了,用途在直接用接口访问
而被 default 修饰的方法无法直接用接口访问
因此由实现接口的类来实现
如果不想使用默认的 default 修饰的方法,也可以重写接口中 default 修饰的方法然后调用
这样写也可以
总结:
- 被 default 修饰的可以重写,不重写就是接口默认的
- 静态的不能重写,通过接口名可以自己调用
- 接口中未实现的 TestA() 一定要重写
⑦ 重写接口中方法时,不能使用默认的访问权限
⑧ 接口中不能有静态和实例代码块以及构造方法
⑨ 接口也是有对应字节码文件的
接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
⑩ 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法
如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类,不需要重写接口中的抽象方法,但下一个类再继承时全需要重写(普通类继承抽象类)
2.2 例子
实现笔记本电脑使用USB鼠标、USB键盘的例子
1. USB接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
3. 鼠标类:实现USB接口,并具备点击功能
4. 键盘类:实现USB接口,并具备输入功能
创建包和接口
创建 USB 两个标准功能:打开设备、关闭设备
鼠标类,实现 USB 接口后重写接口里这两个方法,再加上自己的功能
键盘类,实现 USB 接口后重写接口里这两个方法,再加上自己的功能
笔记本类:使用 USB 设备
测试类
测试结果
加入打开电脑关闭电脑,用电脑中的功能 “.” 就出来了
可以加上分隔符表示不同设备在使用
打印结果
代码中发生的向上转型和向下转型
在 Java 中,接口可以用来定义一组方法规范,而不提供具体实现。然后,可以有不同的类去实现这个接口,从而提供不同的行为
2.3 实现多个接口
在 Java 中,类和类之间是单继承的,一个类只能有一个父类,即 Java 中不支持多继承,但是一个类可以实现多个接口
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类
快速重写方法:鼠标放在父类 Animal 处,Alt + 回车或者使用 ctrl + i 快速实现
再次 Alt + 回车重写构造方法就可以重写完成
然后进行向上转型和动态绑定
继承表达的含义是 is - a 语义,而接口表达的含义是具有 xxx 特性,因此只要具备该特性就可以使用该接口,不需要先继承某种关系
不能先实现接口后继承,extends 靠后
2.4 接口间的继承
在 Java 中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承
即:用接口可以达到多继承的目的
接口可以继承一个接口,达到复用的效果,使用 extends 关键字
2.5 接口使用实例
大于小于号不用于比较两个引用的大小
用接口进行比较
<> 里面写比较类型,表示当前类可以进行比较,这个语法叫泛型
Student 类里面要重写 compareTo 方法
传一个对象 Student o,this 代表调用对象
这个接口缺陷:一旦要比较除年龄外的,此时 compareTo 没法修改里面逻辑
因此用另一个方式解决了这个问题,叫做比较器(Comparetor)
此时就很灵活了,因为没有直接定义在 Student 类里面
想比较什么就写一个自己想要的比较器就行了
总结两种比较方式
①
②
Comparable 作用就是默认的,已经提供好的,和比较器可以共存是不冲突的
此时可以重写 compareTo 就可以调用自己写的默认的了
但一旦写死了就不要变了
这样修改后就不会根据根据默认的去比较了,而是根据比较器去比较
Comparable 和 Comparator 的区别在于一个是默认的一个需要自己调用
实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序),此时 compareTo 调用的是自己写的
2.6 Clonable 接口和深拷贝
2.6.1 Clonable 接口
Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 "拷贝",但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出 CloneNotSupportedException 异常
① Object 类中存在一个 clone 方法
不同包的子类或者同一个包可以使用
此代码满足不同包的子类,因此重写父类方法后用 super 调用
出现报错
把异常抛进去后依旧报错
因为克隆方法的返回值是父类,此时调用返回子类需要进行强转
强转以后运行报错
② 当我们自己写的类型要支持克隆的时候,要实现 Cloneable 接口
Cloneable 接口是空接口,也称为标记接口,表明当前类是可以克隆的
没实现接口时会出现报错,因为当前类不支持克隆,要支持克隆就要实现接口
2.6.2 浅拷贝
分析打印结果
因为 money 对象只有一个,因此里面数据改变后访问结果也改变
这种拷贝叫做浅拷贝
Object 的默认是浅拷贝这种说法是错的,深浅拷贝跟每个类的克隆方法没有关系,在于代码的实现,而不在于某个方法
2.6.3 深拷贝
深拷贝是指在拷贝这个对象的同时,把对象里面包含的引用所指的对象也拷贝一份
深拷贝意味着此对象(当前代码指 money )也要支持克隆
方法写了但没有调用,因此 money 依旧没有被克隆
调用方法克隆 money
分析克隆 money 过程
tmp 被回收了
结果 money 克隆成功,这就是深拷贝
2.7 抽象类和接口的区别
抽象类是一个类可以被继承,里面可以包含普通方法普通字段,而接口里面只能包含抽象方法,静态方法也可以,成员方法默认是 public static final修饰
抽象类当中的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法, 子类必须重写所有的抽象方法
3 Object类
Object 是 Java 默认提供的一个类,Java 里面除了 Object 类,所有的类都是存在继承关系的,默认会继承 Object 父类,即所有类的对象都可以使用 Object 的引用进行接收
一般锁开的是可以直接调用的,锁起来的和钥匙是受保护的,其它都是公开的
① equals 方法
能比较当前定义的两个对象一不一样
不重写的话调用默认的,比较的是地址
可以重写 Object 下面的 equals 方法然后调用
也可以编译器直接生成重写(一般 equals 方法和 hashCode 方法同时生成)
② hashCode 方法
重写 hashCode 方法定位对象在内存当中的位置
③ toString() 方法
如果要打印对象中的内容,可以直接重写 Object 类中的
不重写的话调用默认的,比较的是地址
也可以编译器直接生成重写(一般 toString() 方法和 hashCode 方法同时生成)