关于Java多态

在面向对象的语言中,有三个基本特征:封装,继承,多态。封装是通过将某些特征和行为合并起来创建新的数据类型;继承是子类通过 extends 关键字来获取父类的方法和属性,同时继承允许将对象视为它自己本身的类型或子类类型来处理;多态的作用就是消除类型之间的耦合关系,多态方法调用允许一种类型表现出与其他类型的区别,只要他们是从同一个父类中继承而来的即可。

向上转型

我们知道对象既可以作为它自己本身的类来使用,也可以作为它的父类类型来使用,像这种把对某个对象的引用作为其父类类型的引用的做法,被称之为向上转型。我们看看下面这个例子:

public class Ball {


    public void play() {
        System.out.println("play ball");
    }

    /**
     * 该方法需要传入一个 Ball 类型的对象
     *
     * @param ball
     */
    public static void fun(Ball ball) {
        ball.play();
    }

    public static void main(String[] args) {
        //创建一个 baseball 对象
        Baseball baseball = new Baseball();
        fun(baseball);
        fun(new Basketball());
    }
}

class Baseball extends Ball {

    /**
     * 重写 play 方法
     */
    public void play() {
        System.out.println("play baseball");
    }
}

class Basketball extends Ball {

    /**
     * 重写 play 方法
     */
    public void play() {
        System.out.println("play basketball");
    }
}

在 fun() 方法中需要传入一个 Ball 类型的对象,同时也接受任何继承自 Ball 类的对象,例子中一个 Baseball 类的对象,这样做是允许的,因为 Baseball 继承自 Ball,所以 Ball 的接口必定存在于 Baseball 中。

绑定

但是这样做也带来一个问题,由于 fun() 方法接受一个 Ball 引用,那么编译器要怎样才能知道这个 Ball 引用指向的是 Baseball 对象还是 Basketball 对象呢,编译器是无从得知的,所以我们在此引入一个概念叫做绑定。先说下概念,绑定就是将一个方法调用和一个方法主体关联起来,分为前期绑定后期绑定,上述做法就是前期绑定,当只告诉编译器要传入一个 Ball 引用的时候,编译器无法知道究竟要调用那个方法才对。所谓后期绑定,就是在运行的时候根据对象的类型进行绑定,后期绑定也叫做动态绑定运行时绑定

Java 中除了 static 方法和 final 方法之外,其它所有的方法都是后期绑定。将方法声明为 final,不仅可以防止被覆盖,还能有效关闭动态绑定,这样编译器就可以为 final 方法调用生成更加有效的代码。但是这样做对整体性能不会带来什么改观,我们前面已经说过了,要谨慎使用 final,根据设计来判断是否使用。

当我们知道 Java 中所有方法都是通过动态绑定来实现多态之后,我们可以只需要与父类打交道了。那么上面的例子中 fun() 方法的传递的参数可以这么初始化:

//创建一个 Ball 对象
Ball ball = new Baseball();
fun(ball);

这里我们创建了一个  Baseball 对象,并且把得到的引用直接赋值给 Ball 的对象,这种将一种类型赋值给另一种类型的做法看似错误,但实际上是没有问题的,因为通过继承,Baseball 就是一种 Ball。

我们再来看看下面这个例子:

public class Ball {

    public void play() {
        System.out.println("play ball");
    }

    /**
     * 该方法需要传入一个 Ball 类型的对象
     * @param ball
     */
    public static void fun(Ball ball) {
        ball.play();
    }

    /**
     * 随机返回 Ball 的子类对象
     * @return
     */
    public static Ball next(){
        //产生 0 到 1 的随机数
        int random = new Random().nextInt(2);
        if(random == 0){
            return new Baseball();
        }else{
            return new Basketball();
        }
    }

    public static void main(String[] args) {
        //调用 play 方法
        next().play();
    }
}

class Baseball extends Ball {

    /**
     * 重写 play 方法
     */
    public void play() {
        System.out.println("play baseball");
    }
}

class Basketball extends Ball {

    /**
     * 重写 play 方法
     */
    public void play() {
        System.out.println("play basketball");
    }
}

我们通过 next() 方法随机产生 Ball 的引用(向上转型是在 return 语句中发生的),然后通过该引用来调用 play() 方法,编译器是无法知道具体的类型到底是什么的。因此,在编译时,编译器不需要获得任何具体的类型信息就能进行正确的调用,对 play() 方法的调用都是通过动态绑定进行的。

这种只与父类接口通信的程序是可扩展的,因为可以从通用的父类继承出新的子类,从而可以添加新的功能,那些调用父类接口方法不需要任何改动就可以作用与新类。

还有一点,就是如果某个方法是静态的,那么它的行为就不具有多态性,因为静态方法是与类关联的而不是与某个对象相关联的。

欢迎关注公众号:一盐难进

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值