Java 中语法上实现多态的方式分为两种:1. 重载、2. 重写,重载又称之为编译时的多态,重写则是运行时的多态。

那么底层究竟时如何实现多态的呢,通过阅读『深入理解 Java 虚拟机』这本书(后文所指的书,如无特殊说明,指的都是这本书),对多态的实现过程有了一定的认识。以下内容是对学习内容的记录,以备今后回顾。

写着写着突然发现内容有点多,分为上和下,上主要记录重载的知识点,下则是重写的相关知识点。

重载

重载就是根据方法的参数类型、参数个数、参数顺序的不同,来实现同名方法的不同调用,重载是通过静态分派来实现的,那么什么是静态分派呢,先展示一下书中的示例代码:

public class StaticDispatch {
static abstract class Human {
} static class Man extends Human {
} static class Woman extends Human {
} public void sayHello(Human guy) {
System.out.println("hello,guy!");
} public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
} public void sayHello(Woman guy) {
System.out.println("hello,lady!");
} public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
//输出:
//hello,guy!
//hello,guy!

在 IDEA 中可以看到未被调用的方法名为灰色,这就可以知道示例代码在编译期间就已经确定了会调用的方法。在了解静态分派前,需要先熟悉一下静态类型和实际类型这两个概念。

静态类型和实际类型

Human man = new Man();

Human 称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),Man称为变量的实际类型(Actual Type)。

书中有这样一段话:

静态类型和实际类型在程序中都可以发生变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。

书中还举个例子:

//实际类型变化
Human man = new Man();
man = new Woman();
// 个人理解:man 的原本的实际类型是 Man,当第 3 行执行时,man 的实际类型就变成了 Woman
//静态类型变化
sr.sayHello((Man) man);
// 个人理解:接着上一步,man 的静态类型是 Human,此时显式转换为 Man,作为 sayHello(Man guy)方法的参数
sr.sayHello((Woman) man);
// 个人理解:前面将 man 的静态类型转换为 Man,但是第 8 行方法中的 man 静态类型还是从 Human 转换成 Woman
// 最终,man 的静态类型还是声明时的 Human

对于书中的那段话,理解起来还是有点绕,以下是我的个人理解:

  1. 首先静态类型和实际类型都是针对变量而言的,描述的是变量的属性,并且这两个属性会发生变化;
  2. 静态类型指的是声明该变量时的类型,而实际类型指的是给该变量赋值时赋值号右边的变量类型;
  3. 静态类型的变化仅仅在使用时发生,这里要注意两点:1)仅仅的意思是要么变量的静态类型不变,要么就是在使用该变量的时候发生了变化;2)最终该变量的静态类型是不会改变的,还是原来声明时的类型。

StaticDispatch类的 main 方法中,sayHello 方法的两次调用传入的参数静态类型是一致的,但是实际类型不通,结果调用的是同一个方法。从这一点可以看出,编译器是根据参数的静态类型来确定调用的方法的,静态类型在代码写完之后,就是已知的了,所以说重载在代码运行前就已经确定了。

截取 main 方法的字节码:

  public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
// 0xbb 创建一个对象,并将其引用值压入栈顶
0: new #7 // class jvmlearn/StaticDispatch$Man
// 0x5c 复制栈顶数值并将复制值压入栈顶
3: dup
// 0xb7 调用超类构造方法,实例初始化方法,私有方法
// 这个指令会用掉当前栈顶的值,所以前面复制了一份
4: invokespecial #8 // Method jvmlearn/StaticDispatch$Man."<init>":()V
// 0x4c 将栈顶引用型数值存入第二个本地变量
// 可以看下面的局部变量表
7: astore_1
8: new #9 // class jvmlearn/StaticDispatch$Woman
11: dup
12: invokespecial #10 // Method jvmlearn/StaticDispatch$Woman."<init>":()V
15: astore_2
16: new #11 // class jvmlearn/StaticDispatch
19: dup
20: invokespecial #12 // Method "<init>":()V
23: astore_3
// 0x2d 将第四个引用类型本地变量推送至栈顶
24: aload_3
25: aload_1
// 0xb6 调用实例方法
// 这里可以直接看到参数是 Human 类型,34 行的代码也一样
26: invokevirtual #13 // Method sayHello:(Ljvmlearn/StaticDispatch$Human;)V
29: aload_3
30: aload_2
31: invokevirtual #13 // Method sayHello:(Ljvmlearn/StaticDispatch$Human;)V
34: return
LineNumberTable:// 行号表
line 30: 0
line 31: 8
line 32: 16
line 33: 24
line 34: 29
line 35: 34
LocalVariableTable:// 局部变量表,存了 main 方法的参数和局部变量,静态方法第一个局部变量不是 this,也没有 this
Start Length Slot Name Signature
0 35 0 args [Ljava/lang/String;
8 27 1 man Ljvmlearn/StaticDispatch$Human;
16 19 2 woman Ljvmlearn/StaticDispatch$Human;
24 11 3 sr Ljvmlearn/StaticDispatch;

现在回到静态分派的定义,所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,静态分派的典型应用就是方法的重载。

特点

静态分派发生在编译阶段,是由编译器来确定使用哪个重载的方法,但这个重载的方法并不是唯一确定的。实际上编译器只是查找出当前重载的所有方法里面最合适的那一个。产生这种情况的原因,摘取书上的解释:

字面量不需要定义,所以字面量没有显式的的静态类型,它的静态类型只能通过语言上的规则去理解和推断。

下面是书上给出的关于重载的这个特点的示例代码:

public class Overload {

    public static void sayHello(Object arg) {
System.out.println("hello Object");
} public static void sayHello(int arg) {
System.out.println("hello int");
} public static void sayHello(long arg) {
System.out.println("hello long");
} public static void sayHello(Character arg) {
System.out.println("hello Character");
} public static void sayHello(char arg) {
System.out.println("hello char");
} public static void sayHello(char... arg) {
System.out.println("hello char ...");
} public static void sayHello(Serializable arg) {
System.out.println("hello Serializable");
} public static void main(String[] args) {
sayHello('a');
}
}

在 IDEA 中可以看到,调用的是 sayHello(char arg)方法。如果将该方法注释掉,编译器并不会报错,可以看到接下来调用的方法是 sayHello(int arg)。

可以进一步测试,不断的注释当前调用的方法,就能发现编译器查找重载方法的规则,即自底向上的进行自动类型转换,自底向上进行查找。

参考

  • 『深入理解 Java 虚拟机』:第二版,8.3.2 分派:1. 静态分派 P-247

Java 中多态的实现(上)的更多相关文章

  1. 深入Java核心 Java中多态的实现机制(1)

    在疯狂java中,多态是这样解释的: 多态:相同类型的变量,调用同一个方法时,呈现出多中不同的行为特征, 这就是多态. 加上下面的解释:(多态四小类:强制的,重载的,参数的和包含的) 同时, 还用人这 ...

  2. 个人对Java中多态的一些简单理解

    什么是多态 面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. 多态的定义:指允许不同类的对象对同一消息做出响应.即同一 ...

  3. Java中多态的一些简单理解

    什么是多态 .面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. .多态的定义:指允许不同类的对象对同一消息做出响应.即 ...

  4. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  5. Java 中多态的实现(下)

    Java 中多态的另一个语法实现是重写.重载是通过静态分派实现的,重写则是通过动态分派实现的. 在学习动态分派之前,需要对虚拟机的知识有一个初步的了解. 虚拟机运行时数据区 运行 Java 程序时,虚 ...

  6. 关于java中多态的理解

    java三大特性:封装,继承,多态. 多态是java的非常重要的一个特性: 那么问题来了:什么是多态呢? 定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行 ...

  7. 第78节:Java中的网络编程(上)

    第78节:Java中的网络编程(上) 前言 网络编程涉及ip,端口,协议,tcp和udp的了解,和对socket通信的网络细节. 网络编程 OSI开放系统互连 网络编程指IO加网络 TCP/IP模型: ...

  8. 对Java中多态,封装,继承的认识(重要)

                                                            一.Java面向对象编程有三大特性:封装,继承,多态 在了解多态之前我觉得应该先了解一下 ...

  9. Java中多态、抽象类和接口

    1:final关键字(掌握) (1)是最终的意思,可以修饰类,方法,变量. (2)特点: A:它修饰的类,不能被继承. B:它修饰的方法,不能被重写. C:它修饰的变量,是一个常量. (3)面试相关: ...

随机推荐

  1. asp.net core 3.x 授权中的概念

    前言 预计是通过三篇来将清楚asp.net core 3.x中的授权:1.基本概念介绍:2.asp.net core 3.x中授权的默认流程:3.扩展. 在完全没有概念的情况下无论是看官方文档还是源码 ...

  2. 加密算法极先锋之MD5算法

    在开发过程中,避免不了要涉及到数据加密,比如用户账号密码的加密,用户敏感数据的加密,涉及到的加密算法种类繁多,作为拿来主义的开发者时间精力有限,能够清楚其中主流的加密算法和用途,就已经足够了. 主要的 ...

  3. JS水仙花数

    题目:3位数==个位立方+十位的立方+百位的立方.这个3位数就是水仙花数.要求打印出所有的水仙花数 <body> <div id=d1> </div> <sc ...

  4. SQL Server 2019 表无法修改问题

    SQL Server 2019 表无法修改问题 问题描述: 解决方法: 1.在菜单栏中,点击工具->选项,示例: 2.在选项中单击设计器->表设计器和数据库设计器->取消勾选阻止保存 ...

  5. centos下mysql中table大小写改为不敏感

    项目由win系统部署到linux系统下,启动项目报错如下: Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: ...

  6. ThreadPool(线程池)介绍

    >>返回<C# 并发编程> 1. 线程池的由来 1.1. 线程池出现前 1.2. 线程池的诞生 1.3. CLR线程池工作过程 2. 线程池解决的问题 2.1. 异步调用方法 ...

  7. Kafka消费者没有收到通知的分析

    今天遇到两位三方人员跟我反馈,某微服务的异步接口功能不正常了,由于该异步接口采用Kafka异步消息的方案,对方说没有收到Kafka给消费者的通知,根据此问题,联系了相关人员进行了分析: (一)明确环境 ...

  8. luogu P3369 【模板】普通平衡树

    ————————————————版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https:// ...

  9. 硬件知识整理part3--电阻在单片机系统中的应用

    邦有道,如矢:邦无道,如矢.  --孔子 电阻在电路中主要功能是限流和分压等等.在单片机系统中自然也是. 电阻作为限流应该是最常用的应用之一,对于单片机外围设计来说,电阻的应用非常重要,在很多时候,我 ...

  10. Asp.net core 3.1+EF Core2.2.6+Oracle.EntityFrameworkCore2.1.19连接Oracle数据库

    Asp.net Core 3.1+EF Core2.2.6+Oracle.EntityFrameworkCore2.1.19连接Oracle数据库 1.前言 本次主要采用Asp.net core3.1 ...