chapter7 面向对象之向上转型、向下转型及多态
1.对象类型转换
将一个类型强制转换成另一个类型的过程被称为类型转换。本节所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,会抛出 Java 强制类型转换(java.lang.ClassCastException)异常。
Java 语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。Java 中引用类型之间的类型转换(前提是两个类是父子关系)主要有两种,分别是向上转型(upcasting)和向下转型(downcasting)。
1.1向上转型
父类引用指向子类对象为向上转型,将一个子类对象提升成为一个父类,因此可以直接访问或者调用父类所有的没有被private修饰的成员,而如果是private成员变量,那么根据封装的道理进行访问调用。如果方法中子类中有重写的,那么输出的是子类中的信息,即看子类的具体实现,语法格式如下:
其中,fatherClass 是父类名称或接口名称,obj 是创建的对象,sonClass 是子类名称。
向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员(这里所说的成员是没有被private修饰的成员),不能调用子类类型中特有成员,最终运行效果看子类的具体实现(比如说方法被子类覆盖后,那么向上转型之后,调用这个方法,输出的就是看子类,再比如说,如果父类中的一个变量被子类再一次赋值更改了,那么这时候访问这个变量的时候,输出的就是子类这个值了)。
有人问:如果父类中的成员是私有的,那么这样子可以访问父类中的成员吗?答案是否定的,即使没有发生类型转换,也是不行,这是为什么,这就回到封装来解释,因为私有,所以除了本类可以访问之外,其他类都不可以方法,只能在本类对外提供一个方法,才可以访问到。
代码实例:
父类:
public class Figure {
public int length;
public int width;
public Figure(int length, int width){
this.length = length;
this.width = width;
}
public void area(){
System.out.println("这个类是父类,没有办法计算具体类型形状的平面图形的面积");
return;
}
}
子类1:三角形
public class FigureTranigle extends Figure {
public FigureTranigle(int length, int width) {
//调用父类的构造函数,如果没有写,那么就会默认调用父类无参的构造方法,此时如果父类中没有无参的构造方法,就会发生报错
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
}
@Override
//重写方法
public void area(){
System.out.println("三角形的面积:" + this.length * this.width / 2.0);
}
//本类的特有方法
public void display(){
System.out.println("我是矩形");
}
}
子类2:矩形
public class FigureJvXing extends Figure {
public FigureJvXing(int length, int width) {
//调用父类的构造函数,如果没有写,那么就会默认调用父类无参的构造方法,此时如果父类中没有无参的构造方法,就会发生报错
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
}
@Override
//重写方法
public void area(){
System.out.println("矩形的面积为:"+this.width * this.length);
}
//本类的特有方法
public void display(){
System.out.println("我是矩形");
}
}
测试类:
public class FigureTest {
public static void main(String[] args){
FigureTranigle figureTranigle = new FigureTranigle(20,10);//构建子类对象
figureTranigle.area();//调用子类方法,从而三角形的面积
FigureJvXing figureJvXing = new FigureJvXing(10,20);//构建子类对象
figureJvXing.area();//调用子类的方法,从而输出矩形面积
Figure figure = new Figure(3,3);
figure.area();//调用父类中的方法
System.out.println("下面的代码和上面的代码结果相同,但是代码少,从而说明了多态的好处");
//调用方法,此时注意的是方法的参数是父类,但是这里却是子类,此时是向上转换,没有发生强制转换
methodArea(new FigureTranigle(20,10));//输出100.0
//调用方法,此时注意的是方法的参数是父类,但是这里却是子类,此时是向上转换,没有发生强制转换
methodArea(new FigureJvXing(10,20));//输出200
//调用方法,此时注意的是方法的参数是父类,这里也是父类
methodArea(new Figure(3,3));//输出这个类是父类,没有办法计算具体类型形状的平面图形的面积
}
public static void methodArea(Figure figure){
figure.area();
}
}
结果:
在向上转型中有几点需要作为补充的:
①成员变量的问题:如果在父类中含有某一个变量num,然后在它的子类中再次定义统一个变量名的变量num,也是同样类型,那么向上转型之后,访问这个变量,输出的是子类那个值吗?答案是错误的,应该输出的是父类中的值。看代码即可知道:
//父类
public class Figure {
public int length;
public int width;
public int num = 2;//一个变量
public Figure(int length, int width){
this.length = length;
this.width = width;
}
public void area(){
System.out.println("这个类是父类,没有办法计算具体类型形状的平面图形的面积");
return;
}
}
//子类
public class FigureTranigle extends Figure {
public FigureTranigle(int length, int width) {
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
this.num = 9;//对父类中的这个变量进行重写,所以再访问这个变量的时候输出9
}
@Override
public void area(){
System.out.println("三角形的面积:" + this.length * this.width / 2.0);
}
}
public class FigureTest {
public static void main(String[] args){
Figure figure2 = new FigureTranigle(20,30);//向上转型
System.out.println(figure2.num);//输出9
System.out.println(new FigureTranigle(3,5).num);//输出9
}
}
}
结果:
注意这样的情况(这一种情况还不是很懂,如果有大佬懂的,请指教):
父类和上面一种情况的父类一样,这种情况的子类的代码和上一种情况的子类有一点不同,就是num变量那里有所区别。
//父类和上面父类的一样
public class Figure {
public int length;
public int width;
public int num = 2;//一个变量
public Figure(int length, int width){
this.length = length;
this.width = width;
}
public void area(){
System.out.println("这个类是父类,没有办法计算具体类型形状的平面图形的面积");
return;
}
}
//子类
public class FigureTranigle extends Figure {
int num = 9;
public FigureTranigle(int length, int width) {
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
}
@Override
public void area(){
System.out.println("三角形的面积:" + this.length * this.width / 2.0);
}
}
//测试类
public class FigureTest {
public static void main(String[] args){
Figure figure2 = new FigureTranigle(20,30);//向上转型
System.out.println(figure2.num);//输出2
System.out.println(new FigureTranigle(3,5).num);//输出9
}
}
}
结果:
1.2向下转型
向下转型:与向上转型相反,子类对象指向父类引用为向下转型,值得注意的是在进行向下转型之前要求先将一个子类对象通过向上转型提升成为父类对象(这里可以理解是乔装打扮成另一个人,但是他的身份依然是这个人,并没有因为化妆就变成另一个人)然后再进行向下转型,,向下转型语法格式如下:
向下转型可以调用子类类型中所有的成员,不过需要注意的是在进行向下转型的时候,要用到了向上转型,从而使父类引用对象指向子类,即FatherClass fatherClass = new SonClass();,然后才可以进行向下转型,即SonClass sonClass = (SonClass)fatherClass。如果不这样做的话,可以知道会发生报错。首先考虑一下如果父类是一个接口或者抽象类的情况,那么他们不可以实例化,如果没有先进行向上转型的话,那么就和我们知道的结论(抽象类、接口不可以实例化)相违背。那么一般类可以实例化,那难道不可以吗?答案是不可以,我们可以试着运行一下,那么会抛出一个异常ClassCastException,表示类型转换异常,从而我们的结论得证,在进行向下转型之前,必须要先进行向上转型。
由此我们可以知道,在转型中,自始至终都是子类对象在做类型转换。
父类:
public class Figure {
public int length;
public int width;
public Figure(int length, int width){
this.length = length;
this.width = width;
}
public void area(){
System.out.println("这个类是父类,没有办法计算具体类型形状的平面图形的面积");
return;
}
}
子类1:
public class FigureTranigle extends Figure {
public FigureTranigle(int length, int width) {
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
}
@Override
public void area(){
System.out.println("三角形的面积:" + this.length * this.width / 2.0);
}
//本类的特有方法
public void display(){
System.out.println("我是三角形");
}
}
子类2:
public class FigureJvXing extends Figure {
public FigureJvXing(int length, int width) {
super(length, width);
this.length = length;
this.width = width;//初始化当前对象的长、宽
}
@Override
public void area(){
System.out.println("矩形的面积为:"+this.width * this.length);
}
//本类的特有方法
public void display(){
System.out.println("我是矩形");
}
}
测试类:
public class FigureTest {
public static void main(String[] args){
/*先进行向上转型,将一个子类对象提升成为父类对象,此时这个父类对象只能
*直接调用非private成员,如果要访问父类的私有成员,那么回到封装的知识点
*上,在这个类中对外提供一个方法,从而可以访问到,但是父类对象不可以调
*用子类的特有方法。
*/
Figure figure2 = new FigureTranigle(20,30);
//向上转换完毕之后,那么就可以进行向下转型了,将这个父类对象强制转换成为子类对象。
if(figure2 instanceof FigureTranigle) {
//判断figure2是否为子类FigureTranigle的一个实例,如果是,才可以进行转型。因为转型的前提条件就是两个类是继承的关系。
FigureTranigle figureTranigle1 = (FigureTranigle) figure2;
//向下转型完毕之后,这个对象就可以访问子类的所有成员,当然如果是private修饰的成员,那么不可以直接访问,此时回到封装知识点
figureTranigle1.area();//子类改写父类的方法,
figureTranigle1.display();//调用子类中特有的方法
}
}
}
结果:
有一点需要注意上面提到,在进行向上转型的完毕之后,为了防止出现错误,用到了instacneOf,用来判断一个对象是否为某一个类、接口的实例,因此在进行向下转型之前,要判断这时的父类对象是否为对应子类(所谓的对应,就是你接下来进行向下转型的那个子类)的一个实例,可以避免出现异常。
那么为什么不是父类的一个实例呢?首先进行转型的两个类必须是继承关系。其次,如果向上转型的时候,子类是父类另一个子类,比如上面的代码,不是FigureTranigle这个子类,而是FigureJvXing这个子类,经过向上转型的时候,figure2是Figure(父类)、FigureJvXing(子类2)的实例,那么接下来判断的不是对应子类的一个实例,而是判断是否为父类的一个实例,此时可以进行接下来的向下转型,然后就会发生异常。看下面图片得证:
instanceOf 用法:
obj instanceOf Class
obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。
2.多态
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才可以直接调用父类的没有被private修饰方法(如果被private修饰,那么通过回顾封装的知识进行调用),如果在父类中某些方法被子类重写了,那么调用这个方法的时候,输出的是子类中的数据信息,但是不能调用子类特有的方法。