🎇个人主页 :Ice_Sugar_7
🎇所属专栏 :快来卷Java啦
🎇欢迎点赞收藏加关注哦!
类和对象3
🍉多态
概念:对于同一个行为,不同的对象去做,会产生不同的状态
比如对于吃这个行为,狗这个对象去做的话就是吃狗粮;猫去做的话就是吃猫粮
再比如,对于景区买票这个行为,学生去做的话就是买学生票;儿童去做的话就是买儿童票;成人去做的话就是买成人票
java中的多态指同一个方法可以根据接收的不同参数类型
,产生不同的行为
要实现多态,必须同时满足以下几个条件:
- 子类必须要对父类中方法进行
重写
- 必须在
继承
体系下实现向上转型
- 通过
父类的引用
调用重写的方法
下面对这些条件一一讲解
🍌重写
在继承关系
上,如果满足:
- 方法名一样
- 方法的参数列表一样
那我们就说这两个方法之间的关系是重写
举个例子:
java
public class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name+"正在吃饭");
}
}
public class Dog extends Animal{
public String color;
@Override
public void eat() {
System.out.println(name+"在吃狗粮");
}
}
父类中有一个eat方法,而子类中也有一个,不同点在于它们所吃的东西不同(一个吃饭、一个吃狗粮)
重写的规则:
- 被重写的父类方法不能被private、final修饰,并且不是构造方法、静态方法
- 被重写的方法返回值类型可以不同,但必须具有父子关系
- 子类方法的访问权限不能低于父类中被重写方法的访问权限。比如父类方法被public修饰,那子类中重写的方法就不能声明为 protected
- 重写的方法可以使用
@Override
注解来显式指定,它能帮我们进行校验,确保顺利重写
重写和重载的区别:
🍌向上转型&向下转型
一、向上转型
创建一个子类对象,但是用父类引用来引用它
语法格式:父类类型 对象名 = new 子类类型()
java
Animal animal = new Cat("Mimi",1);
Animal是父类类型,但可以引用一个子类对象,因为是从小范围
向大范围
的转换(子类是小范围,父类是大范围,这就类似隐式类型转换),安全性很好
常见的可以发生向上转型的场景:
-
直接赋值
这个就是刚才上面所举的例子
-
方法传参
形参为父类引用,可以接收任意子类对象
java
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("Mimi",1);
eat(cat);
}
- 作为返回值返回
方法的返回类型为父类类型,返回任意子类对象
java
public Animal func() {
Dog dog = new Dog();
return dog;
}
向上转型可以提高代码的灵活性、通用性 ,比如说有一个方法的参数类型是Animal类,那它就可以接收Dog类、Cat类、Bird类等实参,而且根据传递的对象的类型,确定要使用哪个对象的具体表现(比如传Dog类就表现出跑的动作,而传Bird类就表现出飞的动作),这就是我们所讲的多态
二、向下转型
将一个子类对象向上转型之后,它不能调用子类特有的属性、方法,但有时候可能需要调用子类特有的方法,所以将父类引用再还原为子类对象即可,即向下转型
简而言之就是:父类强制类型转换之后给子类。从谁转型上来的,就转下去成为谁
但是向下转型不太安全,因为需要进行强制类型转换
,如果转换后的类型与向上转型之前子类对象类型不一致的话,运行时就会抛异常
举个例子,比如我想让狗喵喵叫,这肯定是不行的
java
public static void main(String[] args) {
Animal animal = new Cat();
Animal animal1 = new Dog();
Dog dog = new Dog();
dog = (Dog)animal1; //向下转型成功
dog = (Cat)animal; //转换失败,因为animal不是由Cat类型向上转型得到的
dog.mew();
}
为了防止报错,可以在向下转型之前使用关键字instanceof
进行检验
o1 instanceof o2
instanceof可以用来判断o1是否是o2或者o2子类实例化的对象,如果是,返回true;反之返回false
而对于第三个条件:通过父类的引用调用重写的方法 。其实你会发现只要满足前面两个条件,那第三个条件肯定也满足了
满足三个条件之后,就会发生动态绑定
,它是多态的基础
。而有动态绑定,那自然也有静态绑定
,下面对这两个概念进行解析
🍌静态绑定&动态绑定
- 静态绑定
在编译阶段
就确定要调用哪个函数
java
int add(int x,int y) {
return x+y;
}
int add(int x,int y,int z) {
return x+y+z;
}
比如上面两个add方法构成重载,在编译阶段根据所传参数个数就能确定要调用哪个add方法
- 动态绑定
当一个父类的引用
指向一个子类的对象时,可以通过父类的引用调用子类重写的方法。这种情况下,Java会根据对象的实际类型来决定调用哪个方法,这就是动态绑定
动态绑定是在程序运行期间
才确定要调用哪个方法
java
public static void main(String[] args) {
Animal animal = new Dog("圆圆",19);
animal.eat();
}
当调用一个方法时,编译器会根据引用的类型
来确定要调用的方法,所以在编译阶段,调用的还是Animal的eat方法(通过汇编
可以观察到)
但是程序运行时,实际上被调用的方法是由引用所指向的对象的类型
决定的,所以我们看到运行结果是调用Dog的eat方法
🍌多态的利弊
多态的好处:
- 能够简化代码,避免使用大量的 if - else
比如要写一个类来打印不同的图案(○、△、❀),如果不基于多态,实现的代码如下:
java
public class Shape {
public Shape shape;
public void draw() {
System.out.println("画一个图形");
}
}
public class Circle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
public class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
public class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public static void main(String[] args) {
String[] array = {"Triangle","Circle","Circle","Flower"};
for(String shape:array) {
if(shape.equals("Triangle")) {
System.out.println("△");
} else if(shape.equals("Circle")) {
System.out.println("○");
} else if (shape.equals("Flower")) {
System.out.println("❀");
}
}
}
如果使用多态,就可以简化为:
java
public class Shape {
public Shape shape;
public void draw() {
System.out.println("画一个图形");
}
}
public class Circle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
public class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
public class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public static void main(String[] args) {
Shape[] array = {new Triangle(),new Circle(),new Circle(),new Flower()};
for(Shape shape:array) {
shape.draw();
}
}
以子类对象为数组元素,通过for循环调用draw函数打印相应的图案
- 可扩展能力更强
以上面打印图案为例,如果要新增一种新的形状,使用多态的方式代码改动成本也比较低
缺陷:
- 属性没有多态性
当父类和子类都有同名属性
的时候,通过父类引用,只能引用父类自己的成员属性 - 构造方法没有多态性
注意:不要在构造方法里面调用重写的方法!!!因为会发生动态绑定,调用子类重写的方法,但此时子类还没构造完成,可能会出现一些极难被发现的问题
🍉写在最后
以上就是本篇文章的全部内容,如果你觉得本文对你有所帮助的话,那不妨点个小小的赞哦!(比心)