深入分析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多态的分析与理解,总结一下就是说,类调用和接口调用两种方式区分不同方法是不一样的,类调用是根据多态方法在方法表中的位移量,而接口调用是根据搜索整个方法表来实现的。


相关文章
|
1月前
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
69 1
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
31 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
17天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
34 2
|
18天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
23天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
27天前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
20 4
|
1月前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
35 4
|
1月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
41 1
|
1月前
|
安全 Java API
🌟探索Java宇宙:深入理解Java技术体系与JVM的奥秘
本文深入探讨了Java技术体系的全貌,从Java语言的概述到其优点,再到Java技术体系的构成,以及JVM的角色。旨在帮助Java开发者全面了解Java生态,提升对Java技术的认知,从而在编程实践中更好地发挥Java的优势。关键词:Java, JVM, 技术体系, 编程语言, 跨平台, 内存管理。
35 2
|
1月前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
18 1