第一次尝试使用markdowm写博客哈
文章目录
- 1.多态的引入
- 2.重写和重载
- 3.避免在构造方法里面去调用重写
- 4.向上转型和向下转型
- 5.让你真正明白什么是多态
- 6.通过一些习题进行理解
1.多态的引入
首先说一下,这个想要使用多态需要我们满足的条件,然后具体的进行阐述:
1.必须要在我们的继承的体系之下,就是这个多态是基于继承关系的;
2.子类需要对于父类的方法进行重写;
3.通过父类的引用调用重写的方法;
首先我们通过下面的这个代码,看一下什么是—向上转型:
在我们的这个main方法里面,是定义了两个对象,一个是指向cat这个子类的引用,一个是指向dog这个子类的引用,这个new一个cat这个对象,这个时候,我们的cat这个子类赋值给了这个animal这个基类的对象,这个就是我们上面说的这个向上转型;
此外我们的这个向上转型可以通过参数的传递和方法的返回值实现:
参数的传递就是我们的func(父类对象),但是我们进行这个func函数调用的时候,使用的是这个func(子类)进行调用的,这个就是参数的传递引起的向上转型现象,这个我们传递的参数是子类的对象,但是这个方法里面使用的是父类的对象进行接收,这个时候就是向上转型;
方法的返回值就是func函数的返回值是一个父类的对象,但是我们的return的时候返回一个子类的对象,这个时候也是不会报错的,这个return子类对象到父类的过程就是向上转型;
2.重写和重载
其实这个学过C++的都知道这个函数的重载(因为我自己学过,所以更有体会),两个编程语言的这个机制都是相通的,我们的这个java里面的重载是方法重载,这个方法其实就是函数,这个里面的重载,其实和我们的函数重载没有太大的区别;
尤其是这个final这个关键字,它的作用和我们在C++里面学习的这个const更是如出一辙,就是相同的意思,我觉得就是想要换一个名字,彰显和别人的不同:
重写就是我们的这个方法的里面的参数,以及这个返回值都是要一样的
3.避免在构造方法里面去调用重写
就是这个构造方法里面加上我们的重写之后可能会出现很多的问题,下面的这个实例就是可能会出现的问题:
在这个里面,我们是写了一个B这个基类,我们D这个类是继承B的,因此我们的的这个主函数里面创建d这个对象的时候先回去调用父类的构造方法;
因为我们之前说过:执行顺序,父类的static方法,子类的static方法,父类的实例化,父类的构造,子类的实例化,子类的构造,因此在这个里面,我们没有静态的方法,因此这个就会从我们的父类的实例化和构造开始执行,这个时候父类里面没有实例化,因此这个时候直接执行我们的这个构造,这个时候的构造里面是一个func函数,这个函数在我们的父类和子类里面都是有实现的,但是这个时候在调用的时候我们调用的是子类的func函数,主要就是因为动态绑定(下面的一个点里面会说明):
运行的时候根据创建的对象的实际类型确定的,就是我们创建的是D类的对象,虽然这个func函数是在我们的B类里面的构造函数被调用的,但是因为我们创建的对象的类型,因此这个执行的时候执行的就是我们的D里面的func函数(和我们的创建对象的类型是一样的);
这个时候,我们执行子类里面的这个func函数,这个num打印的数值是我们的这个0而不是1,因为这个时候还没有进行这个num的初始化的工作,为什么呢,看下面的这个表:我们现在是执行的2这个步骤,这个时候我们是因为在额合格func函数跳转到了这个子类里面去,但是这个时候我们的执行顺序还是2这个步骤,当我们的3,4执行到子类的实例化和构造的时候,这个num才会被初始化,但是这个时候我们还是2这个阶段,因此这个num还是没有被初始化的,因此这个就是打印的结果就是0;
通过上面的这个例子,相信你也发现了,当我们的这个方法的重写和构造函数结合起来的时候,这个里面就会出现很多问题,因此我们不建议把两者结合起来使用,避免产生一些隐藏的让我们难以发现的问题;
4.向上转型和向下转型
我们上面的这个其实已经说明了什么是向上转型:例如我们的这个父类的引用指向的是我们的子类的对象,上面的这个animal animal1 = new cat()这样的写法,实际上就是这个animal这个父类的引用引用到的就是我们的子类的对象cat,这个从子类到父类,实际上就是向上转型的过程,前提是两个需要满足继承的关系,才可以进行转型;
向下转型就是和向上转型相反的,就是我们的基类被子类的对象引用所指向,这个时候需要进行强制类型转换,但是即使有些时候进行强制类型转换,也可能会出现一些问题,因为这个向下转型过程中出现的问题可能在编译阶段不会显示出来,在这个运行阶段会报错,这个也是常见的,下面的这个例子就是我们进行向下转型,强制类型转换的时候进行编译没有问题,但是运行之后就会出现问题:
这个截取的图片是没有运行的时候,编译器确实没有发现错误,但是给出了警告,实际上这个试运行无法通过的,就是因为我们的这个cat111向上转型成为这个animal1之后,我们的这个aniaml1的引用就是指向的我们的这个cat111这个对象,当我们强制类型转换的时候,这个是可以回来的;
但是,我们的这个aniaml1没有指向过这个dog111(这个主要是我们的cat111进行过向上转型,因此这个animal1的引用就是指向的我们的这个cat111这个对象,但是这份dog111没有向上转型),因此这个是无法通过我们的这个强制类型转换进行这个向下转型的操作的(这个错误编译器没有检查出来,但是确实无法运行通过);
5.让你真正明白什么是多态
上面也是说了很多,有的可能用处不大,但是都需要我们理解这些机制,下面的这个代码可以让我们直观清晰的感受到这个多态的现象,结合上面的语法进行这个现象的理解;
这个代码上面已经出现过,但是这个做了进一步的修改:我们定义了这个test函数在这个主方法里面,这个test函数的参数是我们的animal类的一个对象,就是父类的对象,但是我们传递参数的时候给的是子类的对象,这个时候从子类->父类的过程就是向上转型
在执行这个test方法的时候,我们的方法也不知道我们的传递参数具体是什么,但是当我们把这个animal这个对象传递过去的时候,这个时候打印的就是我们的这个小猫在吃鱼,当我们的这个animal传递过去的时候,这个时候打印的结果就是我们的小狗在吃骨头,像这样:相同的方法在调用的时候变现出来不一样的行为,这个现象就叫做多态;
在这个代码里面,我们综合使用了向上转型,方法的重写,以及这个继承的相关的知识,也是用了这个super关键字对于这个子类里面的构造函数进行实现(有参数的话就需要我们自己去进行实现)
动态绑定:编译的时候,无法确定这个test函数的行为,因为这个具体我们会传递什么参数还是不确定的,但是这个并不影响,我们在运行的时候,就可以知道具体调用哪个类的方法(像上面的这个里面,我们传递animal1的时候,就具体的调用了我们的cat里面的这个方法,传递这个animal2的时候,就会具体调用dog里面的方法),在这个过程中,动态就是我们的这个调用的方法随着传递的参数是可以改变的,这个就是动态,绑定就是原来的这个编译的时候的不确定性到传递参数之后的这个具体调用的方法的确定性,这个调用的方法和调用的方法连接在了一起,这个就是绑定;
6.通过一些习题进行理解
下面的这个代码的输出结果:
这个主要考察的就是我们对于这个构造函数的理解,当我们的这个基类里面实现了我们的这个有参数的构造函数的时候,我们的这个子类就不会提供默认的构造函数,屙屎需要我们自己去写,这个里面,因为这个基类的构造函数是有一个String类型的参数的,因此这个时候我们需要在这个子类里面去实现这个super(s)从而实现对于我们的这个形参的初始化工作,否则就会报错;
class Base{
public Base(String s){
System.out.print("B");
}
}
public class Derived extends Base{
public Derived (String s) {
System.out.print("D");
}
public static void main(String[] args){
new Derived("C");
}
}
下面的这个程序的执行结果:我们的执行顺序依然是这个父子类的静态方法,父类的实例化和构造,子类的实例化和构造方法;
这个里面没有静态的方法,因此这个时候直接进行实例化和构造的判断:这个主要就是捋清楚这个继承的层级关系,在这个里面,我们先到4这个地方,这个时候因为X是基类,因此会先去执行这个1这个地方,这个时候就会执行Y的构造函数,打印输出Y,然后我们就会到2这个位置,打印输出X,接着去执行子类的这个4这个位置(上面执行的都是父类的相关内容),主要就是因为想要执行子类的,首先就要执行父类的(执行父类的打印的结果是YX);
执行子类的时候,4打印结果Y,因为这个地方new了一个对象,然后执行5里面的内容,打印输出我们的Z;
class X{
Y y=new Y();//1
public X(){//2
System.out.print("X");
}
}
class Y{
public Y(){//3
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();//4
public Z(){//5
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
下面的这个代码的执行结果:
在下面的这个代码里面,我们的child是子类,main函数里面的这个子类的对象给了我们的父类的对象,这个就是向上转型的过程,但是因为这个p里面的成员变量是私有的,因此这个会出现编译报错的情况;
public class Person{
private String name = "Person";
int age=0;
}
public class Child extends Person{
public String grade;
public static void main(String[] args){
Person p = new Child();
System.out.println(p.name);
}
}
下面的这个还是向上转型,我们的这个B是父类,所以new B的时候,这个里面的输出结果就是我们的B,但是我们new D的时候,因为这个里面的进行的是向上转型,但是这个是属于D的对象因此这个b进行func函数的调用的时候,就会调用这个D里面的func函数,打印输出D,这个就是动态绑定;
class B {
public int Func() {
System.out.print("B");
return 0;
}
}
class D extends B {
@Override
public int Func() {
System.out.print("D");
return 0;
}
}
public class Test {
public static void main(String[] args) {
B a = new B();
B b = new D();
a.Func();
b.Func();
}
}
好了,上面的几个题目,希望可以帮助你对于向上转型和动态绑定有更深层次的理解和体会~~~