什么是多态
通俗来说就是多种形态,具体点就i是去完成某个行为,当不同的对象去完成时会产生不同的状态
都是吃东西,狗是吃狗粮,猫是吃猫粮
向上转型
本质就是创建一个子类对象,将其当作父类对象来使用
语法格式:父类类型 对象名=new 子类类型();
发生向上转型的场景:
1.直接赋值
2.方法传参
传的是cat,但用Animal类来接收
3.方法返回
方法的重写
什么是方法的重写
重写也称为覆盖,重写只能是子类对父类的方法而言
就是在方法名一样,参数列表一样,方法返回值一样
要求
1.子类重写父类方法是一般方法名一样,参数列表一样,方法返回值一样
2.但方法返回值也可以不同,但必须具有父子关系(协变类型),例如一个返回animal,一个返回dog
3.被重写的方法的访问权限不能比父类的低
4.父类被private,static,final修饰的方法以及构造方法都不可以被重写
5.重写的方法都可以用@override注解来显式指定,这个注解会帮我们进行合法性校验,例如方法名写错了,就会报错
编译器自己提供了重写的方法:在子类中点右键,点生成,里面就有构造函数,getter,setter,重写方法等
注意,所有类的父类默认都是object类
动态绑定
也称为后期绑定,即在编译时无法确定方法的行为,需要等到程序运行时,才能确定具体调用那个类的方法,例如:
在main方法中,将小狗对象赋给了animla类,所以在调用eat时,调用了Dog的eat,这就是动态绑定,绑定到了子类的方法
实际上,在编译阶段,绑定的确实是Animal的eat方法,但是在运行时,最终确定了要绑定的是dog的eat方法,这就是动态绑定,是在后期绑定
静态绑定
也称为前期绑定,早绑定,就是在编译时通过参数就能确定用哪个方法典型代表就是函数的重载。
回看toString
System.out.println(dog);
当dog类没有写toString方法时,编译器就去它的父类里面找,但Animal类也没有,就去Object类里面找
当dog类没有写toString方法而父类里面写了时,编译器就调用父类的
当子类自己写了,就调用自己的
实现多态的条件
1.必须在继承关系下,从而能够发生向上转型
2.子类必须对父类中的方法进行重写
3.通过父类的引用调用重写的方法
多态举例
这是将要在main函数中调用的方法
这是父类的eat方法
这是dog类的eat方法
这是cat类的eat方法
这是main函数
那么会输出什么呢?按理说,都调用了eatFunc,而eatFunc里面是有调用了Animal类的eat,按理说是都输出name+正在吃饭,但实际上如下:
这就叫多态,有了重写的方法,有了向上转型(一定得有父子关系),在运行时就会发生动态绑定,绑定到不同的重写方法,使得不同的对象会有不同的行为
注意
在eatFunc中的animal是局部变量,它的类型是Animal类,所以他只能调用Animal类里面的方法,而不能调用它的子类的方法
调用dog类的wangwang就会报错!!!
还是那句话,实际上在编译时调用的的确是Animal类的eat方法,只不过是在运行时绑定到了子类的方法,所以不能直接用父类的引用来调用子类的方法
向下转型
我们知道,上面这段代码是将子类的对象经过向上转型后当成父类使用,这时,这个子类对象就只能调用父类的方法,再无法调用子类特有的方法,那要想调用子类的方法,就要将这个父类引用还原为子类对象即可(再强调一遍,animal的类型是Animal,即animal是一个父类引用;new Dog是产生了一个子类对象,然后将它看作父类来使用),如下:
这就是把animal这个父类引用还原为了子类,(注意这里会有一个类型强转,一定要加上括号,否则会报错),此时就可以调用子类的方法了
错误的的向下转型
把一个cat对象给到了父类,还原时却还原成了dog类,这是错误的向下转型
ClassCastException:类型转换异常
总结
对于向下转型:
本来是猫还原为猫,安全
本来是狗,还原为猫,不安全
总之,向下转型很危险,如何解决:
instanceof
if(animal instanceof Dog)
这句话的意思是:如果animal是Dog类的一个实例,那么就执行if后面的语句
instanceof是一个二元运算符,目的在于判断它左边的对象是否是它右边的类的实例,如果是就返回true,不是就返回false
例如:
为什么无法运行呢?因为a是一个局部变量,局部变量必须人为初始化
初始化之后为什么还会报错?这是因为左边的对象不可以是那八种基础数据类型
多态的优缺点
1.能够降低圈复杂度,避免重复使用if else
看如下代码,想要按照圆圈,矩形,圆圈,矩形,三角形的顺序画出图形:
class Shape{
public void draw(){
System.out.println("画一个图形");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("😊");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("长方形");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("🔺");
}
}
public class Test1 {
public static void main(String[] args) {
Cycle cycle=new Cycle();
Rect rect=new Rect();
Triangle triangle=new Triangle();
String []str={"cycle","rect","cycle","rect","triangle"};
for(String x:str){
if(x.equals("cycle")){
cycle.draw();
}
else if(x.equals("rect")){
rect.draw();
}
else{
triangle.draw();
}
}
}
}
下面是结果:
这段代码用了好多if语句,太复杂了,用多态来修改一下:
定义一个shape类型的数组,里面的对象的类型是它的子类,这就发生了向上转型,最后再调用重写的方法,实现动态绑定。
2.可扩展能力强
比如还想要画一朵花,只要再写一个花类就可以
3.属性没有多态性
如果父类和子类的成员变量同名了,只可以调用父类的变量
例如父类里面,a=0,子类里面,a=10;下面的语句,打印出来的a是0
要想打印出子类的a,可以进行向下转型,或者直接类型强转
但注意,这就不是多态了
4.构造方法没有多态性
注意:避免在构造方法中调用重写的方法
看下面的代码:
class A{
public void func(){
System.out.println("A func()");
}
public A() {
func();
}
}
class B extends A{
private int num=1;
@Override
public void func() {
System.out.println("B func()");
System.out.println(num);
}
}
public class Test {
public static void main(String[] args) {
B b=new B();
}
}
结果是什么:
当创建一个b时,会先对父类进行构造,也就是进入父类的构造方法,而在父类的构造方法中,调用了func(),由于动态绑定,最终执行到的是子类的func。但由于在执行func()时,父类还没有构造完成,所以更没有轮到子类构造,所以num=1这句话还没来得及执行,所以num暂时是默认初始值0.
所以以后不要在构造函数中调用重写的方法,因为此时子类还没有构造完毕,一旦调用,就会发生动态绑定,可能出现隐藏的极难发现的问题