今日任务:
1,能够独立使用抽象类
2,能够独立使用多态
3,能够独立使用接口
4,能够理解适配器设计模式
1. 抽象类
1.1. 抽象类的产生
需求:创建类描述猫和狗的行为;
猫的行为:抓老鼠;吃鱼;
狗的行为:看家;吃骨头;
分析:这个需求有两类事物,需要创建两个类来分别描述它们;
1、创建描述猫的类:
1.png
2、创建描述狗的类:
2.png
3、问题:上面两个类中有相同的方法:
3.png
4、为了提高代码的复用性,需要将其抽取到共同的父类中;猫和狗都是动物,所以可以创建一个动物类,让猫和狗都继承它:
4.png
问题:在抽取共同方法到父类中的过程中发现,不同子类虽然具有相同的方法,但是不同子类的实现不同,在父类中无法准确描述;
5.png
像这种在父类中不能准确描述、需要子类去实现的方法,就应该定义为抽象方法;
抽象方法的书写格式:
A、抽象函数没有方法体,连大括号都没有;
B、抽象函数使用关键字abstract修饰,关键字写在函数的返回值类型前面;
抽象函数表示的是描述不清的功能;如果一个类中有了抽象函数,说明这个类表示的事物也是描述不清的,需要定义为抽象的类;
抽象类的书写格式:
直接将abstract关键字写在class关键字前面;
6.png
1.2. 抽象类的特点
1.2.1. 抽象类不能实例化
因为抽象类表示的是一个描述不清楚的事物,就像一张数据不清的图纸,无法创建对象;
7.png
1.2.2. 子类继承抽象类,必须实现父类的所有抽象函数,否则子类也是抽象的
8.png
9.png
结论:抽象类不能直接使用,必须通过子类继承它,然后重写抽象类中的所有抽象函数,最后通过子类对象使用这些函数;
如果希望一些类都必须具有某个功能,就可以将这个功能放到他们共同的父类中,如果不同的子类的实现不同,就需要将这个功能定义为抽象函数;
1.3. 抽象类的细节问题
1, 抽象类一定是父类吗?
是;
首先抽象类就是通过多个不同的子类不断向上抽象共同的信息,发现有相同但实现不同的功能,所以才定义为抽象类的;
而且抽象类不能实例化,只能通过子类继承它,然后在子类中重写父类所有抽象函数,通过子类对象去使用;
所以抽象类一定是父类;
2, 抽象类中可以书写哪些成员?
抽象类也是一个类,可以书写类中的所有成员;
10.png
3, 抽象类中可以不书写抽象方法吗?
可以;
11.png
如果一个类虽然没有抽象函数,但是不想被实例化,又可以被继承,就可以将其定义为抽象类,没有抽象方法的抽象类;
这种类的典型应用之一就是适配器模式;
4,抽象关键字abstract不能和哪些关键字共存?
因为抽象函数必须被继承的子类重写,所以和不能被继承和重写的关键字都不能共存:private;final;
12.png
13.png
因为被static修饰的函数是静态函数,可以直接类名访问,和类的对象无关;如果抽象函数可以静态化,也可以直接通过类名访问,但是调用一个抽象函数没有意义,所以抽象函数也不能和static关键字共存;
14.png
5、一个类什么时候需要定义为抽象类?
1、如果一个类中有抽象函数,这个类就要被定义为抽象类;
2、如果一个类中没有抽象函数,但是这个类不希望被实例化,同时希望它可以被继承,就需要声明为抽象类;
1.5. 抽象类总结
抽象类的概念:是使用关键字abstract修饰的类就是抽象类;
抽象类的产生:当多个不能相互继承的类具有相同的功能时,就需要将共同的信息向上抽取,放到公共的父类中;如果公共的父类只能描述所有子类都具有的功能,但描述不清功能的具体实现,就需要将该函数定义为抽象的,使用关键字abstract修饰;如果一个类中出现了抽象的函数,说明这个类也是不具体的,应该定义为抽象类,使用abstract修饰;
抽象类的特点:
抽象类不能实例化;
抽象类中的抽象函数必须由子类去实现,否则子类也是抽象类;
抽象类也是一个类,类中可以书写所有成员,也可以没有抽象函数;
因为抽象函数必须由子类实现,所以不参与继承的(private)和不能被子类重写的(final)关键字不能和abstract共存;
因为静态函数不需要类的对象就可以直接使用,所以static关键字也不能和abstract共存;
如果一个类不希望被实例化,但又希望被子类继承,就可以定义为抽象的,即使类中没有抽象函数;
2. 多态
2.1. 多态的引入
在生活中,我们经常对同一类事物有不同的称呼。例如:
苹果,又叫水果;裤子,又叫衣服;
而且一般习惯使用更抽象的概念描述具体的事物;如:
买苹果,一般说买水果;买裤子,一般说买衣服;
像这样同一个事物可以用不同的方式表示,就叫做多态;
2.2. java中的多态
2.2.1. 多态的体现
在Java中,使用类描述一类事物;
要模拟生活中的多态,就需要使用两个类;而且两个类都能指代同一个具体对象;
15.png
java中多态的体现:
使用父类型引用指向子类对象;
(使用接口类型引用指向实现类对象)
2.2.2. 多态的使用前提
之所以可以使用父类型引用指向子类对象,因为子类和父类之间有 “是”的关系,子类描述的事物实际上是父类的一个特例,所以可以使用父类型引用指向子类对象;
而没有继承关系的两个类就不能这样做:
16.png
所以java中使用多态的前提:
多态中的两个类之间有继承关系;
2.2.3. 多态的好处
在生活中,一般都会使用多态的形式描述一个事物,如喝啤酒,不说啤酒,而是说喝酒,买裤子不说买裤子,而是说买衣服,是因为这样指代的范围更广,方便描述;
用软件的术语来说,就是提高扩展性,降低耦合性;
例如:模拟动物医院中,动物医生检查动物(猫、狗);
17.png
18.png
19.png
20.png
问题:1、每添加一个测试的动物,都要添加一个测试的功能,使用麻烦(也就是扩展性差);而且每个功能中的代码都基本一样,造成代码复用差;
2、猫和狗中也有重复的代码,代码复用性差;
使用多态解决问题:
//模拟动物医院中,动物医生检查动物(猫、狗);
//动物
abstract class Animal
{
String name;//名字
int age;//年龄
public abstract void eat();
public abstract void say();
public abstract void sleep();
}
//猫
class Cat extends Animal
{
//吃
public void eat(){
System.out.println("吃鱼");
}
//叫
public void say(){
System.out.println("喵喵");
}
//睡觉
public void sleep(){
System.out.println("zzZZZ");
}
}
//狗
class Dog extends Animal
{
//吃
public void eat(){
System.out.println("吃骨头");
}
//叫
public void say(){
System.out.println("汪汪");
}
//睡觉
public void sleep(){
System.out.println("zzZZZ");
}
}
//动物医生
class AnimalDoctor
{
//检查动物
public void check(Animal a){
//显示动物的属性
System.out.println(a.name + "|" + a.age);
//测试动物的行为
a.eat();//测试吃
a.say();//测试叫
a.sleep();//测试睡
}
}
class Test
{
public static void main(String[] args)
{
//创建一个动物医生的对象
AnimalDoctor ad = new AnimalDoctor();
//父类引用指向子类对象,使用了多态
Animal c = new Cat();
c.name = "小咪";
c.age = 3;
//调用动物医生对象的检查功能,检查猫的对象
ad.check(c);
System.out.println("--------------------");
//创建一个狗的对象
Dog d = new Dog();
d.name = "阿黄";
d.age = 4;
//调用动物医生对象的检查功能,检查狗的对象
/*
check函数接收参数的变量是Animal类型的变量;
Animal是Dog类的父类,在给函数传参时,将Dog类型的对象d赋值给Animal 类型的变量,
是将子类对象赋值给父类引用变量,使用了多态
*/
ad.check(d);
}
}
21.png
结论:
1、使用多态可以提高程序的扩展性;
2、提高代码的复用性,降低程序的冗余性;
2.2.4. 多态的弊端
多态有好处,也会有弊端;
22.png
23.png
报错原因:编译期只进行语法检查,不会执行代码;
在编译期检查到30行代码时发现,要通过Fu类型的变量f调用一个函数func,就会去Fu类中找有没有这个方法,因为没有,所以会报错;
使用多态的弊端:
只能使用父类中已经定义的成员变量和函数,不能使用子类中独有的变量或函数;
2.3. 多态的类型转换
要解决多态的弊端,必须学习使用多态的类型转换:
2.3.1. 多态的类型转换
自动向上转型:
在使用多态时,将子类型对象赋值给父类型引用,相当于将对象的类型提升为父类型,叫做向上转型;因为这个过程可以自动进行,所以叫做自动向上转型;
如:
class Fu{}
class Zi extends Fu{}
Fu f = new Zi();//就是发生了自动向上转型
强制向下转型:
将父类型引用(对象)赋值给子类型引用,相当于将父类型向下转换成子类型,叫做向下转型;这个过程不能自动进行,需要强制进行,所以叫做强制向下转型;
如:
class Fu{}
class Zi extends Fu{}
Fu f = new Fu();
Zi z = (Zi)f;//这里将父类型引用转换为子类引用,发生了强制向下转型;
24.png
结论:使用强制向下转型,可以解决多态使用中的弊端;
2.3.2. 强制向下转型的问题
25.png
2.3.3. instanceof关键字
要避免强制向下转型中出现的问题,就应该在转型前先对要转型的引用进行检查,判断类型是否正确;
要判断一个引用指向的对象是否是某种类型,就要使用instanceof关键字:
用法:引用名 instanceof 类名
作用: 判断符号左边的引用指向的对象是否是右边这个类的对象;
根据这个关键字的用法,我们可以在对一个引用进行强制向下转型前进行判断,如果是某个类的对象,再强制转换,否则就不转换,就可以避免上面的问题了;
26.png
结论:在强制向下转型前先使用instanceof关键字进行判断,可以避免出现类型转换异常;
2.4. 多态中成员使用的特点
在使用多态时,是通过父类型引用操作子类对象的,这就会造成实际使用的成员和使用对象本身所属类型的引用操作对象有所不同,在实际开发中就要注意它们的不同;
27.png
编译时:使用的所有成员(静态和非静态的)都要检查父类中有没有;
28.png
29.png
运行时:只有非静态函数使用的是子类中的,其他成员全部都是父类的;
为方便大家记忆,将上面得到的结果总结成一句话就是:
多态中的成员,在编译期全部看父类,运行期只有非静态函数看子类;
2.5. 多态总结
多态的概念:同一个事物可以有多种不同的表示形态;
多态在Java中的体现:父类型引用指向子类实例对象;(接口类型引用指向实现类对象);
多态的前提:多态中的两个类型,必须有继承关系(或者接口实现关系);
多态的好处:使用多态可以提高代码复用性,降低维护成本;提高程序扩展性,降低程序的耦合性;
多态的弊端:父类型引用不能使用子类独有属性和功能;
多态弊端的解决:使用强制向下转型,将父类型引用转换为子类型引用;
多态的类型转换:将子类型引用赋值给父类型引用,是向上转型,因为父类型中的成员子类中都有,所以可以自动转换,叫做自动向上转型;
将父类型引用赋值给子类型引用,是向下转型,因为父类型中不一定具有子类型中的成员,所以需要强制转换,叫做强制向下转型;
强制向下转型容易遇到的问题:如果将不是该类的对象强制转换为该类型,就会出现一个运行时异常:类型转换异常;
避免类型转换异常的解决办法:在强制向下转型之前先使用关键字:instanceof 进行判断;
多态中成员的特点:多态发生在父类型引用和子类对象之间,所以静态成员不参与多态,编译和运行都看父类;
非静态成员,编译期都看父类,运行期,函数看子类;
3. 接口
3.1. 接口的引入
需求:分别描述以下三种动物的行为:
狗:吃;叫;
猫:吃;叫;
猪:吃;叫;
类的关系图解:
30.png
需求升级:
猫和狗,经过训练,学会的新的功能:表演;
31.png
3.2. 接口的代码体现:
3.2.1. 接口的声明格式
接口不是一个类,所以声明接口不能使用class关键字,而是使用interface关键字;
32.png
接口编译后也会生成一个class文件:
Inter.class
3.2.2. 接口中能书写的成员
接口中可以书写的成员和类不同,只能书写成员变量和函数;
而且成员变量和函数都有固定的修饰符修饰:
成员变量的修饰符是: public static final
函数的修饰符是: public abstract
在实际使用中,修饰符可以省略不写,但是编译器会添加;
但是不能写错了;
33.png
为了简单,一般函数和变量前面的修饰符都可以省略不写,但是关键字public不要省略;
(从JDK8开始,为了能对原来的接口扩展新的方法,允许接口中可以有默认实现的方法,使用default修饰;)
3.2.3. 接口的实现:
34.png
类和抽象类的关系是继承,而类和接口的关系是实现;
一个类实现一个接口,使用的是关键字:implements;
35.png
一个类继承一个类同时实现一个接口的写法:
先写继承,然后再跟接口的实现:
36.png
3.2.4. 多态使用接口:
一般开发中,使用接口,通过多态的形式使用接口;
接口的多态的体现:
接口类型引用指向实现类对象;
37.png
3.3. 接口的多实现&多继承
java中的类只能支持单一继承和多重继承,但是不等于java不支持多继承;java通过接口实现多继承;
3.3.1. 接口的多实现
一个类可以同时实现多个接口;
38.png
3.3.2. 接口的多继承
接口与接口之间是继承关系,一个接口可以继承多个接口
39.png
40.png
3、接口的使用思想
3.3.3. 接口的作用总结
1、接口用来描述不属于一个继承体系的额外的公共的功能;
2、通过多态使用接口,可以提高程序的扩展性;
3、接口实现的java的多继承;
4、接口可以定义规则;
3.3.4. 使用接口定义规则
需求:模拟电脑使用键盘和鼠标
3.4. 接口总结
接口的声明格式:
interfaceInter{}
接口中能书写的成员:
成员变量:全部都使用public static final 修饰;所以接口中的成员变量都是常量;
成员函数:全部都使用 public abstract 修饰;
接口中没有构造函数;
接口的细节:
接口不能实例化,只能被类实现;
实现接口的类必须实现接口的所有方法,否则实现类必须是抽象的;
接口与接口是继承的关系,一个接口可以继承多个接口;类和接口是实现的关系,一个类可以实现多个接口;
通常开发中使用接口类型的引用指向实现类的对象,这是多态的体现;
接口的作用:
描述非继承体系中的共性内容;
通过多态使用接口,可以提高程序的扩展性;
实现的java的多继承;
接口可以定义规则;
4. 接口和抽象类的区别
接口和抽象类的共同点:都可以有抽象函数,都不能实例化;
4.1. 接口和抽象类的区别
1、从声明上
i. 抽象类是一个类,需要使用关键字:class声明
ii. 接口不是一个类,使用关键字:interface声明
2、从能够书写的成员上看:
i. 抽象类可以书写类中的所有成员
ii. 接口中只能书写常量和抽象函数;
(从JDK8开始,接口中可以有实现的函数)
3、从有无构造函数上看:
i. 抽象类有构造函数,是给子类实例化对象使用的
ii. 接口没有构造函数
4、从作用上看:
i. 抽象类是描述的继承体系中的共同的特征和行为,如果行为不确定,就定义为抽象函数;
ii. 接口描述的是不属于继承体系的共同的功能;接口可以用来定义规则;
5、继承关系上:
i. 抽象类是一个类,只支持单一继承和多重继承;
ii. 接口和接口可以多继承和多重继承;接口和实现类可以多实现;
4.2. 接口和抽象类的使用练习
需求:描述手机和电脑,都有开机、关机和可以玩游戏的功能;
41.png
5. 适配器(Adaptor)设计模式
5.1. 什么是适配器
生活中的适配器
电源适配器:变压;把原来不符合使用的220伏的交流电转换成适合电脑使用的电压和直流电;
变压器:将不符合使用要求的电压转换成符合使用要求的电压;
调制解调器(猫):将电信号和数字信号进行相互转换;
网线转接器:将电脑上没有空间不能直接使用的水晶头转换为电脑上有空间使用的USB接口;
适配器,可以将原本不符合使用的转换成符合使用要求的东西;
适配器模式:
一种设计模式,解决的问题就是:
将原来不符合使用要求转换成符合使用要求的;
5.2. Java中的适配器
根据适配的内容不同,可以将适配器分为三类:
类的适配;对象的适配;接口的适配;
需求:看下列程序,分别书写实现类满足User类的需求;
interface Inter//接口
{
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
class User//用户类
{
public static void test1(Inter i){
i.method1();
i.method2();
}
public static void test2(Inter i){
i.method2();
}
public static void test3(Inter i){
i.method4();
}
}
原来的实现:
//需求:看下列程序,分别书写实现类满足User类的需求;
//需求:看下列程序,分别书写实现类满足User类的需求;
interface Inter//接口
{
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
class User//用户类
{
public static void test1(Inter i){
i.method1();
i.method2();
}
//要求:本方法中调用的method2方法和test1方法中调用的method2方法实现不同;
public static void test2(Inter i){
i.method2();
}
public static void test3(Inter i){
i.method4();
}
}
/*
因为要求再test2方法和test1方法接收的参数是不同类的对象,里面的method2方法的实现不同;
所以要重新定义一个类实现Inter接口,实现里面的method2方法;
*/
class InterImpl2 implements Inter
{
public void method2(){
System.out.println("2222222222222222222222222");
}
//其他方法再test1中没有使用到,不需要实现;但是因为是接口的实现类,所以还必须重写这些函数
//所以都使用空实现
public void method1(){}
public void method3(){}
public void method4(){}
public void method5(){}
}
class InterImpl1 implements Inter
{
public void method1(){
System.out.println("test1----method1");
}
public void method2(){
System.out.println("test2----method2");
}
//其他方法再test1中没有使用到,不需要实现;但是因为是接口的实现类,所以还必须重写这些函数
//所以都使用空实现
public void method3(){}
public void method4(){}
public void method5(){}
}
class Test
{
public static void main(String[] args)
{
//测试User类的test1方法,因为时静态函数,所以可以直接通过类名调用;
User.test1(new InterImpl1());//使用匿名对象的方式传参
//测试User类的test2方法
User.test2(new InterImpl2());
}
}
程序中的问题:
42.png
直接使用接口,会有麻烦,不方便使用,所以可以考虑使用适配器设计模式;
首先应该定义一个适配器类,然后具体的实现类不直接实现接口,而是和适配器类打交道;
43.png
接口的适配器,就是一个使用空实现实现了接口中所有抽象函数的抽象类;
44.png
通过适配器类,具体的业务逻辑类就可以只关心需要关心的功能,不需要实现的功能,就可以忽略;
5.3. 适配器小结
适配器的作用,就是适配、转换,将不符合使用要求的东西转换为符合使用要求的;
适配器设计模式,解决的是怎么将不符合使用要求的类、对象或接口转换为符合使用要求的;
根据适配的对象不同,可以将适配器分为类的适配、对象的适配和接口的适配;
接口的适配的步骤:
1、创建一个适配器类实现接口,在这个类中使用空实现实现接口中的所有抽象函数;
2、因为适配器类中的函数都是空实现的,创建该类的对象没有意义,所以要将适配器类定义为抽象类;
3、要使用适配器类,只需书写类继承适配器类即可;