[Java SE] 继承与多态(三):多态

1.多态的概念

通俗来说就是:多种形态,具体一点就是去完成某个行为,当不同的对象去完成的时候会产生出不同的效果
在这里插入图片描述
总的来说:同一件事情发生在不同对象的身上,就会产生不同的结果

2. 多态实现的条件

Java中想要实现多态,必须要满足以下几个条件,缺一不可:

  • 必须在继承体系下
  • 子类必须要对父类中的方法进行重写
  • 通过父类引用调用重写的方法
    多态实现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
public class Animal {
	String name;
	int age;
	public Animal(String name, int age){
		this.name = name;
		this.age = age;
	}
	public void eat(){
		System.out.println(name + "吃饭");
	}
}
public class Cat extends Animal{
	public Cat(String name, int age){
		super(name, age);
	}
@Override
	public void eat(){
		System.out.println(name+"吃鱼~~~");
	}
}
public class Dog extends Animal {
	public Dog(String name, int age){
		super(name, age);
	}
	@Override
	public void eat(){
		System.out.println(name+"吃骨头~~~");
	}
}
public class TestAnimal {
	// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
	// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
	// 注意:此处的形参类型必须时父类类型才可以
	public static void eat(Animal a){
		a.eat();
	}
	public static void main(String[] args) {
		Cat cat = new Cat("元宝",2);
		Dog dog = new Dog("小七", 1);
		eat(cat);
		eat(dog);
	}
}

当类的调用者在编写eat这个方法的时候,参数类型为Animal,此时在方法内部并不关注当前的a指向的是那个类型的实例,此时a这个引用调用eat方法可能会有多种不同的表现,这种行为称为多态

3.重写

3.1概念
重写:也称为覆盖。重写就是对非静态方法,非private修饰的方法,非final修饰的方法,非构造方法的实现过程进行重新编写返回值和形参都不能改变,即外壳不变,实现核心重写,好处就是子类可以在父类的基础上实现自己的方法,定义特定于自己的行为
3.2 规则

  • 子类在重写父类的方法时,一般情况下返回值和形参都不能改变
  • 被重写的方法返回值类型有时可以不同,但是他们必须具有父子关系
  • 访问权限不可以比父类中被重写的方法更低,例如:父类方法被public修饰,则子类方法中重写该方法就不能声明为private
  • 父类方法不可以被static,final,private修饰,也不可以是构造方法,否者不可以被重写
  • 重写的方法,编译器会用“@override”来注解,可以检查合法性

3.3重写和重载的区别

区别点重写重载
参数列表一定不可以修改必须修改
返回类型一定不可以修改(除非有父子关系)可以修改
访问限定符一定不可以降低权限可以修改

在这里插入图片描述
3.4 静态绑定与动态绑定

  • 静态绑定:也称为前期绑定,在编译时根据用户传递的参数类型就可以知道调用哪个方法,典型代表为方法的重载
  • 动态绑定:也称为后期绑定,在编译时,不可以确定方法的行为,无法知道调用哪个方法,需要等程序运行时才可以确定。

4. 向上转型和向下转型

4.1 向上转型

  1. 概念:创建一个子类对象,把他当做父类对象来使用,就是范围从小到大的转换
  2. 语法格式:==父类类型 对象名=new 子类类型()
Animal animal=new Cat("miaomiao",2);

我们就拿上面的代码来说明:
在这里插入图片描述
3. 使用场景

  • 直接赋值
  • 方法传参
  • 方法返回
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
	public static void eatFood(Animal a){
		a.eat();
	}
// 3. 作返回值:返回任意子类对象
	public static Animal buyAnimal(String var){
		if("狗".equals(var) ){
			return new Dog("狗狗",1);
		}else if("猫" .equals(var)){
			return new Cat("猫猫", 1);
		}else{
			return null;
	}
}
public static void main(String[] args) {
	Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
	Dog dog = new Dog("小七", 1);
	eatFood(cat);
	eatFood(dog);
	Animal animal = buyAnimal("狗");
	animal.eat();
	animal = buyAnimal("猫");
	animal.eat();
	}
}

通过上述的代码,我们不难发现向上转型的有限,他可以使得代码更加简单灵活,但是缺点也很明显,不可以调用子类特有的方法,怎么办呢,我们这里便引出了向下转型
4.2向下转型
将一个子类对象经过向上转型之后当做了父类的对象使用,无法再调用到子类的方法,但是我们有时候会想要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转型

  1. 语法格式:子类类型 对象名=(子类类名)经过向上转型的父类对象
    从上述语法格式来看,其实就是强制类型转换
  2. 安全性问题
    向下转型存在不安全的问题,不同于向上转型,是安全的,比如原来一个对象是狗类,向上转为了动物类,再向下转型的时候必须转回狗类,不可以转为猫类
    在这里插入图片描述
public class TestAnimal {
	public static void main(String[] args) {
		Cat cat = new Cat("元宝",2);
		Dog dog = new Dog("小七", 1);
		// 向上转型
		Animal animal = cat;
		animal.eat();
		animal = dog;
		animal.eat();
		// 编译失败,编译时编译器将animal当成Animal对象处理
		// 而Animal类中没有bark方法,因此编译失败
		// animal.bark();
		// 向上转型
		// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
		// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
		cat = (Cat)animal;
		cat.mew();
		// animal本来指向的就是狗,因此将animal还原为狗也是安全的
		dog = (Dog)animal;
		dog.bark();
	}
}
  1. 解决方案
    使用instanceof判断一个对象所属于的类是否属于一个类的父类
public class TestAnimal {
	public static void main(String[] args) {
	Cat cat = new Cat("元宝",2);
	Dog dog = new Dog("小七", 1);
	// 向上转型
	Animal animal = cat;
	animal.eat();
	animal = dog;
	animal.eat();
	if(animal instanceof Cat){
		cat = (Cat)animal;
		cat.mew();
	}
	if(animal instanceof Dog){
		dog = (Dog)animal;
		dog.bark();
	}
}
}

5.避免在构造方法中使用重写的方法

下面展示一段有坑的代码

class B {
	public B() {
		func();
	}
	public void func() {
		System.out.println("B.func()");
	}
}
class D extends B {
	private int num = 1;
	@Override
	public void func() {
		System.out.println("D.func() " + num);
	}
}
public class Test {
	public static void main(String[] args) {
		D d = new D();
	}
}

运行结果:D.func() 0
难道结果不应该是1吗,我们下面解释为什么不是1

在构造D对象的同时,会调用B的构造方法,在子类构造的时候,先构造父类。B的构造方法调用了func方法,此时会触发动态绑定,会调用D中的func,此时D还没有触发构造方法,D自身还没有构造,此时num处于未初始化状态,值为0,所以输出为0.
所以我们在构造方法中调用重写方法时一定要注意,在自己编程是尽量避免这种行为

  • 33
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值