面向对象
# 1. 模板方法模式
# 1.1 抽象类
如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
抽象的使用总结与注意事项:
- 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
- 抽象类没有创建对象的能力。
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
# 1.2 模板方法
模板方法模式实现步骤:
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
提示
模板方法我们是建议使用final修饰的,模板方法是给子类直接使用的,不是让子类重写的, 一旦子类重写了模板方法就失效了。
例如,中学生和小学生写《我的爸爸》这篇作文,他俩除了文章主体部分不同,别的地方都一样,此时,可以定义一个抽象类,用一个公共的模板方法来实现相同的部分,不同的部分在这个抽象类中声明一个抽象方法,让子类在此处来实现他们分别的功能。如下图代码所示:
# 2. 接口
# 2.1 基本知识
接口体现了一种规范,约束类一定要干啥事。规范一定是公开的。
JDK 8 之前接口中只能有抽象方法和常量。
关键字:
interface
接口的实现:
implements 接口1,接口2,...
几种关系:
- 类和类的关系:单继承。
- 类和接口的关系:多实现。
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口。
接口多继承的作用: 规范合并,整合多个接口为同一个接口,便于子类实现。(子类只需要implements一个接口就可以了,不用很累赘地实现很多个接口)
# 2.2 接口新增的方法
JDK8开始后新增了那些方法 ?
- 默认方法:default修饰,实现类对象调用。
- 静态方法:static修饰,必须用当前接口名调用。不能用子类调用静态方法。
- 私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用。
- 他们都会默认被public修饰。
注意
JDK8新增的3种方法我们自己在开发中很少使用,通常是Java源码涉及到的,我们需要理解、识别语法、明白调用关系即可。
提示
切换模块的编译版本,选择JDK版本:File -> Project Structure -> Modules ->language level
# 2.3 使用接口的注意事项(面试中涉及)
比较偏的一些语法,实际开发中通常不用,只有在一些面试中可能会涉及。
接口不能创建对象
一个类实现多个接口,多个接口中有同样的静态方法不冲突。
一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
继承只能在实现之前写,即先有亲爸再有干爹。接口为干爹,继承为亲爸。
一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
一个接口继承多个接口,是没有问题的,即使多个接口中有重名的方法,但如果多个接口中存在规范冲突则不能多继承。
# 3. 多态
# (1) 什么是多态
同类型的对象,执行同一个行为,会表现出不同的行为特征。
父类的引用类型指向子类的构造,向上造型,即为多态。
# (2) 多态的常见形式
- 父类类型 对象名称 = new 子类构造器;
- 接口 对象名称 = new 实现类构造器;
Animal a = new Dog();
a.run(); // 方法调用:编译看左,运行看右
System.out.println(a.name); // 方法调用:编译看左,运行也看左,动物名称
Animal a1 = new Dog();
a1.run();
System.out.println(a1.name); // 动物名称
2
3
4
5
6
7
# (3) 多态中成员访问特点
- 方法调用:编译看左,运行看右。=> 因此多态下不能使用子类的独有功能。
- 变量调用:编译看左,运行也看左。(多态侧重行为多态)
因此上述代码的输出结果都是动物名称。
# (4) 多态的前提
有继承/实现关系;有父类引用指向子类对象;有方法重写。
# (5) 优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Animal a = new Dog(); // 后续业务更改时只需要修改此处new的Dog即可
a.run(); // 后续业务行为随对象而变,后续代码无需修改
2
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
public static void go(Animal a){
System.out.println("开始。。。");
a.run();
System.out.println("结束。。。");
}
// 这个函数可以传上面代码中的a, 也可以传a1
2
3
4
5
6
# (6) 多态下引用数据类型的类型转换
自动类型转换(从子到父):子类对象赋值给父类类型的变量指向。
// 自动类型转换 Animal a = new Dog(); a.run(); //a.lookDoor(); // 多态下无法调用子类独有功能
1
2
3
4强制类型转换吗(从父到子)
此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
// 强制类型转换:可以实现调用子类独有功能的 Dog d = (Dog) a; d.lookDoor();
1
2
3作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
注意: 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
建议
建议强制转换前,先判断变量指向对象的真实类型,再强制类型转换。
变量名 instanceof 真实类型
判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true
以上判断是很有必要的,因为很有可能在方法内部看不到这个对象到底是啥类型。如下代码所示,不知道传进来的 Animal 到底是狗还是乌龟:
public static void go(Animal a){
System.out.println("预备~~~");
a.run();
// 独有功能
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("结束~~~~");
}
2
3
4
5
6
7
8
9
10
11
12
13
# 4. 内部类
内部类就是定义在一个类里面的类
# 4.1 静态内部类
有static修饰,属于外部类本身。它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer{
// 静态成员内部类
public static class Inner{
}
}
2
3
4
5
静态内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
范例:Outer.Inner in = new Outer.Inner();
这种语法比较少用,一般还是分开定义两种类的。
# 4.2 成员内部类(非静态内部类)
无static修饰,属于外部类的对象。JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了。
public class Outer {
// 成员内部类
public class Inner {
}
}
2
3
4
5
6
成员内部类创建对象的格式:格式:外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
范例:Outer.Inner in = new Outer().new Inner();
(先有外部对象,然后才能创建成员内部类的对象)
关于static
static 修饰的是表示属于类的,而不是属于对象的。若有1000个对象,那么属于对象的部分也是有1000个,而 static 修饰的部分只有一个,因为只有一个这样的类。所以对象里面是不存在静态的。
成员内部类比静态内部类更常用,因为他有对象创建的先后次序。企业内部,必须先有车这个对象,再有发动机对象。
注意
在成员内部类中访问所在外部类的对象,格式为:外部类名.this
# 4.3 局部内部类
鸡肋语法,了解即可
# 4.4 *匿名内部类
# (1) 概述
本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
作用:方便创建子类对象,最终目的为了简化代码编写。
点击展开代码
/**
目标:学习匿名内部类的形式和特点。
*/
public class Test {
public static void main(String[] args) {
Animal a = new Animal(){
@Override
public void run() {
System.out.println("老虎跑的块~~~");
}
};
a.run();
}
}
//class Tiger extends Animal{
// @Override
// public void run() {
// System.out.println("老虎跑的块~~~");
// }
//}
abstract class Animal{
public abstract void run();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上面这段代码中虽然 Animal 是个抽象类,但是在 Test 中也可以用 Animal 来创建一个匿名内部类。匿名内部类写出来就会产生一个匿名内部类的对象。匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。即产生的匿名内部类相当于注释掉的 Tiger 子类对象。
匿名内部类也会产生 Class 文件。
# (2) 常见使用形式
有了匿名内部类之后,可以不是必须要定义另外的Class去实现相应的接口了,可以直接在 new 的时候定义匿名内部类出来进行接口的实现。
具体的一个例子
/**
目标:掌握匿名内部类的使用形式(语法)
*/
public class Test2 {
public static void main(String[] args) {
Swimming s = new Swimming() {
@Override
public void swim() {
System.out.println("学生快乐的自由泳🏊");
}
};
go(s);
System.out.println("--------------");
Swimming s1 = new Swimming() {
@Override
public void swim() {
System.out.println("老师泳🏊的贼快~~~~~");
}
};
go(s1);
go(new Swimming() {
@Override
public void swim() {
System.out.println("运动员泳🏊的贼快~~~~~");
}
});
System.out.println("--------------");
/**
学生 老师 运动员可以一起参加游泳比赛
*/
public static void go(Swimming s){
System.out.println("开始。。。");
s.swim();
System.out.println("结束。。。");
}
}
interface Swimming{
void swim();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
经常用匿名内部类方法的入参形式,如下图:
啥时候用匿名内部类
开发中不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。能写的时候才要写,可遇不可求。