多态
一.向上转型(子类引用给父类)
在类的继承中我们写了这样的一个代码
Bird bird=new Bird("小小");
上述代码也可以写成
Bird bird=new Bird("小小"); Animal bird2=bird;
或者写成Animal bird2=new bird("小小");
此时bird2是一个父类的引用,指向一个子类(Bird)的实例,这种写法称为向上转型
向上转型发生的时机:
①直接赋值(上面所描述的情况)
②方法传参
③方法返回
方法传参
//定义动物
class Animal{
public String name;
public Animal(String name){
this.name=name;
}
public void eat(String food){
System.out.println(this.name+"animaleat"+food);
}
}
//鸟类继承动物类
class Bird extends Animal{
public Bird(String name) {
super(name);
}
//方法重写
@Override
public void eat(String food) {
System.out.println(this.name+"birdeat"+food);
}
}
public class Test {
public static void main(String[] args) {
Bird bird=new Bird("小小");
feed(bird); //传参
}
public static void feed(Animal animal){
animal.eat("谷子");
}
}
//打印结果
小小birdeat谷子
注意到上述代码feed方法形参是Animal类型,实际上它对应的是Bird的实例。
方法返回
public class Test {
public static void main(String[] args) {
Animal animal=findMyAnimal();
}
public static Animal findMyAnimal(){
Bird bird=new Bird("小小");
return bird;
}
}
上述代码findMyAnimal方法返回的是一个Animal类型的引用,但是实际上却是对应到了Bird的实例
二.动态绑定
也称运行时绑定
解决的问题就是当子类重写了父类的方法时,运行时会出现什么样的结果呢?
先观察下列代码:
//定义动物类
class Animal{
protected String name;
public Animal(String name){
this.name=name;
}
public void eat(String food){
System.out.println(this.name+"animaleat"+food);
}
}
class Bird extends Animal{
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) { //子类重写eat方法
System.out.println(this.name+"birdeat"+food);
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Animal("小小");
animal1.eat("豆子");
Animal animal2 = new Bird("大大");
animal2.eat("豆子");
}
}
//打印结果
小小animaleat豆子
大大birdeat豆子
animal1和animal2虽然都是Animal的引用,但是animal1指向的是Animal的实例,animal2指向的是Bird类型的实例
animal1和animal2分别调用eat方法,此时animal1调用的是父类的eat方法,animal2调用的则是子类的eat方法
所以,在Java中,调用某个类的方法,到底是执行父类的方法还是子类的方法,要看究竟这个引用指的是父类对象还是子类对象。这个过程是程序运行时决定的,因此为动态绑定
发生动态(运行时绑定)条件:
①继承(父类要引用子类)
②通过父类的引用调用子类和父类同名的覆盖方法
这里有个概念是重写,那么重写和重载有什么区别呢?
重写和重载
使用多态有什么好处呢?
①类的调用者对类的使用成本进一步降低
②能够降低代码的“圈复杂度”,避免使用大量的if-else
圈复杂度?
圈复杂度是描述一段代码复杂程度的方式。一段代码如果平铺直叙,那么就容易理解,而如果有很多的条件分支或者循环语句,就认为理解起来复杂
计算一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”。若一个方法的圈复杂度太高,就需要考虑重构。
③可扩展能力更强
三.super关键字
由于重写机制,调用的是子类的方法,如果需要在子类内部调用父类方法呢?这时候就可以使用super关键字
换句话数就是super就相当于是父类实例的引用
super常见用法:
1.使用super来调用父类的构造器(要构造子类就需要先构造父类)
2.使用super来调用父类的普通方法
参考代码:
...
...
class Bird extends Animal{
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) {
super.eat(food);
System.out.println(this.name+"birdeat"+food);
}
}
//打印结果
大大animaleat豆子
大大birdeat豆子
上述代码在子类的eat方法中加入了super关键字,所以先上父类调用eat方法。
在这里要区分this关键字和super关键字的区别:
四.在构造器中用重写的方法(慎用)
构造方法内是否可以发生运行时绑定?
观察下列代码:
class B{
//B的构造中由func方法
public B(){
func();
}
public void func(){
System.out.println("B.fun()");
}
}
class D extends B{
private int num=1;
@Override
public void func() { //重写父类的func方法
System.out.println("D.func()"+num);
}
}
public class Test{
public static void main(String[] args) {
D d=new D();
}
}
//打印结果
D.func()0
上述代码打印结果显示此时调用了子类的func方法,这是为什么呢?
①构造D对象的同时,会调用B的构造方法
②B的构造方法中调用了func方法,此时会触发动态绑定,会调用子类D中func方法
③此时D对象自身还没有构造,所以num处在未初始化的状态,值为0