java中的向上转型和向下转型

一、编译类型和运行类型

在了解向上转型和向下转型我们需要先了解什么是编译类型,什么是运行类型。

以如下代码为例,Aniaml是他的编译类型(因为在运行前就确定了),Dog是他的运行类型(因为他有个new的过程要让代码跑起来)。

Animal animal = new Dog();

二、向上转型

2.1什么是向上转型

向上转型就是将子类对象赋值给父类引用。如一下代码,animal是对象引用,而new Cat()才是真正的对象。向上转型无法调用子类特有的属性和方法。

2.2动态绑定

往下的animal.eat()和animal.say()涉及到动态绑定,由此我们可以引出什么是动态绑定。在 Java 中,动态绑定又称运行时绑定,是指程序在运行期间才确定要调用的方法具体实现的机制。与之相对的是静态绑定,这里就不细说了。

如前面所说我们知道animal实际引用的“Cat对象(即运行类型是Cat,可以通过调用animal.getClass()来输出相关信息)。因此在调用eat方法时他会去找Cat类里面eat方法,如果Cat类中没有该方法则向上去父类中找eat方法,若找到Object类依旧没有则报错。

只有方法才有动态绑定,属性遵循的是就近原则。子类重写的say方法中输出的name应该是子类的name(此时最近的就是子类中的name属性),这里可以体现出属性遵循的是就近原则。如果想要访问父类的name可以使用super.name



public class Main {
    public static void main(String[] args){
        Animal animal = new Cat("小白","小黄","加菲猫");  //向上转型
        animal.eat();
        animal.say();
        //System.out.println(animal.breed);  错误,向上转型不能访问子类的特有方法和属性
    }
}

class Animal{
    String name;

    public Animal(String name) {
        this.name = name;
    }

    void eat(){
        System.out.println("调用了Animal类中的eat方法");
    }
    void say(){
        System.out.println("调用了Animal类中的say方法,猫的名字是" + name);
    }
}

class Cat extends Animal{
    String name;   //名字
    String breed;  //品种

    public Cat(String animalName, String catName, String breed) {
        super(animalName);   //初始化父类name
        this.name = catName; //初始化子类name
        this.breed = breed;
    }

    @Override
    void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    void say(){
        System.out.println("猫的名字是" + name);
    }
}

2.3向上转型的注意事项

  • 父类引用只能访问父类中定义的成员
  • 向上转型会丢失子类特有的信息
  • 向上转型是隐式的,无需强制类型转换
  • 方法调用遵循动态绑定
  • 属性不参与动态绑定,遵循就近原则

三、向下转型

3.1什么是向下转型

向下转型就是将父类类型的引用强制转换为子类类型的引用。目的是通过父类引用获取子类特有的属性或方法。向下转型依旧是方法调用遵循动态绑定,属性不参与动态绑定,遵循就近原则。

如以下代码中 Cat cat = (Cat)animal是将对象引用(animal)的类型转换成子类类型(即向下转型),此时可以调用子类的特有方法和属性。向下转型后依旧可以使用父类的属性和方法。

向下转型的父类引用必须指向目标子类的对象。比如Animal animal = new Cat("小白","小黄","加菲猫")的运行类型是Cat我们不能将他向下转型成Dog类型,因为我们new出来的对象是Cat类型的。



public class Main {
    public static void main(String[] args){
        Animal animal = new Cat("小白","小黄","加菲猫");  //向上转型
        animal.eat();
        animal.say();
        Cat cat = (Cat)animal;  //向下转型
        System.out.println(cat.breed);
    }
}

class Animal{
    String name;

    public Animal(String name) {
        this.name = name;
    }

    void eat(){
        System.out.println("调用了Animal类中的eat方法");
    }
    void say(){
        System.out.println("调用了Animal类中的say方法,猫的名字是" + name);
    }
}

class Cat extends Animal{
    String name;   //名字
    String breed;  //品种

    public Cat(String animalName, String catName, String breed) {
        super(animalName);   //初始化父类name
        this.name = catName; //初始化子类name
        this.breed = breed;
    }

    @Override
    void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    void say(){
        System.out.println("猫的名字是" + name);
    }
}

因此我们向下转型时最好使用 instanceof来检查类型,避免抛出 ClassCastException(类型转换异常)。

void fun(Animal animal){
    if(animal instanceof Cat){
        Cat cat = (Cat)animal;
        System.out.println(cat.breed);
    }
}

3.2向下转型的注意事项

  • 向下转型的父类引用必须指向目标子类的对象
  • 转型前最好使用 instanceof 检查类型
  • 转型仅改变对象引用类型,不改变对象本身(即  向下转型只是将 “父类引用” 转换为 “子类引用”,对象的实际类型(创建时的 new 子类())不会改变。)
  • 避免过度使用向下转型
  • 方法调用遵循动态绑定
  • 属性不参与动态绑定,遵循就近原则

注:文章中可能存在表述不准确、细节的完整性不足等问题。如果大家发现有错误或需要补充的地方,欢迎随时指出,非常感谢!

 

你可能感兴趣的:(java)