【Java16】多态

向上类型转换

对于引用变量 ,在程序中有两种形态:一种是编译时类型 ,这种引用变量的类型在声明它的时候就决定了;另一种则是运行时类型,这种变量的类型由实际赋给它的对象决定。

当一个引用变量的编译时类型和运行时类型不一致时,就出现了多态(Polymorphism)

对面向对象语言来说,所有的对象(Object),或者说类的实例,本质上都是引用变量。因此,多态最主要就是针对对象来说的:声明时引用变量指向的对象的类型,和运行时引用变量指向的对象的类型不一致。

java 复制代码
class BaseClass
{
  public int book = 6;
  public void base()
  {
    System.out.println("父类的普通方法");
  }
  public void test()
  {
    System.out.println("父类被覆盖的方法");
  }
}

public class SubClass extends BaseClass
{
  // 覆盖
  public String book = "Java疯狂讲义"; // 同名实例
  public void test()
  {
    System.out.println("子类的覆盖父类的方法");
  }
  public void sub()
  {
    System.out.println("子类的普通方法");
  }
  public static void main(String[] args)
  {
    var bc = new BaseClass(); // 声明一个BaseClass的对象,编译时和运行时的类型一致,不存在多态
    System.out.println(bc.book); // 6
    bc.base(); // 父类的方法
    bc.test(); // 父类的方法
    //-------------
    var sc = new SubClass(); // 声明一个SubClass的对象,同样不存在多态
    System.out.println(sc.book); // 子类实例变量覆盖了父类的实例变量,输出"Java疯狂讲义"
    sc.base(); // 子类方法覆盖了父类的方法
    sc.test(); // 子类的普通方法
    //-------------
    BaseClass polymophicBC = new SubClass(); // 编译时类型是BaseClass,运行时类型是SubClass,发生了多态
    System.out.println(polymophicBC.book); // 输出6,是父类的实例变量
    polymophicBC.base(); // 执行父类的base方法
    polymophicBC.test(); // 执行当前类,也就是运行时类型SubClass的test方法
    // polymophicBC的编译时类型是BaseClass,没有提供sub方法
    // 因此调用sub方法时会出现编译错误
    // polymophicBC.sub();
  }
}
  • 28~31行是标准的对象的声明与使用;
  • 33~36行是标准的继承;
  • 38~41行出现了多态。

对变量polymophicBC来说,编译时类型是BaseClass(声明语句左端),运行时类型是SubClass(声明语句右端)。

把一个子类对象赋给一个父类引用变量,在这个过程中发生了什么?

类型转换

多态在Java中实现的机制就是把子类对象赋值给父类引用变量 ,这实际上就是一种类型装换 ,具体也叫向上转型(up-casting)。这种类型转换由系统自动完成。

从声明语句左边来看:

  • BaseClass bc = new BaseClass();
  • BaseClass polymophicBC = new SubClass();

bcpolymophicBC都是BaseClass引用类型,但是它俩在执行同名函数test()时却产生了不同的结果。这种调用同一个方法却出现不同行为特征的现象,就是多态

多态机制下,父类引用变量在运行时总是调用子类的方法,也就是说呈现出子类的行为特征而不是父类的行为特征。

对象的实例变量不具有多态性。

  • 第39行,book仍然是父类的实例变量。

引用变量在编译阶段只能调用其编译时类型拥有的方法,但是在运行时可以执行其运行时类型拥有的方法。

  • 第44行,BaseClass不具有sub()方法,因此不能调用,发生编译错误;
  • 但第41行,BaseClass具有test()方法,因此可以调用,且在运行时执行的是SubClass的同名方法。

使用var时,并不能改变编译时类型,因此也可能会发生多态:

java 复制代码
var v1 = new SubClass(); // 自动推断是SubClass,没有多态
var v2 = polymophicBC(); // 赋值,v2自动推断是BaseClass
// 此时调用sub方法,遵照多态机制,会发生编译错误
// v2.sub();
强制类型转换

按照上面规则,引用变量只能调用编译时类型 拥有的方法,即使它的运行时类型对象实际上包含了远不止这些方法。

如何让这个引用变量调用运行时类型所拥有的方法呢?

既然普通的多态依赖的是向上转型 ,即把子类对象赋给父类引用变量,类似于我们把double基本变量赋给float。那么也可以反过来,执行强制类型转换

强制类型转换借助类型转换运算符,和C++类似,就是()

类型转换运算符可以实现基本类型之间的转型,也能实现引用变量的转型。

请注意,强制类型转换不是万能的,受到如下约束:

  • 基本类型之间转型只能在数值类型中进行(整数型、字符型、浮点型)。数值型和布尔型之间不能转换(C++中是可以的)。
  • 引用类型转换只能在具有继承关系(直接继承或间接继承都行)的类型之间进行。

强制类型转换在这里,就是把父类实例转换为子类类型。即其编译时类型是父类类型,运行时类型是子类类型。这时候可以使用强制类型转换。

java 复制代码
public class ConversionTest
{
  public static void main(String[] args)
  {
    var d = 13.4; // float
    var l = (long) d; // 强制类型转换
    //------
    var in = 5; // int
    // var b = (boolean) in; // 错误,数值型不能转换为布尔型
    //------
    Object obj = "Hello"; // 向上转型,"Hello"是String,是Object的子类。这实际上就是多态,只不过这时候obj不能执行String拥有的方法
    var objStr = (String) obj; // 强制类型转换,父类/基类和子类,正常
    System.out.println(objStr); // 做为String类型输出
    //------
    Object objPri = Integer.valueOf(5); // 向上转型,运行时类型是Integer
    var in = (Integer) objPri; // 强制类型转换,基类和子类,正常
    // var str = (String) objPri; // objPri运行时时Integer,和String不存在继承关系,运行时会报错(类型转换异常,ClassCastException)
  }
}

再解读一下第12行:

  • objStr,虽然使用了var,但由于使用了强制类型转换符(String),自动推断它是String类型;
  • 此时objObject类型;
  • 因此,将obj赋值给objStr,实际上是把父类对象赋值给子类引用变量,这就和之前的upcasting 正好相反,我们也可以称之为downcasting
小结
  1. 把子类对象(右)赋给父类引用变量(左)时,触发向上转型,这种转型是自动的、总是成功的。这种转型表明这个引用变量编译时是父类类型,运行时是子类类型。它表现出的是子类的行为方式,但是编译时不能调用子类的方法。同时,实例变量仍然是父类的。
  2. 使用强制类型转换可以把一个引用变量转换成其子类类型。这种转换必须是显式的,而且不一定成功(若两端不存在继承关系)。
instanceof

使用instanceof运算符可以判断是否可以执行类型转换,以避免出现ClassCastException

java 复制代码
if (objPri instanceof String)
{
  var str = (String) objPri;
}

instanceof用来判断前面的对象是否是后面的类或者其子类的实例,是的话返回true,否则返回false

在Java 17中,为instanceof增加了快捷用法,来简化上面的判断代码块:

java 复制代码
// 传统instanceof,先判断,再转换,最后使用
if (obj instanceof String) // 先判断
{
  var s = (String) obj; // 再转换
  System.out.println(s.toUpperCase()); // 最后使用
}

// Java 17的模式匹配,同时完成判断和类型转换
if (obj instanceof String s)
{
  System.out.println(s.toUpperCase());
}
相关推荐
尚学教辅学习资料7 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
明月看潮生19 分钟前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
雷神乐乐23 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|28 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
南宫理的日知录30 分钟前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
逊嘘1 小时前
【Java语言】抽象类与接口
java·开发语言·jvm
Half-up1 小时前
C语言心型代码解析
c语言·开发语言
morris1311 小时前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
Source.Liu1 小时前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng1 小时前
【Rust中的迭代器】
开发语言·后端·rust