方法重写与多态

前言

本文为华为开发者学堂-面向对象编程的课程笔记,仅供个人学习使用。

方法重写

我们为什么需要方法重写?

有一个父类/超类叫pet,它有两个子类dog和cat,dog和cat有共同的属性名字和性别,有共同的输入和输出信息方法setInfo()和showInfo(),将这些属性和方法都写在父类里。现在有新的要求,dog增加了一个属性是品种,cat增加了一个属性是体重,这时候发现方法原来的写法不能同时满足两个子类不同属性的输出,此时我们提出三种解决方案:

  1. 删除父类的方法,在子类内分别写方法并调用:这种方法简单粗暴,但是一旦像这样的同时具有相同和不同属性与方法的子类增多,会使得代码有很多重复的部分,增大了工作量。
  2. 保留父类的方法,同时将不同的部分分别写入子类专属的方法内:这种方法较上一种的工作量减少了,但是在调用的时候会变得麻烦,既然都是输入输出信息的功能,为什么不让它们名称相同呢?
  3. 子类重写父类方法:
    • 子类根据需求对从父类继承的方法进行重新编写
    • 重写时,可以用super.方法的方式保留父类的方法
    • 构造方法不能被重写

方法重写的规则

  • 方法名相同
  • 参数列表相同
  • 返回值类型相同或是其子类
  • 访问权限不能严于父类
  • 父类的私有方法不能被子类覆盖
  • 子类方法不能抛出比父类方法更多的异常
  • 父类的静态方法不能被子类覆盖为非静态方法,父类的非静态方法也不能被子类覆盖为静态方法
  • 由于super不能在静态方法中使用,因此子类可以定义与父类同名的静态方法,以便在子类中隐藏父类的静态方法

代码示例(因仅供个人学习用,因此略写了测试类,且由于两个子类大同小异,故只给出cat类(说什么我都开摆!

父类写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pet {//父类
//按照方法1的写法,这里可以啥都没有x
public String name;//方法2中这里要保留
public String sex;
//方法3写法(方法2中父类的写法和方法3一致,故略)
public void setInfo(String name, String sex){
this.name=name;
this.sex=sex;
}
public void showInfo(){
System.out.println("宠物的名字是:"+name+",宠物的性别是:"+sex);
}
}

子类写法

1
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
public class Cat extends Pet{//子类
private float weight;//体重
private String name;//名字
private String sex;//性别
/*方法1写法
public void setInfo(String name,String sex, float weight){
this.name = name;//这里实际上略写了name和sex的声明,这两个属性的声明可以在父类中以public关键字做保留,也可以在子类中写
this.sex = sex;
this.weight = weight;
}
public void showInfo(){
System.out.println("宠物的名字是:"+name+",宠物的性别是:"+sex+",猫的体重是"+weight);
}
*/
/*方法2写法
public void setInfo(String name,String sex, float weight){
this.name = name;
this.sex = sex;
this.weight = weight;
}
public void showCatInfo(){//注意此处的改名
showInfo();
System.out.println("猫的体重是"+weight);
}
*/
//方法3写法

public void showInfo(){
super.showInfo();
System.out.println("猫的体重是"+weight+"kg");
}
}

Super

  • super可以用来访问父类的非私有(private)成员
  • 静态方法中不能出现super
  • 当子类中定义了和父类同名的成员时,super可以使被屏蔽的成员可见
  • super只能出现在子类的方法和构造方法中
  • super调用父类构造方法时,只能是super所在方法的第一句
  • super可以用于调用继承关系中最近的父类/间接父类的成员
    • 当子类、父类、间接父类中都有同名的成员时(子类重写/覆盖父类,父类重写/覆盖间接父类)时,子类中使用super只能访问父类的成员。
    • 当子类和间接父类中有同名成员时,子类中使用super可以访问继承关系中最近的间接父类成员。
1
2
3
4
5
6
7
//访问父类方法
super.print();
//访问父类属性
super.name;
//访问父类构造方法
super();
super(name)

super和this比较

区别 this super
访问属性 访问本类属性,如果没有则从父类找 访问父类属性
访问普通方法 访问本类普通方法,如果没有则从父类找 访问父类普通方法
访问构造方法 调用本类构造,放在构造方法首行 调用父类构造,放在子类构造方法首行

继承条件下的构造方法

  • 当子类构造方法没有通过super显式调用父类的带参构造方法,也没有通过this显式调用自身其他构造方法时,系统默认调用父类的无参构造方法
  • 相对的,当子类构造方法通过super显式调用父类的带参构造方法时,系统执行父类的带参构造方法而不执行父类的无参构造方法
  • 子类构造方法通过this显式调用自身的其他构造方法,在相应构造方法中应用上述两条规则
  • 注意,子类的构造方法在执行时,无论是否有显式给出,父类的构造方法在逻辑顺序上永远是第一个执行的

Object类

Object类是什么?

  • Object类是所有类的直接或间接父类,而且是继承关系最远的那个直接或间接父类

    Object类有什么?

  • 自己找个IDE(比如myeclipse),新建类然后写个this,Object类里的东西就在提示弹窗里显示出来了
  • Object类被子类经常重写的方法
    • toString(): 返回当前对象本身的有关信息,按字符串对象返回
    • equals(): 比较两个对象是否是同一个对象,是则返回true
    • hashCode(): 返回该对象的哈希值
    • getClass(): 获取当前对象所属的类信息,返回Class对象

此处的小补充

1
2
3
4
5
6
7
//equals函数判断是否是一个对象的依据是看两个对象是否占用同一处内存
//这里假设有一个Student类
Student s1 = new Student();
Student s2 = new Student();
Student s3 = s1;
System.out.println(s1.equals(s3));//结果为true
System.out.println(s1.equals(s2));//结果为false,因为二者成员虽然一样,但在不占用同一处内存。

重写Object类的方法(此处以重写equals方法为例)

我们接着上面的问题来,如果两个学生类的所有属性都一样,按理说它们指的就是一个人,但按照equals()的原理却不能认为两个对象相同,即当认为属性相同就是同一对象时,就有了重写equals()的需要。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//如果两个学生的属性(此处为姓名name和学号sid)相同,就认为是同一个学生对象。
public boolean equals(Object obj){//注意,此处是Student的equals方法。
//如果占用的是同一处内存
if(this == obj){
return true;
}
//此处应用instanceof运算符,“对象a instanceof 类型b”的意思是判断a是否为b的实例
elif(!(obj instance of Student)){//如果对象不是Student类型
return false;
}
Student s = (Student)Obj;//将Obj强制转换为student类型,这里实际上也可以省去这一步,但是这样更保险一些。
if (this.name==s.name&&this.sid==s.sid)//这里判断name是否相同还有一种写法就是“this.name.equals(s.name)”,由此可见Java.lang.String已经重写过一遍equals方法,使其可以用于判断字符串是否相同。
return true;
else
return false;
}

多态

我们为什么需要多态?

接着上面说过的dog、cat和pet类继续讲,现在我们设定pet类增加了健康值health这个属性,当健康值小于50时,认定宠物生病了,需要治疗,我们将治疗方法cure()放在新建的主人类master里,治疗方法的内容是显示治疗方案和修改宠物实例的health值为60。而不同的宠物需要不同的治疗方式,我们设定治疗猫时需要显示“打针”,治疗狗时需要显示“吃药”。我们可以使用方法重载来实现,主人类的代码此时如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Master{
public void cure(Cat cat){
if(cat.health<50){
System.out.println("打针");
cat.health=60;
}
}
public void cure(Dog dog){
if(dog.health<50){
System.out.println("吃药");
dog.health=60
}
}
}

但当宠物的种类变多时,这种写法会需要频繁修改,更不用说如果不止是主人类需要有这个cure方法,还有其他和主人类类似的类需要有这个cure方法时,就会导致代码臃肿、可扩展性、可维护性差,因此我们需要多态来优化。

什么是多态?

多态:同一个引用类型,使用不同的实例而执行不同的操作
使用父类作为方法的形参,是Java中实现和使用多态的主要方式。
使用父类作为方法的返回值,也是Java中实现和使用多态的主要方式。
我们接着来看刚才的例子。我们的优化思路是将治疗方法cure移到父类pet内,pet的各个子类重写父类的pet方法,此时主人类、父类、子类的代码简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Master{//主人类写法
public void cure(Pet pet){//父类作为方法的形参
if(pet.health<50){
pet.cure();
}
}
}
public class Pet{//父类写法
public int health;
public String name;
public String sex;
public void cure(){
//这里不要写属性的修改,因为修改的是子类的属性(我好像在说废话x)
}
}
public class Cat extends Pet{//子类写法
public void cure(){
System.out.println("打针");
this.heatlh=60;
}
}

这里附上一个父类作为方法的返回值的多态用法,假设我们要送动物给别人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Master{//主人类写法
public Pet sendPet(int type){//以父类作为返回对象,也是多态的一种用法
Pet pet = null;
switch(type){
case 1:
Pet pet=new Cat();
break;
case 2:
Pet pet=new Dog();
break;
default:
System.out.println("私密马赛!没有这种宠物");
break;
}
return pet;
}
}

注意,这时你再构建对象,你就得写成Pet xx = new Xx(),即向上转型这里和以前的写法规定不一样了。而当master的cure函数收到Pet类型的对象时,系统会找到这个对象指向的具体的类型Xx,也就是实际上在调用时将其视作Xx类型的对象

关于向上(向下)转型

上文中我们把父类类型引用变量指向子类对象,自动进行类型转换的写法称为向上转型,自然地,我们也将父类类型引用变量强制转换为子类类型引用变量的写法称为向下转型

我们为什么需要转型

当我们需要以多态写法(我个人将多态理解成一种写法)去简化调用这些子类的方法时,我们需要将参数表里传入的类型向上转型成子类共同继承的父类类型来使方法在具备简洁的方法体的同时可以灵活调用各个子类重写的父类方法,而当我们需要调用子类的特有方法时,由于父类对象不能调用子类特有的方法就需要将其强制转换回子类,即向下转型,从而实现调用。

转型需要注意的地方

  • 向上转型后只能调用子类覆盖或继承父类的方法,不能调用子类特有的方法
  • 在向下转型的过程中如果没有转换成真实子类类型,会出现类型转换异常,来看下面的代码。
1
2
3
4
5
6
7
public class Test(){//测试类
public static void main(String[]args){
Pet dog = new Dog();//先来个向上转型
Cat cat = (Cat) dog;//再来个向下转型
cat.meow();//让猫(实际上是“变成猫”的狗)调用喵喵叫方法(狗肯定不会喵喵叫啊所以是猫的特有方法)
}
}

上面这段代码在写的时候IDE不会给你报错,也就是没有语法问题。但当你编译它的时候,IDE会告诉你存在编译错误(至少我的Myeclipse是这样)java.lang.ClassCastException:Dog cannot be cast to Cat类型转换异常:狗变不成猫!),所以注意,向下转型时如果没有转化为真实的子类类型,会触发类型转换异常。
如果想要避免类型转换异常的出现,我们可以借助instanceof来进行辅助判断,来看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
public class Test(){//测试类
public static void main(String[]args){
Pet dog = new Dog();//先来个向上转型
if (dog instanceof Dog){
dog.bark();//汪汪汪
}
elif(dog instanceof Cat){
cat.meow();//喵喵喵
}
}
}
  • 注意,使用instanceof时,对象的类型必须和instanceof后面的类在继承上有上下级关系
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022 Daniel Qi
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信