黑马程序员技术交流社区
标题: 【石家庄校区】基础班day09-继承,抽象类 [打印本页]
作者: xiekai_sjz 时间: 2018-9-3 11:34
标题: 【石家庄校区】基础班day09-继承,抽象类
本帖最后由 xiekai_sjz 于 2018-9-9 10:04 编辑
day09 继承, super, this, 抽象类
今天我们继续学习面向对象的相关知识。首先我们先学习继承,Java中的继承作为Java面向对象三大特性之一,在知识点上而言,还是很多的。接下来我们会学习super和this关键字的使用,最后我们学习一种特殊的类-抽象类。最后我们会完成一个简单嗯对发红包小案例。
以下是今天的学习目标:
- 能够写出类的继承格式
- 能够说出继承的特点
- 能够说出子类调用父类的成员特点
- 能够说出方法重写的概念
- 能够说出super可以解决的问题
- 描述抽象方法的概念
- 写出抽象类的格式
- 写出抽象方法的格式
- 能够说出父类抽象方法的存在意义
- 能够完成发红包案例的代码逻辑
以下是今天的详细笔记:
继承
继承的概述继承主要解决的问题:
共性抽取 (避免类中重复代码)
父类: 被继承的类, 也叫作基类, 超类(super class).
父类, 爷爷类, 曾爷爷类, 都统称为父类
子类: 也叫作派生类, 导出类
子类可以拥有父类的内容
子类也可以有自己特有的内容
补充:
子类继承父类是is-a(是一个 / 是一种 / 属于)关系: 兔子 是一种 食草动物 狮子 是一种 食肉动物 食肉动物 是一种 动物 助教 是一种 员工 讲师 是一种 员工
继承的格式
extends: 继承关键字. 用在类的声明上
继承的格式:
public class 子类类名 extends 父类类名 {
}
5分钟练习: 练习继承
需求:
定义动物类(Animal), 类中定义方法吃eat(), 内部输出"吃东西"
定义猫类(Cat), 狗类(Dog), 都继承动物类
定义测试类, 在main()方法中, 创建Cat类对象, 调用吃的方法, 创建Dog类对象, 调用吃的方法
代码:
[Java] 纯文本查看 复制代码
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
public class Cat extends Animal {
}
public class Dog extends Animal {
}
/*
需求:
定义动物类(Animal), 类中定义方法吃eat(), 内部输出"吃东西"
定义猫类(Cat), 狗类(Dog), 都继承动物类
定义测试类, 在main()方法中, 创建Cat类对象, 调用吃的方法, 创建Dog类对象, 调用吃的方法
*/
public class Test {
public static void main(String[] args) {
// 创建猫
Cat cat = new Cat();
cat.eat();
// 创建狗
new Dog().eat();
}
}
继承中成员变量的访问特点
继承关系中, 父子类成员变量重名, 则:
直接通过子类对象访问成员变量: 创建对象时等号左边是哪个类, 就优先用哪个类的成员变量, 没有则向父类中找
间接通过成员方法访问成员变量: 该方法定义在哪个类, 就优先用哪个类中的成员变量, 没有则向父类中找
5分钟练习: 访问成员变量
需求:
定义Fu类, 类中定义如下:
成员变量: int num = 100; (不要加private)
成员方法: public void methodFu(), 方法中打印num
定义Zi类, 继承Fu类, Zi类中定义如下:
成员变量: int num = 200; (不要加private)
成员方法: public void methodZi(), 方法中打印num
定义测试类
创建Fu类对象, 通过Fu类对象调用num和methodFu(), 查看结果
创建Zi类对象, 通过Zi类对象调用num和methodZi(), 查看结果
[Java] 纯文本查看 复制代码
/*
定义Fu类, 类中定义如下:
成员变量: int num = 100; (不要加private)
成员方法: public void methodFu(), 方法中打印num
*/
public class Fu {
int num = 100;
public void methodFu() {
System.out.println(num);
}
}
/*
定义Zi类, 继承Fu类, Zi类中定义如下:
成员变量: int num = 200; (不要加private)
成员方法: public void methodZi(), 方法中打印num
*/
public class Zi extends Fu {
int num = 200;
public void methodZi() {
System.out.println(num);
}
}
/*
需求:
定义Fu类, 类中定义如下:
成员变量: int num = 100; (不要加private)
成员方法: public void methodFu(), 方法中打印num
定义Zi类, 继承Fu类, Zi类中定义如下:
成员变量: int num = 200; (不要加private)
成员方法: public void methodZi(), 方法中打印num
定义测试类
创建Fu类对象, 通过Fu类对象调用num和methodFu(), 查看结果
创建Zi类对象, 通过Zi类对象调用num和methodZi(), 查看结果
*/
public class Test {
public static void main(String[] args) {
// 创建父类对象
Fu fu = new Fu();
System.out.println(fu.num); // 100 等号左边是Fu类, 所以获取的是Fu类中的num
fu.methodFu(); // 100 methodFu()定义在Fu类, 所以获取的是Fu类中的num
// 创建子类对象
Zi zi = new Zi();
System.out.println(zi.num); // 200 等号左边是Zi类, Zi类中有num, 所以获取的是Zi类中的num
zi.methodZi(); // 200 methodZi()定义在Zi类, 所以获取的是Zi类中的num
}
}
区分子类方法中重名的三种变量
变量使用的就近原则:
如果方法中有局部变量, 则使用局部变量
如果方法中没有局部变量, 有本类的成员变量, 则使用本类的成员变量
如果方法中没有局部变量, 也没有本类的成员变量, 有父类的成员变量, 则使用父类的成员变量
如果局部变量, 本类成员变量, 父类成员变量都没有, 则报错
指定访问:
变量名: 局部变量(就近原则)
this.成员变量名: 可以访问本类的成员变量 (当本类没有, 也可以访问父类成员变量)
super.成员变量名: 可以访问父类的成员变量
this: 当前类对象的引用
super: 当前类对象的父类引用
补充:
[Java] 纯文本查看 复制代码
// 说出结果
public class Fu {
int num = 10; // 父类成员变量
}
public class Zi extends Fu {
int num = 20; // 子类成员变量
public void method() {
int num = 30; // 局部变量
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(super.num); // 10
}
}
继承中成员方法的访问特点
父子类中有相同方法的访问规则:
创建的对象是哪个类的, 就优先使用哪个类中定义的方法, 如果没有定义则向上找 (其实就是方法的重写)
(创建的是子类对象, 就优先使用子类中定义的方法)
5分钟练习:
需求:
定义Fu类, 类中定义如下:
成员方法: public void method(), 方法中打印"父类重名方法"
定义Zi类, 继承Fu类, Zi类中定义如下:
成员方法: public void method(), 方法中打印"子类重名方法"
定义测试类
创建Fu类对象, 通过Fu类对象调用method(), 查看结果
创建Zi类对象, 通过Zi类对象调用method(), 查看结果
代码:
[Java] 纯文本查看 复制代码
/*
定义Fu类, 类中定义如下:
成员方法: public void method(), 方法中打印"父类重名方法"
*/
public class Fu {
public void method() {
System.out.println("父类重名方法");
}
}
/*
定义Zi类, 继承Fu类, Zi类中定义如下:
成员方法: public void method(), 方法中打印"子类重名方法"
*/
public class Zi extends Fu {
public void method() {
System.out.println("子类重名方法");
}
}
/*
需求:
定义Fu类, 类中定义如下:
成员方法: public void method(), 方法中打印"父类重名方法"
定义Zi类, 继承Fu类, Zi类中定义如下:
成员方法: public void method(), 方法中打印"子类重名方法"
定义测试类
创建Fu类对象, 通过Fu类对象调用method(), 查看结果
创建Zi类对象, 通过Zi类对象调用method(), 查看结果
*/
public class Test {
public static void main(String[] args) {
// 创建父类对象
Fu fu = new Fu();
fu.method(); // 父类对象调用父类的方法 父类重名方法
// 创建子类对象
Zi zi = new Zi();
zi.method(); // 子类对象中有相同的方法, 调用的是子类中定义的方法 子类重名方法
}
}
继承中方法的覆盖重写: 概念与特点
方法的重写: Override, 也叫覆盖, 覆写.
在继承关系中, 方法名称和参数列表都相同, 这种情况称为方法重写
重写: Override. 继承关系中, 方法名相同, 参数列表也相同
重载: Overload. 方法名相同, 参数列表不同
继承中方法的覆盖重写: 注意事项
方法覆盖重写的注意事项:
1. 父子类之间 方法名 和 参数列表 必须都相同
2. 子类方法的 "返回值类型" 必须 "小于等于" 父类方法的返回值类型
3. 子类方法的 "权限修饰符" 必须 "大于等于" 父类方法的权限修饰符
权限修饰符按权限从大到小: public > protected > 默认(default) > private
@Override: 写在方法声明的上面, 用来检测是不是正确的覆盖重写
这个注解只是帮助检查. 就算不写, 只要满足重写的要求, 也是正确的方法覆盖重写
补充:
[Java] 纯文本查看 复制代码
public class Fu {
// 父类 默认权限, 返回值类型 Object
Object method() {
System.out.println("父类方法执行!");
return new Object();
}
}
public class Zi extends Fu {
// 子类 public比父类权限大, 返回值类型String比Object小
@Override
public String method() {
System.out.println("子类重写方法执行!");
return "a";
}
}
继承中方法的覆盖重写: 应用场景方法重写的应用场景:
子类增强父类的方法
5分钟练习: 利用方法覆盖重写增强子类功能
需求:
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
定义新款手机类(NewPhone), 类中重写父类show()方法, 使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
代码:
[Java] 纯文本查看 复制代码
/*
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
*/
public class Phone {
public void show () {
System.out.println("显示号码");
}
}
/*
定义新款手机类(NewPhone), 类中重写父类show()方法,
使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
*/
public class NewPhone extends Phone {
@Override
public void show() {
super.show(); // 调用老手机已有的功能
// 增加额外的新功能
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
/*
需求:
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
定义新款手机类(NewPhone), 类中重写父类show()方法,
使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
*/
public class Test {
public static void main(String[] args) {
// 创建老手机对象
Phone oldPhone = new Phone();
oldPhone.show(); // 老手机只能显示号码
System.out.println("-----------");
// 创建新手机对象
NewPhone newPhone = new NewPhone();
newPhone.show(); // 新手机既可以实现老手机功能, 还可以显示姓名和头像
}
}
继承中构造方法的访问特点
继承关系中, 构造方法的访问特点:
1. 子类构造方法中第一行默认隐含调用 super(); 子类一定会先调用的父类构造, 然后再执行的子类构造
2. 子类构造中, 可以通过 super(参数); 来调用父类任何构造
3. super(); 必须是子类构造方法的第一行语句. 不能多次调用super构造
4. 子类必须调用父类的构造方法, 不写则赠送无参的 super();
补充:
[Java] 纯文本查看 复制代码
public class Fu {
public Fu() {
System.out.println("父类无参构造");
}
public Fu(int num) {
System.out.println("父类有参构造!");
}
}
public class Zi extends Fu {
public Zi() {
super(); // 在调用父类无参构造方法
//super(20); // 在调用父类重载的构造方法
System.out.println("子类构造方法!");
}
public void method() {
// super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
}
}
this和supersuper关键字的三种用法
super关键字的3种用法:
1. 在子类的成员方法中, 访问父类的成员变量: super.age;
2. 在子类的成员方法中, 访问父类的成员方法: super.eat();
3. 在子类的构造方法中, 访问父类的构造方法: super();
补充:
[Java] 纯文本查看 复制代码
// 补全代码
public class Fu {
int num = 10;
public void method() {
System.out.println("父类方法");
}
}
public class Zi extends Fu {
int num = 20;
public Zi() {
// 访问父类无参构造方法
super();
}
public void methodZi() {
// 打印父类成员变量num
System.out.println(super.num);
}
public void method() {
// 访问父类成员方法method
super.method();
}
}
this关键字的三种用法
this关键字的3种用法:
1. 在本类的成员方法中, 访问本类的成员变量: this.age;
2. 在本类的成员方法中, 访问本类的其他成员方法: this.eat();
3. 在本类的构造方法中, 访问本类的其他构造方法: this(...);
在第三种用法当中要注意:
this(...); 调用也必须是构造方法的第一行语句, 也只能有唯一一个
super() 和 this() 两种构造调用, 不能同时使用
补充:
this.成员变量 / this.成员方法() 如果本类没有该成员变量/成员方法, 也可以调用父类继承过来的成员变量或方法
[Java] 纯文本查看 复制代码
// 补全代码
public class Zi extends Fu {
int num = 20;
public Zi() {
// 调用本类的构造方法 Zi(int num)
this(213);
}
public Zi(int num) {
this.num = num;
}
public void showNum() {
// 打印本类中的成员变量
System.out.println(this.num);
}
public void methodB() {
// 调用本类其他成员方法 showNum()
this.showNum();
}
}
super与this关键字图解
Java继承的三个特点Java中继承的3个特点:
1. Java只支持单继承, 不支持多继承
2. Java支持多层继承 (继承体系)
3. 一个父类可以有多个子类, 一个子类只能有一个直接父类
// 1. Java只支持单继承, 不支持多继承
class C extends A{} //正确
class C extends A,B {} //错误
// 2. Java支持多层继承(继承体系)
class Yeye {}
class Fu extends Yeye {}
class Zi extends Fu {}
抽象类抽象的概念
抽象方法: 没有方法体的方法
格式: 修饰符 abstract 返回值类型 方法名(参数列表);
抽象类: 包含抽象方法的类就是抽象类
格式: 修饰符 abstract class 类名 {}
[Java] 纯文本查看 复制代码
// 抽象类
public abstract class Animal {
// 抽象方法
public abstract void eat();
}
public class Cat {
public void eat() {
sout("吃老鼠");
}
}
new Cat().eat();
抽象方法和抽象类的使用
如何使用抽象类和抽象方法:
1. 抽象类不能创建对象
2. 必须用一个子类来继承抽象父类
3. 子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写要求:去掉 abstract, 补上方法体大括号{}
4. 创建子类对象调用方法
5分钟练习: 定义抽象类
需求:
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
定义猫类(Cat), 继承动物类, 重写吃的方法, 方法中输出"猫吃鱼"
代码:
[Java] 纯文本查看 复制代码
/*
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
*/
public abstract class Animal {
// 抽象方法吃
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
/*
需求:
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
定义猫类(Cat), 继承动物类, 重写吃的方法, 方法中输出"猫吃鱼"
*/
public class Test {
public static void main(String[] args) {
// 创建子类对象
Cat cat = new Cat();
cat.eat(); // 子类调用子类中重写的方法
}
}
抽象方法和抽象类的注意事项
抽象类注意事项:
1. 抽象类不能创建对象. 只能创建其非抽象子类的对象
2. 抽象类中可以有构造方法, 是供子类创建对象时, 初始化父类成员使用的(子类的构造方法有默认的super();)
3. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
4. 抽象类的子类, 必须重写抽象父类中所有的抽象方法. 除非该子类也是抽象类
群主发普通红包: 案例分析
需求:
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
2. 成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作
群主发红包 (发多少钱, 分多少份)
20 / 3 = 6
20 % 3 = 2
{6, 6, 8} ArrayList<Integer>
收红包 (ArrayList<Integer> list)
index Random
int money remove(i)
3个成员随机获取
群主发普通红包: 实现10分钟练习: 实现发红包
子类Manager, 继承User类:
无参/有参构造
发红包方法: public ArrayList<Integer> send(int totalMoney, int count) {}
a. 创建容器.
创建ArrayList<Integer>对象redList, 用于装入平分的金额
b. 判断余额.
getMoney()获取余额, 判断是否小于要发的金额totalMoney
如果小于, 则提示"余额不足", 并return redList空集合
否则继续
c. 从群主余额中扣除发出的红包钱
d. 平分红包金额
int avg = totalMoney / count; // 平均金额
int mod = totalMoney % count; // 平分的余数
for循环向红包集合中 count - 1 个 avg平均金额
添加最后一个金额 avg+mod
e. 返回红包集合
return redList;
3. 定义子类Member, 继承User类:
无参/有参构造
收红包方法: public void receive(ArrayList<Integer> list) {}
a. 生成随机索引
b. 利用索引从集合中删除一个金额, 获取被删除的金额作为我领到的红包
int money = list.remove(index);
c. 将红包金额累加到我的余额
super.setMoney(super.getMoney() + 红包金额);
4. 定义测试类MainRedPacket
创建群主Manager对象, 姓名为"群主", 余额100
创建3个成员Member对象, 姓名随意, 余额0
使用manager对象调用send()方法, 发出20元, 分3份, 获取到集合
使用3个成员对象, 分别调用receive(...)方法, 获取红包
调用show()查看效果
代码:
[Java] 纯文本查看 复制代码
/*
用户类
*/
public class User {
private String name; // 姓名
private int money; // 余额
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 显示信息
public void show() {
System.out.println("我是:" + name + ", 我的余额:" + money);
}
}
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
// 发红包
public ArrayList<Integer> send(int totalMoney, int count) {
// 创建红包集合
ArrayList<Integer> redList = new ArrayList<>();
// 先判断余额是否充足
int leftMoney = this.getMoney();
if (leftMoney < totalMoney) {
System.out.println("余额不足!");
return redList;
}
// 如果余额充足, 则扣除群主金额
this.setMoney(leftMoney - totalMoney);
// 分钱
int avg = totalMoney / count;
int mod = totalMoney % count;
// 除了最后一份钱, 其他都按照平均值
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
// 单独添加最后一份钱
redList.add(avg + mod);
// 返回集合
return redList;
}
}
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
// 收红包
public void receive(ArrayList<Integer> redList) {
// 生成随机索引
int index = new Random().nextInt(redList.size());
// 从集合中取出金额
int money = redList.remove(index);
// 将金额添加到我的余额
this.setMoney(this.getMoney() + money);
}
}
/*
发红包
*/
public class Test {
public static void main(String[] args) {
// 创建群主
Manager manager = new Manager("王健林", 100);
// 创建成员
Member member1 = new Member("奔波儿灞", 0);
Member member2 = new Member("霸波尔奔", 0);
Member member3 = new Member("英特尔", 0);
// 群主发红包
ArrayList<Integer> redList = manager.send(20, 3);
// 群员抢红包
member1.receive(redList);
member2.receive(redList);
member3.receive(redList);
// 展示结果
manager.show();
member1.show();
member2.show();
member3.show();
}
}
作者: 冯光 时间: 2018-9-3 18:55
多谢老师分享
作者: cuipu 时间: 2018-9-3 19:50
谢谢谢美女!
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |