深入分析java中的多态(从jvm角度分析)

简介: 对于java中多态概念的理解一直是面试常问的问题,所以今天花了一些时间好好地整理了一下,力求从java虚拟机的角度来分析和理解多态。

一、认识多态


1、方法调用


在Java中,方法调用有两类,动态方法调用与静态方法调用。


(1)静态方法调用是指对于类的静态方法的调用方式,是在编译时刻就已经确定好具体调用方法的情况,是静态绑定的。

(2)动态方法调用需要有方法调用所作用的对象,是在调用的时候才确定具体的调用方法,是动态绑定的。

我们这里所讲的多态就是后者—动态方法调用。


2、多态概念


多态有两种:类内部之间的多态和类之间的多态。我们先看一下标准的概念:

多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定


(1)Java的方法重载(类内部之间的多态):就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。我们举个例子来解释,就是一对夫妇生了多胞胎,多胞胎之间外观相似,其实是不同的孩子。


(2)Java的方法重写(父类与子类之间的多态):子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改。我们再举个例子,就是子承父业,但是儿子有自己想法,对父亲得产业进行再投资的过程。


二、代码实现多态


1、类内部之间得多态:方法重载


public class SingleClass {
    //孩子1:
    public String child(){
        System.out.println("child1");
        return "child1";
    }
    //孩子2:与孩子1参数个数不同
    public String child(String a){
        System.out.println("child2");
        return "child2";
    }   
    //孩子3:与孩子4参数顺序不同
    public String child(int a,String s){
        System.out.println("child3");
        return "child3";
    }   
    //孩子4:与孩子3参数顺序不同
    public String child(String s,int a){
        System.out.println("child4");
        return "child4";
    }   
    public static void main(String[] args){
        //重载方法调用:略
    }
}

从上述代码我们可以看到,在类的内部可以有相同的方法名,但是有唯一的参数列表。当然返回类型和修饰符也可以不同。下面我们再看一下类之间的多态。


2、类之间的多态:方法重写


类之间的多态其实是有两种方式:继承和接口。我们对这两种方式一个一个说明。


(1)继承方式实现多态


对于继承方式我们使用一个例子来解释,比如说父亲可以对自己的房子有处理权,儿子继承父业同样也有处理权。

第一步:定义父类

public class Father {
    public void dealHouse(){
        System.out.println("父亲处置房产"); 
    }    
}

第二步:定义子类(大儿子和小儿子)

//大儿子
public class SonA extends Father {
    @Override
    public void dealHouse() {
        System.out.println("大儿子处置房产"); 
    }
}
//小儿子
public class SonB extends Father {
    @Override
    public void dealHouse() {
        System.out.println("小儿子处置房产"); 
    }
}

第三步:测试

public class Test {
    public static void main(String[] args) {
        Father father=new Father(); 
        Father sonA=new SonA();
        Father sonB=new SonB();     
        father.dealHouse();
        sonA.dealHouse();
        sonB.dealHouse();
    }
}
//父亲处置房产
//大儿子处置房产
//小儿子处置房产

(2)接口方式实现多态


接口方式实现继承方式其实跟上面一样,只不过把父类变成了接口而已,其他内容只有微笑的变化,这里就不演示了,在这里只给出父接口的形式。

public interface Father {
    public void dealHouse();
}

到了这基本上就对多态形式的代码实现进行了演示,案例也比较简单,但是这对我们理解多态的思想还不够,我们最主要的还是从虚拟机的角度来分析一下。


三、分析多态


想要深入分析多态,我们需要弄清楚几个问题。


1、jvm内存


在上面的代码中我们其实已经看到了,不管是类内部之间实现的多态,还是类之间实现的多态,这些方法的名字其实都是一样的,那我们的程序在运行的时候,底层虚拟机是如何去区分的呢(java虚拟机实现动态调用)?为此我们还是先从java虚拟机讲起。


其实java虚拟机在执行java程序的时候,并不是直接运行的,他需要一个过程,我们使用一张图来看下:

v2-17735481abd0b28e0a4dbf897938feeb_1440w.jpg

上面这张图已经很清晰,也就是说,我们的java文件要想运行,需要通过java编译器编译成.class文件,然后通过类装载器讲.class文件装载到JVM中,最后才是执行。而且JVM分了五个区域,那么在代码中定义的那些多态方法存到了哪个地方呢?为此我们还需要对这块内存区域进行一个分析:

v2-e75b31d4144b58780a07831365c28355_1440w.jpg

我给出了一张java7的运行时数据区划分图,对于每一个区域的基本情况我相信你也能看明白。那么我们的多态方法到底存在了哪呢?没错就是后一个方法区。java堆存的是就是我们建立的一个个实例对象,而方法区存的就是类的类型信息。


而且这个方法区中的类型信息跟在堆中存放的class对象是不同的。在方法区中,这个class的类型信息只有唯一的实例(所以方法区是各个线程共享的内存区域),而在堆中可以有多个该class对象。也就是说方法区的类型信息就是像一个模板,那些class对象就好比通过这些模板创建的一个个实例。


2、通过例子来分析


现在我们拿上面的例子来说明一下多态在java虚拟机中是如何实现的。在测试类中有两行代码:

Father sonA=new SonA();
Father sonB=new SonB();

当程序运行到Father sonA=new SonA()这里就出现了多态,这是因为编译时看到Father,但是运行时new出来一个SonA类,两种类型还不一样。那么这些代码在运行的时候在内存中是如何保存的呢?


(1)Father sonA是一个引用类型,存在了java栈中的本地方法表中了。

(2)new SonA其实创建了一个实例对象,存储在了java堆中。

(3)SonA的类型数据存在了方法区中


我们在内存中看一下:v2-c8b976d1538dda9c17353a2185c7b3cc_1440w.jpg

reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。流程很简单,我们梳理一下:


第一步:虚拟机通过reference(Father的引用)查询java栈中的本地变量表,得到堆中的对象类型数据的指针,

第二步:通过到对象的指针找到方法区中的对象类型数据

第三步:查询方法表定位到实际类(SonA类)的方法运行。

好了,到第三步我们知道,其实是通过方法表来定位到实际运行的方法的。下面我们再来看看这个方法表是什么。


3、方法表


方法表肯定是存在于方法区中的,它是实现多态的关键所在,这里面保存的就是实例方法的引用,而且是直接引用。java虚拟机在执行程序的时候就是通过这个方法表来确定运行哪一个多态方法的。

我们通过上面的例子,来演示一下父子类在方法表中是如何保存的:

v2-a3900cc5648e8e0e6af05618b50448df_1440w.jpg

很明显每一个类都会有一个方法表,子类中不同的方法指向不同的类型信息。继承自Object的就指向Object,继承自Father的就指向Father(也就是包含了父类的方法dealHouse)。


可能我们到这就迷糊了,既然子类的dealHouse方法其实是父类Father的,那么为什么会执行子类的dealHouse方法呢?别着急往下看。这是java虚拟机区分多态方法(实现动态调用)的精华所在。


当Son类的方法表会有一个指向Father类dealHouse方法的指针,同时也有一个指向自己dealHouse方法的指针,这时候,新的数据会覆盖原有的数据,也就是说原来指向Father.dealHouse的那个引用会被替换成指向Son.dealHouse的引用(占据原来表中的位置)


注意:


上述讲述的其实是对继承实现的多态的一种分析,对接口实现的,会有着不一样的理解。Java虚拟机 对于接口方法的调用是采用搜索方法表的方式,如,要在Father接口的方法表中找到dealHouse()方法,必须搜索Father的整个方法表。从效率上来说,接口方法的调用总是慢于类方法的调用的。

以上就是对java多态的分析与理解,总结一下就是说,类调用和接口调用两种方式区分不同方法是不一样的,类调用是根据多态方法在方法表中的位移量,而接口调用是根据搜索整个方法表来实现的。


相关文章
|
12月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
363 4
|
10月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
402 2
|
10月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
370 1
|
11月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
742 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
12月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
1335 0
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
548 2