1、什么是向上转型和向下转型?

1.1 向上转型

向上转型分为两种情况,接口的向上转型和继承的向上转型,向上转型是为多态服务的,在方法的入参带有对象引用时,向上转型可以少写重复代码。
(1)接口的向上转型
接口的向上转型,即声明一个接口变量,指向实现了该接口的类的实例。举例如下,定义一个接口Cat以及该接口的实现类BlueCat,在main函数里通过接口的向上转型创建一个BlueCat对象。
Cat接口:

  1. public interface Cat {
  2. void miao();
  3. }

BlueCat实现类:

public class BlueCat implements Cat {
    @Override
    public void miao() {
        System.out.println("蓝猫喵喵叫");
    }
}

main函数:

public class Main {
    public static void main(String[] args) {
        Cat cat = new BlueCat();
    }
}

main函数里,声明了一个Cat接口变量cat,用来指向BlueCat类的实例。
(2)继承的向上转型
继承的向上转型,即声明一个父类的引用变量,用来指向子类的实例。举例如下,定义一个父类Dog和其子类YellowDog,在main函数里先声明一个父类的引用dog,用它来指向子类YellowDog类的实例。
父类Dog类:

public class Dog {
    private String name;
    public Dog()
    {
        System.out.println("调用Dog类的无参构造函数");
    }
    public void dogMethod()
    {
        System.out.println("调用Dog类独有的方法");
    }
}

子类YellowDog类:

public class YellowDog extends Dog {
    private String name;
    public YellowDog()
    {
        System.out.println("调用YellowDog类的无参构造函数");
    }
    public void yellowDogMethod()
    {
        System.out.println("这是黄狗类独有的方法");
    }
    @Override
    public void dogMethod()
    {
        System.out.println("这是黄狗类重写父类Dog类的方法");
    }
}

main函数:

public class Main {
    public static void main(String[] args) {
        Dog dog = new YellowDog();
    }
}

这个例子是之前讲构造函数时举的,重点在main函数。

1.2 向下转型

子类对象的引用指向父类的实例。向下转型一般用的不多,举个使用场景:当调用子类里有而父类里没有的方法时,需要将引用通过强制转换的形式转化为指向子类的引用,需要提前用instanceOf方法判断要强制转换的引用是否为指向父类的引用。

public void method(Object obj)
    {
        if (obj instanceof Dog)
        {
            YellowDog yellowDog = (YellowDog) obj;
            // 调用YellowDog类有而Dog类没有的方法
            ...
        }
    }

2、向上转型和向下转型注意事项

主要是对向上转型要注意:向上转型不需要强制转换,向上转型后父类的引用指向的属性是父类的属性,父类的引用可以调用父类有而子类没有的方法;父类的引用可以调用子类里覆写了父类的方法,此时调用的是子类里的方法,这个叫动态绑定;父类的引用不可以调用子类里有而父类里没有的方法。
父类:

public class Dog {
    public void dogMethod()
    {
        System.out.println("调用Dog类被子类覆写的方法");
    }
    public void dogOnlyMethod()
    {
        System.out.println("调用Dog类独有的方法");
    }
}

子类:

public class YellowDog extends Dog {
    public void yellowDogOnlyMethod()
    {
        System.out.println("调用黄狗类独有的方法");
    }
    @Override
    public void dogMethod()
    {
        System.out.println("调用黄狗类重写父类Dog类的方法");
    }
}

main函数:

public class Main {
    public static void main(String[] args) {
        Dog dog = new YellowDog();
        // 父类的引用调用子类覆写了父类的方法,此时调用的实际是子类的方法
        dog.dogMethod();
        // 父类的引用调用了父类有而子类没有的方法
        dog.dogOnlyMethod();
        // 父类的引用无法调用子类有而父类没有的方法,此时需要向下转型后方可调用
        // dog.yellowDogOnlyMethod();
        ((YellowDog)dog).yellowDogOnlyMethod();
    }
}

3、为什么要向上转型和向下转型?

对于向下转型,上面例子也说了,父类的引用无法调用子类有而父类没有的方法,此时需要向下转型后方可调用。
对于向上转型,当调用方法的入参是对象的引用时,可以通过向上转型将入参的变量设置为父类的引用,这样可以少些重复代码。
以继承的向上转型为例,在Dog,YellowDog的基础上再增加一个Dog类的子类BlackDog,同样覆写Dog类里的dogMethod方法,在main里新增一个静态方法dogMethodInMain,该方法入参为Dog类的引用,里面调用dogMethod方法,在main函数里调用两次dogMethodInMain方法,入参分别为YellowDog实例和BlackDog实例,如下:
BlackDog子类:

public class BlackDog extends Dog{
    @Override
    public void dogMethod() {
        System.out.println("调用黑狗类重写父类Dog类的方法");
    }
}

main:

public class Main {
    public static void main(String[] args) {
        dogMethodInMain(new YellowDog());
        dogMethodInMain(new BlackDog());
    }
    public static void dogMethodInMain(Dog dog)
    {
        dog.dogMethod();
    }
}

执行结果如下:

调用黄狗类重写父类Dog类的方法
调用黑狗类重写父类Dog类的方法

哪里节省了代码呢?试想一下如果没有向上转型,则要重载2个dogMethodInMain方法,入参分别为BlackDog类引用和YellowDog类引用,如下:

public static void dogMethodInMain(BlackDog dog)
    {
        dog.dogMethod();
    }
    public static void dogMethodInMain(YellowDog dog)
    {
        dog.dogMethod();
    }

这只是子类仅有两个的情况,当子类有很多个时,该方法还要重载这么多版本么?
总结一下为何需要向上转型,答案是可以少写重复代码,具体场景要注意:

  • 方法的入参为父类的引用;
  • 方法内部调用的子类的方法,必须是子类覆写的父类的方法。