一、抽象类
1、抽象类是什么?
-
在Java中
abstract
是抽象的意思,可以修饰类、成员方法。 -
abstract
修饰类:这个类就是抽象类; -
abstract
修饰方法:这个方法就是抽象方法。 -
语法格式:
修饰符 abstract class 类名{ 修饰符 abstract 返回值类型 方法名(形参列表); }
public abstract class Animal{ public abstract void run(); }
2、抽象的注意事项
-
抽象方法只有方法签名,不能声明方法体。
-
一个类中如果定义了抽象方法,这个类也必须声明为抽象类,否则会报错!!
3、抽象的应用场景
-
抽象类可以理解成一张不完整的设计图,一般作为父类,让子类来继承。
-
当父类知道子类一定要完成某些行为,但是每个子类要实现的该行为都有所不同,因此父类就把该行为定义为抽象方法的形式,具体实现交给子类去完成。此时这个类就可以声明为抽象类。
4、知识总结
1、抽象类、抽象方法是什么样的?
- 都是有 abstract关键字修饰的;抽象方法只有方法签名,不能写方法体,给子类自己实现方法体;
- 一个类中一旦定义了抽象方法,这个类必须也声明为抽象类。
2、抽象类基本作用是啥?
- 作为父类,用来被子类继承;
- 当父类知道子类一定要完成某个行为,但由于每个子类要完成的行为都有差别,此时就可以将此行为声明为抽象方法的形式。
3、继承抽象类有哪些要注意的?
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类中的全部抽象方法,否则这个类也必须声明为抽象类。
二、抽象类的案例
1、案例:加油站支付卡
-
系统需求:
- 某加油站推出了2种支付卡:
- 1、预存1万元的金卡,后续加油享受8折优惠;
- 2、预存5千元的银卡,后续加油享受8.5折优惠。
- 请分别实现2种卡片进入收银系统后的逻辑,卡片需要包含主人的名称、金额、支付功能。
- 某加油站推出了2种支付卡:
-
分析实现:
- 1、创建一个卡片类,作为父类,属性包含有名称、金额,行为包含有支付功能:由于2种卡片的加油优惠都不一样,因此需定义为抽象方法,让2种卡片的子类自己来完成各自的需求;由于在父类中定义了抽象方法,所以此类也要声明为抽象类;
- 2、创建一个金卡类,作为子类,继承父类(卡片类),继承后,需重写父类中的全部抽象方法,实现预存为1万元,后续加油支付将享受8折的优惠;
- 3、创建一个银卡类,作为子类,继承父类(卡片类),继承后,需重写父类中的全部抽象方法,实现预存5千元,后续加油支付将享受8.5折的优惠;
- 4、创建一个测试类,分别模拟创建2种卡的主人,传入姓名、金额后,使用各自子类的支付功能,完成支付。
package com.app.d7_abstract_test; /** 1、创建一个卡片类,作为父类, 由于在父类中定义了抽象方法,所以此类也要声明为抽象类。 */ public abstract class Card { /** a、属性包含有名称、金额 */ private String name; private double money; /** b、行为包含有支付功能: 由于2种卡片的加油优惠都不一样,因此需定义为抽象方法, 让2种卡片的子类自己来完成各自的需求。 */ public abstract void pay(double money); /** c、提供成员变量对应的getter、setter方法,暴露其取值和赋值 */ public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
package com.app.d7_abstract_test; /** 2、创建一个金卡类,作为子类, 继承父类(卡片类)。 */ public class GoldCard extends Card{ /** a、继承后,需重写父类中的全部抽象方法, 实现预存为1万元,后续加油支付将享受8折的优惠。 */ @Override public void pay(double consumption) { // 提示一下用户的消费金额和卡片余额 System.out.println("您当前消费金额: " + consumption); System.out.println("您卡片当前余额: " + getMoney()); // (1) 当用户卡片余额不够1万元,无法享受8折优惠 if (getMoney() < 10000) { System.out.println("亲爱的" + getName() +",非常抱歉~ 您卡片预存金额不足1万元,不是金卡用户,无法享受加油的8折优惠!!"); System.out.println("实际支付: " + consumption + "元"); // 消费后,更新余额 setMoney(getMoney() - consumption); }else { // (2) 用户卡片余额达到1万元后,加油享受8折优惠 double result = consumption * 0.8; System.out.println("亲爱的" + getName() + ",由于您卡片预存金额达到了1万元,是金卡用户,可以享受加油的8折优惠!!"); System.out.println("实际支付: " + result + "元"); // 消费后,更新余额 setMoney(getMoney() - result); } } }
package com.app.d7_abstract_test; /** 3、创建一个银卡类,作为子类,继承父类(卡片类)。 */ public class SilverCard extends Card{ /** a、继承后,需重写父类中的全部抽象方法, 实现预存5千元,后续加油支付将享受8.5折的优惠。 */ @Override public void pay(double consumption) { // 提示一下用户的消费金额和卡片余额 System.out.println("您当前消费金额: " + consumption); System.out.println("您卡片当前余额: " + getMoney()); // (1) 当用户卡片余额不够5千元,无法享受8折优惠 if (getMoney() < 5000) { System.out.println("亲爱的" + getName() +",非常抱歉~ 您卡片预存金额不足5千元,不是银卡用户,无法享受加油的8.5折优惠!!"); System.out.println("实际支付: " + consumption + "元"); // 消费后,更新余额 setMoney(getMoney() - consumption); }else { // (2) 用户预存的金额达到5千元后,加油享受8.5折优惠 double result = consumption * 0.85; System.out.println("亲爱的" + getName() + ",由于您卡片预存金额达到了5千元,是金卡用户,可以享受加油8.5折优惠!!"); System.out.println("实际支付: " + result + "元"); // 消费后,更新余额 setMoney(getMoney() - result); } } }
package com.app.d7_abstract_test; /** 4、创建一个测试类 目标:学习一下抽象类的基本使用:做父类、被继承、重写父类的抽象方法 */ public class CardTest { public static void main(String[] args) { /** a、分别模拟创建2种卡的主人, 传入姓名、金额后,使用各自子类的支付功能,完成支付 */ // (1) 金卡用户 System.out.println("----------金卡用户----------"); GoldCard goldCardUser = new GoldCard(); goldCardUser.setName("周卫国"); // 模拟金卡用户卡片余额不足1万元 goldCardUser.setMoney(9999); // 卡片余额 double consumption1 = 200; // 消费金额 goldCardUser.pay(consumption1); // 消费后的余额 System.out.println("您卡片剩余金额: " + goldCardUser.getMoney()); System.out.println("-------------------"); // 模拟金卡用户预存金额足1万元 goldCardUser.setMoney(11000); // 卡片余额 double consumption2 = 456; // 消费金额 goldCardUser.pay(consumption2); System.out.println("您卡片剩余金额: " + goldCardUser.getMoney()); System.out.println("\n====================================\n"); // (2) 银卡用户 System.out.println("----------银卡用户----------"); SilverCard silverCardUser = new SilverCard(); silverCardUser.setName("陈怡"); // 模拟银卡用户预存金额不足5千元 silverCardUser.setMoney(4500); // 卡片余额 double consumption3 = 500; // 消费金额 silverCardUser.pay(consumption3); System.out.println("您卡片剩余金额: " + silverCardUser.getMoney()); System.out.println("-------------------"); // 模拟银卡用户预存金额足5千元 silverCardUser.setMoney(5500); // 卡片余额 double consumption4 = 300; // 消费金额 silverCardUser.pay(consumption4); System.out.println("您卡片剩余金额: " + silverCardUser.getMoney()); } }
----------金卡用户---------- 您当前消费金额: 200.0 您卡片当前余额: 9999.0 亲爱的周卫国,非常抱歉~ 您卡片预存金额不足1万元,不是金卡用户,无法享受加油的8折优惠!! 实际支付: 200.0元 您卡片剩余金额: 9799.0 ------------------- 您当前消费金额: 456.0 您卡片当前余额: 11000.0 亲爱的周卫国,由于您卡片预存金额达到了1万元,是金卡用户,可以享受加油的8折优惠!! 实际支付: 364.8元 您卡片剩余金额: 10635.2 ==================================== ----------银卡用户---------- 您当前消费金额: 500.0 您卡片当前余额: 4500.0 亲爱的陈怡,非常抱歉~ 您卡片预存金额不足5千元,不是银卡用户,无法享受加油的8.5折优惠!! 实际支付: 500.0元 您卡片剩余金额: 4000.0 ------------------- 您当前消费金额: 300.0 您卡片当前余额: 5500.0 亲爱的陈怡,由于您卡片预存金额达到了5千元,是金卡用户,可以享受加油8.5折优惠!! 实际支付: 255.0元 您卡片剩余金额: 5245.0 Process finished with exit code 0
三、抽象类的特征和注意事项小结
-
类有的成员(成员变量、方法、构造器)抽象类都具备;
-
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类;
-
一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类;
-
不能用 abstract关键字修饰变量、代码块、构造器;
-
重要: 得到了抽象方法,失去了创建对象的能力(有得有失),为什么?
// 为什么抽象类不能创建对象?? // 反证法:假如抽象类可以创建对象 // 这句代码就会可行 Animal a = new Animal(); // 那当调用抽象方法可行吗?——> a.run(); // 我们现在学过抽象类,所以都知道,抽象方法是没有方法体的,因此不能创建对象
四、final和abstract之间的关系
-
互斥关系
-
为什么?
-
因为abstract定义的抽象类是作为模板让子类继承;final定义的类则不能被继承;
-
细细区分:
-
先想想 final修饰的类是不是就绝育了?因此不能被继承,也就是不可能有儿子(子类);
-
再想想 abstract修饰的类是必须要被继承的,因此是一定有儿子(子类)的;
-
一个不能有儿子;一个必须有儿子;
-
再想想 final修饰的方法为最终方法,因此不能被重写;
-
再想想 abstract修饰的方法则不是最终方法,而且作为模板让子类实现此方法功能,因此必须被重写;
-
一个不能重写;一个必须重写;
-
结论:这俩就是对头关系,老死不相往来!
-
五、抽象类的应用知识:模板方法模式
1、模板方法模式的应用场景
-
说明:
- 当系统中出现一个功能多处开发,而该功能中大部分代码是一样的,只是其中部分可能不同的时候。
-
模板方法模式实现步骤:
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码;
- 模板方法中就是将不能确定的功能定义成抽象方法让具体子类去实现。
2、案例:写作文
-
通过写作文案例来理解模板方法模式的思想和设计方案:
-
需求:
- 现在有两类学生,分别是中学生、小学生,他们都要写《我的爸爸》这篇作文;
- 要求每种类型的学生,标题第一段和最后一段,内容必须一样,正文部分自己发挥;
- 请选择最优的面向对象方案进行设计。
-
分析实现:
-
1、创建学生类,作为父类,修饰为抽象类,让子类继承;
-
a、作文模板方法: 标题、开头、正文、结尾:
- (1) 标题
- (2) 开头
- (3) 正文
- (4) 结尾
-
-
2、创建小学生类,作为子类,继承父类(学生类);
- a、继承后:重写正文部分的抽象方法,发挥自己的写作水平,写出作文正文部分。
-
3、创建中学生类,作为子类,继承父类(学生类);
- a、继承后:重写正文部分的抽象方法,发挥自己的写作水平,写出作文正文部分。
-
4、创建测试类;
-
a、分别创建中学生、小学生的对象;
-
b、调用方法,输出他们写好的作文。
-
package com.app.d8_abstract_template; /** 1、创建学生类,作为父类,修饰为抽象类,让子类继承。 */ public abstract class Student { /** a、作文模板方法: 标题、开头、正文、结尾 */ public void write() { // (1) 标题 System.out.println("\t\t\t\t《我的爸爸》"); // (2) 开头 System.out.println("\t我的爸爸是一个既让我害怕,又让我喜欢的人。"); // (3) 正文 System.out.print(text()); // 正文 // (4) 结尾 System.out.print("\n\t因为有了爸爸的爱,所以我变得坚强无比;" + "因为我的爸爸很牛,\n开车不看红绿灯的,下辈子我还要做他的儿子..."); } /** b、定义正文部分为抽象方法,让子类自己发挥实现; */ public abstract String text(); }
package com.app.d8_abstract_template; /** 2、创建小学生类,作为子类,继承父类(学生类); */ public class PrimarySchoolStudent extends Student { /** a、继承后:重写正文部分的抽象方法,小学生发挥自己的写作水平,写出作文正文部分。 */ @Override public String text() { return "\t他总喜欢起早贪黑,月亮不睡他不睡...\n" + "\t懵懵懂懂他已经白发苍苍!!"; } }
package com.app.d8_abstract_template; /** 3、创建中学生类,作为子类,继承父类(学生类); */ public class MiddleSchoolStudent extends Student { /** a、继承后:重写正文部分的抽象方法,中学生发挥自己的写作水平,写出作文正文部分。 */ @Override public String text() { return "\t他就像一个金刚,任风吹雨打都不烂...."; } }
package com.app.d8_abstract_template; /** 4、创建测试类; 目标:理解模板方法模式的思想和设计方案; */ public class TemplateTest { public static void main(String[] args) { // a、分别创建中学生、小学生的对象; PrimarySchoolStudent primaryStu = new PrimarySchoolStudent(); MiddleSchoolStudent middleStu = new MiddleSchoolStudent(); // b、调用方法,输出他们写好的作文。 System.out.println("小学生的作文:"); primaryStu.write(); System.out.println("\n------------------------------------------------------------------"); System.out.println("中学生的作文:"); middleStu.write(); } }
小学生的作文: 《我的爸爸》 我的爸爸是一个既让我害怕,又让我喜欢的人。 他总喜欢起早贪黑,月亮不睡他不睡... 懵懵懂懂他已经白发苍苍!! 因为有了爸爸的爱,所以我变得坚强无比;因为我的爸爸很牛, 开车不看红绿灯的,下辈子我还要做他的儿子... ------------------------------------------------------------------ 中学生的作文: 《我的爸爸》 我的爸爸是一个既让我害怕,又让我喜欢的人。 他就像一个金刚,任风吹雨打都不烂.... 因为有了爸爸的爱,所以我变得坚强无比;因为我的爸爸很牛, 开车不看红绿灯的,下辈子我还要做他的儿子... Process finished with exit code 0
-
-
3、模板方法建议使用final修饰
这样会更专业,那么为什么?
- 答:
- 模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了模板方法,则失效了,因此加上final后可以防止子类重写模板方法,这样更安全、严谨、优雅、专业!!
- 模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了模板方法,则失效了,因此加上final后可以防止子类重写模板方法,这样更安全、严谨、优雅、专业!!
4、案例总结
1、模板方法模式解决了什么问题?
- 提高了代码的复用性;
- 模板方法已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,交个子类实现,因此,使用者只需要关心自己需要实现的功能即可。