Java自学多态——自学笔记

写多态前我们需要知道实现多态的基础是什么,基础就是要有继承和塑型(对象的类型转换)。

一、塑型的概念

塑型即类型转换,基本数据类型的变量可以类型转换,对象也可以类型转换。下面只写对象的类型转换。
首先我们得知道,对象的塑型只能出现三种情况,只能被塑型为:

任何一个父类类型。即任何一个用子类new出来的对象都可以被塑型为一个父类变量,即用一个父类引用来接收子类对象。
一般声明的格式为 父类 引用变量 = new 子类(),这种塑型也称作向上转型,即自动塑型

对象所属的类实现的一个接口。虽然不能用接口生成对象,但可以声明接口的引用变量,接口的引用变量可以指向实现了次接口的类对象。

或者回到它自己所在的类。一个对象被塑型为父类或接口后,还可以再被塑型。回到它自己所在的类。
声明格式为:子类 引用变量 = (子类)最开始的子类对象,现在是被向上转型为父类类型的对象。这种转换称作强制转换。

这里引用一篇文章写得很好的:https://blog.csdn.net/xyh269/article/details/52231944

这篇文章中把上面三种情况都使用到了。

需要说明的是,对象的塑型并没有改变它的类型,而是暂时将其当成更一般的类型
当我们用System.out.println(引用变量.getclass().getName());去验证的时候,就算这个对象被塑型为父类类型,但打印出来仍是属于子类。

二、塑型后方法的调用

子类对象被塑型为父类类型,那么调用子类和父类都有的方法时,实际调用的是哪个方法呢?

我们可以这样考虑(不一定对),前面说过塑型并没有改变类型,还是原本对象的类型。那么调用的时候自然还是去调用对象原本的类型的方法。

假如类层次如下:
在这里插入图片描述

Manager man = new Manager();
Employee emp1 = new Employee();
Employee emp2 = new (Employee)man;
emp1.computePay(); //调用Employee类中的computePay()方法
man.computePay(); //调用Manager类中的computePay()方法
emp2.computePay(); //调用Manager类中的computePay()方法

这段代码,Employee类及Manager类中都声明了computePay()方法。将Manager类对象塑型为Employee类之后,当调用它的computePay()方法时,首先在它的本来类Manager中查找,如果不存在,才在其父类中进行查找。如果不存在,才再其父类中进行查找。

当然这里我们可以把这种情况理解为最理想的情况。为什么说最理想的情况呢,是因为子类和父类都有一些方法时可以直接这样,但是实际可能不会这样。比如:

package en.edu.dlnu;

public class  Test{
    public static void main (String []args){
    	Cycle unicycle=new Unicycle();//向上转型
    	Cycle cycle=new Unicycle();
    	
    	cycle.ride();
    	cycle.wheels("cycle", 2);
        
        unicycle.ride();
        unicycle.wheels("unicycle", 1);
        //((Unicycle)unicycle).balance();//向下转型
        unicycle.balance();  //这里是会报错的
    }
}
class Cycle {
    public void ride(){
        System.out.println(" ride Cycle");
    }
    
    public String wheels(String name,int i){
        System.out.println(name+"这种交通工具有"+i+"个轮子");
        return name;
        
    }
}
class Unicycle extends Cycle {
	@Override
	    public void ride(){
	        System.out.println("ride unicycle");
	    }
	    
	    public void balance(){
	        System.out.println("Unicycle balance");
	    }
	}

上面这段代码是会报错的,错误内容是The method balance() is undefined for the type Cycle,就在 unicycle.balance(); 这里,按照上面写的方法的调用来看,这个unicycle是子类的对象指向的父类引用,那么应该现在自己本类里寻找balance()方法,Unicycle 这个子类中确实也有这个方法,为什么还会报错呢?下面就需要更深层次的说明一下了。

三、方法调用深入之多态性之编译期多态和运行期多态

要解释上面的问题就需要讨论编译期多态和运行期多态的区别了,什么时候是编译期多态什么时候是运行期多态了。

一、编译期多态
我们根据方法是否在子类和父类中都存在来分几种情况看一下:
引用另一篇文章的例子:原文地址:https://blog.csdn.net/weixin_39089680/article/details/79650723

情况一
当doWord()方法存在于SuperClass(主类)中,而不存在于子类中,即:
//父类
class SuperClass
{
public void doWork()
{System.out.println(“SuperClass.doWork()…”);}
}

//子类
class SubClass extends SuperClass
{

}

class MethodCallDemo{
public static void main(String[] args)
{
SuperClass clz = new SubClass();
clz.doWork();//此时会输出 SuperClass.doWork()…
}
}

此时执行结果是: 编译通过,执行 SuperClass的doWork(),执行过程是先从SubClass中找doWork()方法,找不到,再去SuperClass中找doWork()方法。 子类没有这个方法,然后去父类找,没有问题。

情况二
当doWord()方法存在于SubClass(子类)中,而不存在于父类中,即:
//父类
class SuperClass
{

}

//子类
class SubClass extends SuperClass
{
public void doWork()
{System.out.println(“SubClass.doWork()…”);}
}

class MethodCallDemo{
public static void main(String[] args)
{
SuperClass clz = new SubClass();
clz.doWork();//此时编译错误
}
}

此时执行结果是: 编译报错,编译时期会去编译类型(SuperClass)中找是否有doWork方法:
找到: 编译通过;
找不到:编译报错。
这时候就出现了之前提过的问题,明明本类(子类)中有这个方法,但是为什么还是出错了。这个例子和上面出错的例子有个相似之处就是调用的方法只在子类中存在,在父类中不存在。出现这个的原因就是 “编译看左边,运行看右边” ,即编译的时候去父类中检查这个方法是否存在,然后运行的时候再去看是调用子类的方法还是父类的方法。

总结一下
多态中的成员访问特点

A. 成员变量:编译看左边,运行看左边。
B. 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
C. 成员方法:编译看左边,运行看右边。由于成员方法存在方法重写,所以它运行看右边。
D. 静态方法:编译看左边,运行看左边。静态和类相关,算不上重写,所以,访问还是左边的。

四、多态性的体现

多态性体现初学可以说是很难真正的理解,可能最简单的理解就是父类引用指向子类对象,然后运行时再去决定调用那个方法。这么说起来有些太抽象难懂了。
从以上的的例子中我们可以看出,当我们定义了一个父类的引用然后指向了子类的对象,这时候利用这个对象调用方法时就是多态性的体现了。前提是这个方法是非静态的,根据“编译看左边,运行看右边”的原则,这个原则就是多态性的关键之处了。这是因为在运行期间才实现的动态绑定,将父类的引用绑定到了子类的对象上,从而用父类的引用去调用父类和子类都有的方法时,实际上调用的是子类的方法,这就是多态性。虽然是父类的引用,但是在运行期间却绑定到了子类对象上。
这篇文章写的非常不错:https://blog.csdn.net/dan15188387481/article/details/49668585

五、动态绑定的概念

上面其实已经说了,这里再单独抽出来写的好看一些。

若在程序运行以前执行绑定(由编译器和链接程序,如果有的话)就叫做早期绑定。

如果绑定在运行期间进行,就称为“后期绑定”,后期绑定也叫做“动态绑定”或“后期绑定”。这是因为在运行期间才实现的动态绑定,将父类的引用绑定到了子类的对象上。

若一种语言实现了后期绑定,同时就必须提供一些机制,可在运行期间判断对象的类型,并分别调用不同的方法。

总结一下:

  1. 多态中的成员访问特点

    A. 成员变量:编译看左边,运行看左边。
    B. 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
    C. 成员方法:编译看左边,运行看右边。由于成员方法存在方法重写,所以它运行看右边。
    D. 静态方法:编译看左边,运行看左边。静态和类相关,算不上重写,所以,访问还是左边的。

  2. 多态性的体现
    ①一个父类的引用指向了子类的对象,对象调用非静态的方法时,②根据“编译看左边,运行看右边”的原则,这个原则就是多态性的关键之处了。③这是因为在运行期间才实现的动态绑定,将父类的引用绑定到了子类的对象上④,从而用父类的引用去调用父类和子类都有的方法时,实际上调用的是子类的方法,这就是多态性。

  3. 动态绑定
    运行期间才实现的动态绑定,将父类的引用绑定到了子类的对象上。

最后在给个例子来让理解一下塑型:Fu f = new Zi();----------首先了解变量F到底是什么,把这句子分2段:Fu f;这是声明一个变量f为Fu这个类,那么知道了f肯定是Fu类。然后我们f=newZi();中建立一个子类对象赋值给了f,结果是什么??

结果是,拥有了被Zi类函数覆盖后的Fu类对象----f------。

也就是说:

只有子类的函数覆盖了父类的函数这一个变化,但是f肯定是Fu这个类,也就是说f不可能变成其他比如Zi这个类等等(突然f拥有了Zi类特有函数,成员变量等都是不可能的)。所以f所代表的是函数被复写后(多态的意义)的一个Fu类,而Fu类原来有的成员变量(不是成员函数不可能被复写)没有任何变化。
这也解释了为什么对象的塑型没有改变类型了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值