方法覆盖与多态
1 方法覆盖
1.1 什么是方法覆盖
在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
1.2 什么时候使用方法覆盖
当从父类中继承过来的方法无法满足当前子类业务需求的时候,需要将父类中继承过来的方法进行覆盖。换句话说,父类中继承过来的方法已经不够用了,子类有必要将这个方法重新再写一遍,所以方法覆盖又被称为方法重写。当该方法被重写之后,子类对象一定会调用重写之后的方法。
1.3 方法覆盖的代码如何实现
/**
* 动物类
*/
public class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//动物类的一个方法
public void move() {
System.out.println(name + "在移动");
}
}
/**
* Cat类继承动物类
*/
public class Cat extends Animal {
}
/**
* 测试类
*/
public class AnimalTest {
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("加菲猫");
cat.move();
}
}
运行结果:

现在想让继承过来的move()方法实现子类特有的动作,例如:猫在走猫步,鸟在飞。
此时需要使用方法覆盖:
/**
* 在Cat类中重写move()方法
*/
public class Cat extends Animal {
public void move() {
System.out.println(this.getName() + "在走猫步");
}
}
/**
* 在Bird类中重写move()方法
*/
public class Bird extends Animal {
public void move() {
System.out.println(this.getName() + "在飞翔");
}
}
/**
*测试类
*/
public class AnimalTest {
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("加菲猫");
cat.move();
Bird bird = new Bird();
bird.setName("麻雀");
bird.move();
}
}
运行结果:

1.4 方法覆盖的条件及注意事项
方法覆盖的条件:
-
方法覆盖发生在具有继承关系的父子类之间,这是首要条件;
-
覆盖之后的方法与原方法具有相同的返回值类型、相同的方法名、相同的形式参数列表;
使用方法覆盖时需要注意:
- 由于覆盖之后的方法与原方法一模一样,建议在开发的时候采用复制粘贴的方式, 不建议手写,因为手写的时候非常容易出错;
- 私有的方法不能被继承,所以不能被覆盖;
- 构造方法不能被继承,所以也不能被覆盖;
- 覆盖之后的方法不能比原方法拥有更低的访问权限,可以更高;
- 覆盖之后的方法不能比原方法抛出更多的异常,可以相同或更少;
- 方法覆盖只是和方法有关,和属性无关;
- 静态方法不存在覆盖;
2 多态
2.1 多态基础语法
多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。
在 Java中允许这样的两种语法出现,一种是向上转型(Upcasting),一种是向下转型(Downcasting),向上转型是指子类型转换为父类型,又被称为自动类型转换,向下转型是指父类型转换为子类型,又被称为强制类型转换。
在Java语言中有这样的一个规定,无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系情况下进行向上转型或向下转型的时候编译器都会报错。
/**
*动物类
*/
public class Animal {
public void move() {
System.out.println("动物在移动!");
}
}
/**
* Cat类继承动物类并重写move()方法
*/
public class Cat extends Animal {
public void move() {
System.out.println("猫在走猫步!");
}
}
/**
*Bird类继承动物类并重写move()方法
*/
public class Bird extends Animal {
public void move() {
System.out.println("鸟在飞翔!");
}
}
/**
* 测试类
*/
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
Cat cat = new Cat();
Bird bird = new Bird();
animal.move();
cat.move();
bird.move();
}
}
运行结果:

测试类也可以这样写:
/**
* @author wcs
* @date 2021/8/7 16:51
*/
public class AnimalTest {
public static void main(String[] args) {
Animal a1 = new Cat();
Animal a2 = new Bird();
a1.move();
a2.move();
}
}
运行结果:

这个测试类演示的就是多态,多态就是“同一个行为(move)”作用在“不同的对象上”会有不同的表现结果。Java中之所以有多态机制,是因为Java允许一个父类型的引用指向一个子类型的对象。
2.1.1 向上转型与向下转型
/**
* 在Cat类中加入特有的方法catchMouse()
*/
public class Cat extends Animal {
public void move() {
System.out.println("猫在走猫步!");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
/**
* 测试类
*/
public class AnimalTest {
public static void main(String[] args) {
//向上转型
Animal a = new Cat();
a.move();
//向下转型:为了调用子类对象特有的方法
Cat c = (Cat) a;
c.catchMouse();
}
}
运行结果:

注意:无论是向上转型还是向下转型,两种类型之间必须要有继承关系。
在访问子类型中特有数据的时候,需要向下转型,但是还要注意以下情况:
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Bird();
Cat c = (Cat) a;
}
}
这样写编译器没有报错,因为编译器只知道 a 变量是Animal 类型,Animal 类和 Cat 类之间存在继承关系,所以可以进行向下转型,语法上没有错误,所以编译通过了。但是运行的时候会出问题吗,因为毕竟 a 引用指向的真实对象是一只小鸟。来看运行结果:

以上的异常是很常见的ClassCastException,翻译为类型转换异常,这种异常通常出现在向下转型的操作过程当中,当类型不兼容的情况下进行转型出现的异常。
为了避免这种异常的发生,建议在进行向下转型之前进行运行期判断,需要用运算符instanceof来判断。
instanceof运算符的语法格式:
(引用 instanceof 类型)
运算结果是布尔类型的。
用instanceof运算符,向下转型可以这样写:
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Bird();
if (a instanceof Cat) {
Cat c = (Cat) a;
c.catchMouse();
}
}
}
以上程序运行后不再发生异常,也没有执行catchMouse(),那是因为if语句的条件没有成立,if中的Java语句并没有执行。在Java中有一条规范:在进行任何向下转型的操作之前,要使用instanceof 进行判断, 这是一个很好的编程习惯。例如:
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Bird();
if (a instanceof Cat) {
Cat c = (Cat) a;
c.catchMouse();
}else {
Bird b = (Bird) a;
b.move();
}
}
}
运行结果:

2.2 多态在开发中的作用
public class Dog {
public void eat() {
System.out.println("狗吃骨头");
}
}
public class Master {
public void feed(Dog d) {
System.out.println("主人喂狗");
d.eat();
}
}
public class Test {
public static void main(String[] args) {
Master m = new Master();
Dog dog = new Dog();
m.feed(dog);
}
}
运行结果:

以上程序主人只有一个宠物狗,只喂这一个狗。当主人家又来了一只宠物猫,主要还要加一个喂猫的方法。
public class Cat {
public void eat() {
System.out.println("小猫吃鱼");
}
}
public class Master {
public void feed(Dog d) {
System.out.println("主人喂狗");
d.eat();
}
public void feed(Cat c){
System.out.println("主人喂猫");
c.eat();
}
}
public class Test {
public static void main(String[] args) {
Master m = new Master();
Dog dog = new Dog();
m.feed(dog);
Cat cat = new Cat();
m.feed(cat);
}
}
运行结果:

以上程序除了要增加一个Cat类外,还要修改Master主人类的源代码,这样就显得很麻烦。于是乎就要用到多态来解决这个问题。
//宠物类
public class Pet {
String name;
//吃的行为
public void eat() {
}
public class Dog extends Pet {
public Dog(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃骨头");
}
}
public class Cat extends Pet {
public Cat(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃鱼");
}
}
public class Master {
//主人喂宠物
public void feed(Pet pet) {
System.out.println("主人喂宠物");
pet.eat();
}
}
public class Test {
public static void main(String[] args) {
//创建主人对象
Master m = new Master();
//创建狗对象
Dog dog = new Dog("哈士奇");
//创建猫对象
Cat cat = new Cat("汤姆猫");
m.feed(dog);
m.feed(cat);
}
}
运行结果:

以上程序中Master类中的方法feed(Pet pet)的参数定义为宠物类,而不是具体的Dog宠物,或者Cat宠物。这些写降低了代码耦合度,扩展力能力更强。比如说此时再来一个宠物兔,只需增加一个”宠物兔类“,然后宠物兔类继承Pet类,并重写eat方法,修改测试类就行了,整个过程不需要修改Master类,知识额外增加了一个类。
public class Rabbit extends Pet {
public Rabbit(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃胡萝卜");
}
}
public class Test {
public static void main(String[] args) {
//创建主人对象
Master m = new Master();
//创建狗对象
Dog dog = new Dog("哈士奇");
//创建猫对象
Cat cat = new Cat("汤姆猫");
//创建兔子对象
Rabbit rabbit = new Rabbit("大白兔");
m.feed(dog);
m.feed(cat);
m.feed(rabbit);
}
}
运行结果:
