Java中向下转型的意义

什么是向上转型和向下转型

在Java继承体系中,认为基类(父类/超类)在上层,导出类(子类/继承类/派生类)在下层,因此向上转型的意思就是把子类对象转成父类类型,即将父类的引用指向子类对象;向下转型的意思就是把父类对象转成子类类型,即将子类的引用指向父类对象。


向上转型

先看一下向上转型的例子:

首先定义一个动物类,即Animal类,并赋予一个eat()方法

class Animal  
 {  
    public void eat()  
    {  
        System.out.println("父类的 eating...");  
    }  
}  

然后定义一个鸟类,即Bird类,继承Animal类并重写Animal类的eat()方法,并赋予一个方法fly()方法

class Bird extends Animal  
{     
    @Override  
    public void eat()  
    {  
        System.out.println("子类重写的父类的  eatting...");  
    }     
    public void fly()  
    {  
        System.out.println("子类新方法  flying...");  
    }  
}  

最后定义一个Sys类,实现向上转型,先看代码

public class Sys  
{  
    public static void main(String[] args)   
    {  
        Animal b=new Bird(); //向上转型  
        b.eat();   
        //  b.fly(); b虽指向子类对象,但此时子类作为向上的代价丢失和父类不同的fly()方法  
        sleep(new Male());  
        sleep(new Female());//传入的参数是子类!!
    }  

    public static void sleep(Human h) //方法的参数是父类!!
        {  
             h.sleep();  
        }  
} 

好,看完代码了,我们来解释一下向上转型是如何进行的吧!

  • 向上转型的实现
Animal b=new Bird(); //向上转型
            b.eat(); // 调用的是子类的eat()方法
            b.fly(); // 报错!!!!!-------b虽指向子类对象,但此时子类作为向上转型的代价丢失和父类不同的fly()方法(子类向上转型会丢失与父类不同(父类所不拥有的方法)
  • 为何不直接Bird b=new Bird();b.eat() 呢?
    这样就没有体现出面向对象的抽象的编程思想,且降低了代码的可扩展性

  • 向上转型的好处?

 sleep(new Male());//调用方法时传入的参数是子类
           sleep(new Female());
             public static void sleep(Human h) //方法的参数是父类
            {
                 h.sleep();
             }

如上代码就是用的向上转型,若是不用向上转型,那么有多少个子类就得在这儿写多少种不同的睡觉方法,如果有一千个,甚至更多子类,还不写的奔溃,所以使用向上转型既提高了代码的可扩展性,又减少了程序员的工作,何乐而不为呢?


再来看一下向下转型:

向下转型

和上面一样,我们先定义一个水果类,即Fruit类,并赋予一个myName()方法

class Fruit{
    public void myName(){
        System.out.printf("superclass:Fruit");
    }
}

接着我们定义一个苹果类,即Apple类,继承Fruit类并重写Fruit类的myName()方法,并赋予一个myMore()方法

class Apple extends Fruit{
    public void myName(){
        System.out.printf("sonclass:Apple");
    }

    public void myMore(){
        System.out.printf("A little Apple of Fruit");
    }
}

最后定义一个Sys类来实现向下转型,但实现向下转型前需要先实现向上转型,所以这也是为什么我们前面要介绍向上转型的原因,先看代码

public class Sys{
    public static void main(String[] args) {   
        Fruit a=new Apple(); //向上转型  
        a.myName();  

        Apple aa=(Apple)a; //向下转型,编译和运行皆不会出错(正确的)  
        aa.myName();//向下转型时调用的是子类的myName()方法  
        aa.myMore();;  

        Fruit f=new Fruit();  
        Apple aaa=(Apple)f; //-不安全的---向下转型,编译无错但会运行会出错  
        aaa.myName();  
        aaa.myMore();   
    }  
}
  • 子类是父类的超集,子类拥有的信息(例如方法),父类不一定有。用子类类型去操作父类对象是不安全的。

  • 父类是子类的子集,父类拥有的公有信息(例如方法),子类一定有,所以用父类类型去操作子类对象是安全的,也即向上转型是成立的。

这里有必要再说一下向上转型:

向上转型可分为两种,一种是隐式转换,一种是强制转换。

class A {  

}  
interface C {  

}  
public class B extends A implements C {  

    public static void main(String[] args) throws Exception {  
        // B t = (B) new A(); // 向下转型不成立,运行时会抛出错误  

        /** 
         * 向上转型 
         */  
        // 隐式转换  
        A a1 = new B();  

        // 强制转换  
        A a2 = new B();  
        C c = (C) a2;  

        // 强制转换  
        Class<> clazz = Class.forName("p.B");  
        A a3 = (A) clazz.newInstance();  
    }  
}  

当子类有两种或多种父类类型,并且这些父类类型并不在同一个继承体系内时,如果要将子类对象的引用从一个父类传给另一个父类,就需要进行强制转换。

向下转型是你向上转型后才能执行的操作,一定要牢记这一点。

类型还原

  public class B {  

    /** 
     * @param args 
     * @throws Exception 
     */  
    public static void main(String[] args) throws Exception {  
        Object o = new B();// 向上转型  
        B b = (B) o;// 类型还原  

        b = (B) Class.forName("p.B").newInstance();  
    }  

}  

向下转型的实例

首先,我们定义一个空接口Food,不赋予任何方法

public interface Food{

}

然后新建一个Vegetables,并实现Food接口:

public class Vegetables implements Food{

    public void green(){
        System.out.println("Vegetables is green food");
    }

    public void nutrition(){
        System.out.println("Vegetables is very nourishing");
    }
}

新建一个Meal,并实现Food接口:

public class Meal implements Food{

    public void testgood(){
        System.out.println("Meal test good");
    }

    public void nutrition(){
        System.out.println("Meal is also nourishing");
    }
}

新建一个Fruit类,并实现Food接口:

public class Fruit implements Food{

    public void necessary(){
        System.out.println("Fruit is necessary food for man");
    }

    public void nutrition(){
        System.out.println("Meal is also nourishing");
    }
}

上面的程序都很简单,定义了一个Food接口,并在Vegetables、Meal、Fruit类分别实现它,每一个方法也很简单,仅仅打印了一行信息而已。

接下来,我们想象一个情景:我们在商城买食品,食品有很多,但基本上可以分为蔬菜、肉类和水果,等等,这些都属于食品。电子产品是抽象的。好,那么我们决定买一台Thinkpad,一个鼠标和一个键盘。
这时,我们需要一个购物车来放我们需要买的食品。我们可以添加进购物车,然后通过购物车还能知道存放的食品数量,能拿到对应的食品。

那么,一个购物车类就出来了,如下:

import java.util.ArrayList;
import java.util.List;

public class ShopCar{

    private List<Food> mlist = new ArrayList<Food>();

    public void add(Food food){

        mlist.add(food);
    }

    public int getSize(){

        return mlist.size();
    }

    public Electronics getListItem(int position){

        return mlist.get(position);
    }

}

List 集合是用来存放食品的,add 方法用来添加食品到购物车,getSize 方法用来获取存放的食品数量,getListItem 方法用来获取相应的食品。

大家可能会疑惑的是为什么是放 Electronics 的泛型,而不是放Vegetables,Meal,Fruit等?

想象一下,如果是List,肯定放不进肉Meal吧,难道我们要生成3个集合?这里是定义了3个食品类,但是我如果有100种食品呢,难道要定义100个集合?

这太可怕了。所以之前,我们写了一个Food接口,提供了一个Food的标准,然后让每一个Food子类都去实现这个接口。

实际上这里又涉及到了向上转型的知识点,我们虽然在add 方法将子类实例传了进来存放,但子类实例在传进去的过程中也进行了向上转型。

所以,此时购物车里存放的子类实例对象,由于向上转型成Food,已经丢失了子类独有的方法,以上述例子来分析,Vegetables实例就是丢失了green() 和nutrition() 这两个方法,而Meal实例就是丢失了testgood()和nutrition()这两个方法。

但是实际上,这些食物丢失任何一个方法都是我们所不希望的。

接着我们写一个测试类 Test 去测试购物车里的食品。

public class Test{

    public static final int Vegetables= 0;
    public static final int Meal= 1;
    public static final int Fruit = 2;

    public static void main(String[] args){

        //添加进购物车
        ShopCar shopcar = new ShopCar();
        shopcar.add(new Vegetables());
        shopcar.add(new Meal());
        shopcar.add(new Fruit ());

        //获取大小
        System.out.println("购物车存放的食品数量为 ——> "+shopcar.getSize());


        //开始测试Vegetables蔬菜
        Vegetables vegetables= (Vegetables)shopcar.getListItem(Vegetables);
        vegetables.green();
        vegetables.nutrition();

        System.out.println("-------------------");

        //开始测试Meal肉
        Meal meal = (Meal)shopcar.getListItem(Meal);
        meal .testgood();
        meal .nutrition();

        System.out.println("-------------------");

        //开始测试Fruit水果
        Fruit fruit = (Fruit )shopcar.getListItem(Fruit);
        fruit .necessary();
        fruit .nutrition();
    }

}

举个例子分析一下

//开始测试Vegetables蔬菜
        Vegetables vegetables= (Vegetables)shopcar.getListItem(Vegetables);
        vegetables.green();
        vegetables.nutrition();
(Vegetables)shopcar.getListItem(Vegetables);

这句代码是获取到Food类型的实例。不是Vegetables的实例

通过向下转型,赋值给子类引用

Vegetables vegetables= (Vegetables)shopcar.getListItem(Vegetables);

样子类实例又重新获得了因为向上转型而丢失的方法(gren和nutrition)


向下转型的意义

向下转型最大的作用是Java的泛型编程,作用巨大,Java中集合框架作用大都如此。

而在Android开发中,我们在Layout文件夹,用xml写的控件。为什么能在Activity等组件中通过 findViewById() 方法找到呢?为什么 findViewById(R.id.textview) 方法传入TextView的id后,还要转型为TextView呢?这就是 Java 向下转型的一个应用。

  • 27
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值