一、继承
1.1概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要 继承那一个类即可。
其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是: is-a
的关系。例如,图中兔子属于食草动物,食草动物属于动 物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系
定义:
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接 访问父类中的非私有的属性和行为
继承主要解决的问题就是:共性抽取
好处:
1.提高代码的复用性。
- 类与类之间产生了关系,是多态的前提
1.3 继承的格式
通过extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类{
....
}
class 子类 extends 父类{
....
}
具体如下:
//定义一个父类:员工
public class Employee {
public void method(){
System.out.println("方法执行");
}
}
//定义了一个员工的子类:讲师
public class Teacher extends Employee {
}
/*
在继承的关系中,“子类is-a父类“。
*/
public class Demo01Extends {
public static void main(String[] args) {
//创建一个子类对象
Teacher teacher= new Teacher();
//Teacher继承了父类的method方法
teacher.method(); //方法执行
//创建另一个子类对象
Assistant assistant=new Assistant();
assistant.method();
}
}
1.3 继承后的特点—成员变量
如果子类和父类的变量没有重名则不影响,直接调用即可
但是如果子类和父类的变量名重名了,那么如下:
package roderick.day09.demo02;
/*
在父子类的继承关系当中,如果成员变量充满,则创建子类对象时,有两种访问方式:
直接->子类.成员变量
等号左边是谁就优先用谁,没有则向上找
间接->成员方法访问成员变量
该方法属于谁,就优先用谁,没有则向上找
*/
class Fu {
int num=5;
public void showFu(){
System.out.println("num="+num);
}
}
class Zi extends Fu{
int num=6;
public void show(){
//访问父类中的num
System.out.println("Fu num="+num);
//访问子类中的num2
System.out.println("Zi num="+num);
}
}
public class Extends {
public static void main(String[] args) {
//创建子类对象
Zi z=new Zi();
//调用子类中的show方法
z.show();
// Fu num = 6
// Zi num = 6
z.showFu();
//num = 5
}
}
如果是 对象名.成员变量 调用则对象名是谁的就优先用谁的 如果是通过方法来调用则 方法属于谁优先用谁的
Super关键字->解决重名问题
如果父子类中出现重名变量,在子类中访问父类非私有变量的情况,可以使用super
关键字,类似之前学过的this
super.父类成员变量名
class Zi extends Fu{
//Zi中的成员变量
int num=6;
public void show(){
//访问父类中的num
System.out.println("Fu num="+ super.num);
//访问子类中的num
System.out.println("Zi num="+ this.num);
}
}
小贴士:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能 直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员 变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。
1.4 继承后的特点—成员方法
继承关系中,无论是成员方法还是成员变量,都是向上找的
成员方法重名—重写(Override)
另称—>覆盖重写(推荐)
方法重写 :
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效 果,也称为重写或者复写。声明不变,重新实现
这里要和方法重载区别开来,重载是参数列表不一样
要点:@Override
代码如下
// @Override -->安全监测覆盖重写是否正确,写在方法前面
class Fu{
public void show(){
System.out.println("Fu show");
}
}
@Override
class Zi extends Fu{
//子类重写了父类的show方法
public void show(){
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args){
Zi z = new Zi();
//子类中有show方法,只执行覆盖重写后的show方法
z.show();//Zi show
}
}
小贴士:子类方法的返回值必须小于等于父类方法的返回范围;->使用@Override可监测
注意:
子类 方法的全选必须【大于等于】父类方法的权限修饰符。
public > protected > (default) > private
PS: default 修饰符不需要写-> (default) class xxx{ }
1.5 继承后的特点—构造方法
首先我们要回忆两个事情,构造方法的定义格式和作用
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构 造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代 码如下:
class Fu{
private int n;
Fu(){
System.out.println("Fu()");
//无参构造
}
}
class Zi extends Fu{
Zi(){
//super () 调用父类构造方法
//先执行父类初始化
super();//->不写的话默认有一个super();
System.out.prinln("Zi()");
}
}
public class ExtendsDemo07{
publlic static void main(String args[]){
Zi zi=new Zi();
}
}
输出结果:
Fu ()
Zi ()
注意:
- 如果父类中没有无参构造而是有参构造,则需要在子类无参构造中重载
super(参数)
super()
必须在子类构造方法的第一行
1.6 this 和 super
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。
目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构 造方法调用时,一定先调用父类的构造方法。理解图解如下:
super 和 this的用法
1.访问成员
this.成员变量 --- 本类的
super.成员变量 --- 父类的
this.成员方法名() --- 本类的
super.成员方法名() ---父类的
2.访问构造方法
this(...) --本类的构造方法
super(...) --父类的构造方法
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现
1.7 继承的特点
1.Java只支持单继承,不支持多继承
//一个类只能有一个父类,不能有多个
class C extends A{} //ok
class C extends A,B... //error
- Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
顶层父类是Object类(祖先),所有的类默认继承Object
3.子类和父类是一种相对的概念;
二、抽象类
2.1 概述
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法 的类就是抽象类
父类中的方法不确定如何进行{ }方法体实现,那么这就应该是一个抽象方法
- 抽象方法:没有方法体的方法
- 抽象类:包含抽象方法的类
2.2 abstract使用格式
抽象方法
使用 abstract
关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式
修饰符 abstract 返回值类型 方法名(参数列表);
public abstract void eat();
抽象类
如果一个类包含抽象方法,那么该类必须是抽象类
定义格式:
abstract class 类名字{
}
public abstract class Animal{
public abstract void eat();//抽象方法
}
抽象方法的使用
继承抽象类的子类必须覆盖重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父 类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
@Override
public class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
}
public class CatTest{
public static void main(String[] args){
//创建子类对象
Cat c= new Cat();
//调用eat方法
c.eat();
}
}
输出结果
猫吃鱼
2.3 注意事项
1.抽象类不能创建对象,如果创建则报错,只能创建其非抽象子类的对象
假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义
2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
子类的构造方法中,有默认的super(),需要访问父类构造方法。
3.抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设 计。
4.抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。
假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义。
三、继承的综合案例
3.1 综合案例:群主发普通红包
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
- 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
- 成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。
import java.util.ArrayList;
import java.util.Random;
class User{
//定义成员变量
private String name; //用户名
private int leftMoney; //余额
//构造方法
public User(){}
public User(String name, int leftMoney) {
this.name = name;
this.leftMoney = leftMoney;
}
public void show()
{
System.out.println("我叫:"+this.name+"我的余额:"+this.leftMoney);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLeftMoney() {
return leftMoney;
}
public void setLeftMoney(int leftMoney) {
this.leftMoney = leftMoney;
}
}
class Manager extends User{
public Manager() {}
//调用父类构造方法
public Manager(String name, int leftMoney) {
super(name, leftMoney);
}
public ArrayList<Integer> send(int totalMoney,int count)
{
//需要一个集合,存储若干个红包的金额
ArrayList<Integer> list=new ArrayList<>();
//获取群主余额,是否够发红包
//不能就返回null,并提示,能则继续
int leftMoney=super.getLeftMoney();//群主当前余额
if(totalMoney>leftMoney)
{
System.out.println("余额不足");
return null;
}
//设置群主余额
super.setLeftMoney(leftMoney-totalMoney);
//发红包拆分为count份
int avg=totalMoney/count;
int mod=totalMoney%count; //余数。分给最后一个红包
for (int i = 0; i < count-1; i++) {
list.add(avg);
}
//最后一个红包
int last=avg+mod;
list.add(last);
return list;
}
}
class Staff extends User{
public Staff(){}
public Staff(String name, int leftMoney) {
super(name, leftMoney);
}
public void receive(ArrayList<Integer> list){
//从多个红包当中抽取一个,给我自己
//随机获取一个集合当中的索引编号
int index = new Random().nextInt(list.size());
//根据索引,从集合中删除,并且得到被删除的红包,
int delta=list.remove(index);
//当前成员本有多少钱
int money=super.getLeftMoney();
//加法,并且设置回去
super.setLeftMoney(money+delta);
}
}
public class Demo04TestRedPacket {
public static void main(String[] args) {
Manager manager=new Manager("群主",100);
Staff one=new Staff("A",0);
Staff two=new Staff("B",0);
Staff three=new Staff("C",0);
Staff four=new Staff("D",0);
manager.show();
one.show();
two.show();
three.show();
four.show();
System.out.println("------------------------------");
//获取群主发的红包
ArrayList<Integer> redList =manager.send(22,4);
//四个成员去收红包
one.receive(redList);
two.receive(redList);
three.receive(redList);
four.receive(redList);
manager.show();
one.show();
two.show();
three.show();
four.show();
}
}
输出结果:
我叫:群主 我的余额:100
我叫:A 我的余额:0
我叫:B 我的余额:0
我叫:C 我的余额:0
我叫:D 我的余额:0
------------------------------
我叫:群主 我的余额:78
我叫:A 我的余额:7
我叫:B 我的余额:5
我叫:C 我的余额:5
我叫:D 我的余额:5