【Java16】多态

向上类型转换

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

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

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

在这里插入图片描述

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时,并不能改变编译时类型,因此也可能会发生多态:

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

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

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

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

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

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

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

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

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

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

if (objPri instanceof String)
{
  var str = (String) objPri;
}

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

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

// 传统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());
}
<think>我们正在讨论Java中的多态。根据用户要求,我们需要详细解释Java多多的定义、原理、实现方式和应用场景,并参考提供的引用内容。同时,我们需要在回答中适当添加引用标记,并生成相关问题。 引用内容摘要: [^1]: 多态Java面向对象的三大特性之一,体现了代码的灵活性和可扩展性。 [^2]: 多态的本质是同一操作作用于不同对象,产生不同的执行结果,通过运行时动态绑定实现。 [^3]: 多态的核心在于“编译看左边,运行看右边”,即编译时类型是父类,运行时根据实际对象类型调用方法。 [^4]: Java的方法调用分为静态方法调用(静态绑定)和实例方法调用(动态绑定,多态的核心)。 回答结构: 1. 定义:解释多态的概念,引用[1][2]。 2. 原理:包括动态绑定、方法表、JVM实现机制,引用[3][4]。 3. 实现方式:继承、方法重写、向上转型,引用[1][2]。 4. 应用场景:举例说明多态在实际编程中的应用。 注意:使用LaTeX格式的数学表达式(虽然多态中数学公式不多,但如果有表达式则按规则书写),并生成相关问题。 </think> ### Java多态全面解析:定义、原理、实现与应用 #### 一、多态的定义 多态(Polymorphism)是Java面向对象编程的三大核心特性之一,指**同一操作作用于不同对象时产生不同行为**的能力。其本质可概括为: - **编译时类型与运行时类型分离** 编译时看父类(声明类型),运行时看子类(实际类型),即"编译看左边,运行看右边"[^3]。 - **统一接口,多样实现** 通过父类引用调用方法,实际执行由子类对象决定[^2]。 ```java Animal animal1 = new Dog(); // 父类引用指向子类对象 Animal animal2 = new Cat(); animal1.sound(); // 输出"汪汪!"(实际调用Dog类方法) animal2.sound(); // 输出"喵喵!"(实际调用Cat类方法) ``` #### 二、核心原理:动态绑定 多态依赖**动态绑定(Dynamic Binding)** 机制实现: 1. **方法调用过程** ```mermaid graph LR A[调用animal.sound()] --> B[JVM查找对象实际类型] B --> C{方法表vtable} C -->|存在重写| D[执行子类方法] C -->|无重写| E[执行父类方法] ``` 2. **关键组件** - **方法表(vtable)**:每个类维护包含方法地址的表 - **invokevirtual指令**:JVM通过此指令实现动态方法调用[^4] - **运行时类型识别(RTTI)**:确定对象实际类型 3. **与静态绑定的对比** | 特性 | 动态绑定(多态) | 静态绑定 | |--------------|--------------------------------|------------------------| | **绑定时机** | 运行时 | 编译时 | | **方法类型** | 实例方法(非private/final) | static/private/final方法 | | **指令** | invokevirtual | invokestatic | | **性能** | 稍慢(需查表) | 更快 | #### 三、实现方式与必要条件 实现多态需同时满足三个条件: 1. **继承关系** 存在父类与子类的层次结构 ```java abstract class Animal { // 抽象父类 abstract void sound(); } ``` 2. **方法重写(Override)** 子类重新定义父类方法 ```java class Dog extends Animal { @Override // 注解确保正确重写 void sound() { System.out.println("汪汪!"); } } ``` 3. **向上转型(Upcasting)** 父类引用指向子类对象 ```java Animal myPet = new Dog(); // 向上转型 ``` > **特殊场景**: > - **接口多态**:通过接口引用实现(`List<String> list = new ArrayList<>()`) > - **构造器多态**:子类构造器隐式调用`super()` #### 四、应用场景与实战价值 1. **框架扩展性** Spring框架的依赖注入: ```java @Autowired private PaymentService payment; // 可注入CreditCardPay/Alipay实现 ``` 2. **集合泛型处理** ```java List<Shape> shapes = Arrays.asList(new Circle(), new Square()); shapes.forEach(Shape::draw); // 自动调用不同绘制逻辑 ``` 3. **模板方法模式** 父类定义算法骨架,子类实现具体步骤: ```java abstract class Game { void init() { /* 通用初始化 */ } abstract void start(); // 子类自定义 // 模板方法 final void play() { init(); start(); } } ``` 4. **解耦调用方与实现方** 调用方只需知道父类接口,无需关注子类细节: ```java public void feed(Animal animal) { animal.eat(); // 传入Dog/Cat对象行为不同 } ``` #### 五、优势与挑战 | **优势** | **挑战** | |-----------------------------------|-----------------------------------| | ✅ 提高代码扩展性(开闭原则) | ❗ 过度使用降低代码可读性 | | ✅ 降低模块耦合度 | ❗ 性能损耗(方法表查询) | | ✅ 增强代码复用性 | ❗ 调试难度增加(需跟踪运行时类型)| > **最佳实践**: > - 优先使用接口定义行为 > - 避免在构造器中调用可重写方法 > - 对需要多态销毁的对象声明虚析构(C++场景) --- ### 总结 Java多态通过**动态绑定**机制实现"同一接口,多样行为",其核心技术是运行时方法查找(invokevirtual指令)和方法表(vtable)机制。合理运用多态可显著提升代码的灵活性和可扩展性,是构建复杂系统的核心设计模式[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值