Java如何对方法进行调用详解

一、方法调用

方法调用的唯一目的:确定要调用哪一个方法

方法调用分为解析调用和分派调用

二、非虚方法与虚方法

非虚方法: 静态方法,私有方法,父类中的方法,被final修饰的方法,实例构造器

与之对应不是非虚方法的就是虚方法了

它们都没有重写出其他版本的方法,非常适合在类加载阶段就进行解析(符号引用->直接引用)

三、调用指令

普通调用指令

  • invokestatic:调用静态方法
  • invokespecial:调用私有方法,父类中的方法,实例构造器方法,final方法
  • invokeinterface:调用接口方法
  • invokevirtual: 调用虚方法

使用invokestaticinvokespecial指令的一定是非虚方法

使用invokeinterface指令一定是虚方法(因为接口方法需要具体的实现类去实现)

使用invokevirtual指令的是虚方法

动态调用指令

invokedynamic: 动态解析出需要调用的方法再执行

jdk 7 出现invokedynamic,支持动态语言

测试虚方法代码

父类

public class Father {
    public static void staticMethod(){
        System.out.println("father static method");
    }

    public final void finalMethod(){
        System.out.println("father final method");
    }

    public Father() {
        System.out.println("father init method");
    }

    public void overrideMethod(){
        System.out.println("father override method");
    }
}

接口

public interface TestInterfaceMethod {
    void testInterfaceMethod();
}

子类

public class Son extends Father{

    public Son() {
        //invokespecial 调用父类init 非虚方法
        super();
        //invokestatic 调用父类静态方法 非虚方法
        staticMethod();
        //invokespecial 调用子类私有方法 特殊的非虚方法
        privateMethod();
        //invokevirtual 调用子类的重写方法 虚方法
        overrideMethod();
        //invokespecial 调用父类方法 非虚方法
        super.overrideMethod();
        //invokespecial 调用父类final方法 非虚方法
        super.finalMethod();
        //invokedynamic 动态生成接口的实现类 动态调用
        TestInterfaceMethod test = ()->{
            System.out.println("testInterfaceMethod");
        };
        //invokeinterface 调用接口方法 虚方法
        test.testInterfaceMethod();
    }

    @Override
    public void overrideMethod(){
        System.out.println("son override method");
    }

    private void privateMethod(){
        System.out.println("son private method");
    }

    public static void main(String[] args) {
        new Son();
    }
}

注意: 接口中的默认方法也是invokeinterface,接口中的静态方法是invokestatic

四、解析调用

在编译就确定了要调用哪个方法,运行时不可以改变

解析调用 调用的是 非虚方法

五、分派调用

分派又分为静态分派与动态分配

早期绑定:解析调用和静态分派这种编译期间可以确定调用哪个方法

晚期绑定: 动态分派这种编译期无法确定,要到运行时才能确定调用哪个方法

六、静态分派

//  静态类型      	 实际类型
	List list = new ArrayList();

静态分派: 根据静态类型决定方法执行的版本的分派

发生在编译期,特殊的解析调用

典型的表现就是方法的重载

public class StaticDispatch {
    public void test(List list){
        System.out.println("list");
    }

    public void test(ArrayList arrayList){
        System.out.println("arrayList");
    }

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        List list = new ArrayList();
        StaticDispatch staticDispatch = new StaticDispatch();
        staticDispatch.test(list);
        staticDispatch.test(arrayList);
    }
}
/*
list
arrayList
*/

方法的版本并不是唯一的,往往只能确定一个最适合的版本

七、动态分派

动态分派:动态期根据实际类型确定方法执行版本的分派

动态分派与重写有着紧密的联系

public class DynamicDispatch {
    public static void main(String[] args) {
        Father father = new Father();
        Father son = new Son();

        father.hello();
        son.hello();
    }
    static class Father{
        public void hello(){
            System.out.println("Father hello");
        }
    }

    static class Son extends Father{
        @Override
        public void hello() {
            System.out.println("Son hello");
        }
    }
}
/*
Father hello
Son hello
*/

虽然常量池中的符号引用相同,invokevirtual指令最终指向的方法却不一样

分析invokevirtual指令搞懂它是如何确定调用的方法

1.invokevirtual找到栈顶元素的实际类型

2.如果在这个实际类型中找到与常量池中描述符与简单名称相符的方法,并通过访问权限的验证就返回这个方法的引用(未通过权限验证返回IllegalAccessException非法访问异常)

3.如果在实际类型中未找到,就去实际类型的父类中寻找(没找到抛出AbstractMethodError异常)

因此,子类重写父类方法时,根据invokevirtual指令规则,先找实际类型,所以才存在重写的多态

频繁的动态分派会重新查找栈顶元素实际类型,会影响执行效率

为提高性能,JVM在该类方法区建立虚方法表使用索引表来代替查找

字段不存在多态

当子类出现与父类相同的字段,子类会覆盖父类的字段

public class DynamicDispatch {
    public static void main(String[] args) {
        Father son = new Son();
    }
    static class Father{
        int num = 1;

        public Father() {
            hello();
        }

        public void hello(){
            System.out.println("Father hello " + num);
        }
    }

    static class Son extends Father{
        int num = 2;

        public Son() {
            hello();
        }

        @Override
        public void hello() {
            System.out.println("Son hello "+ num);
        }
    }
}
/*
Son hello 0
Son hello 2
*/

先对父类进行初始化,所以会先执行父类中的构造方法,而构造方法去执行了hello()方法,此时的实际类型是Son于是会去执行Son的hello方法,此时子类还未初始化成员变量,只是有个默认值,所以输出Son hello 0

八、单分派与多分派

public class DynamicDispatch {
    public static void main(String[] args) {
        Father son = new Son();
        Father father = new Father();

        son.hello(new Nod());
        father.hello(new Wave());
    }
    static class Father{

        public void hello(Nod nod){
            System.out.println("Father nod hello " );
        }

        public void hello(Wave wave){
            System.out.println("Father wave hello " );
        }
    }

    static class Son extends Father{

        @Override
        public void hello(Nod nod) {
            System.out.println("Son nod hello");
        }

        @Override
        public void hello(Wave wave) {
            System.out.println("Son wave hello");
        }
    }

    //招手
    static class Wave{}
    //点头
    static class Nod{}
}
/*
Son nod hello
Father wave hello
*/

宗量: 方法参数与方法调用者

分派还可以分为单,多分派

单分派:根据一个宗量选择方法

多分派:根据多个宗量选择方法

在编译时,不仅要关心静态类型是Father还是Son,还要关心参数是Nod还是Wave,所以静态分派是多分派(根据两个宗量对方法进行选择)

在执行son.hello(new Nod())时只需要关心实际类型是Son还是Father,所以动态分派是单分派(根据一个宗量对方法进行选择)

到此这篇关于Java如何对方法进行调用详解的文章就介绍到这了,更多相关Java调用方法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 总结Java调用Python程序方法

    如何使用Java调用Python程序 本文为大家介绍如何java调用python方法,供大家参考. 实际工程项目中可能会用到Java和python两种语言结合进行,这样就会涉及到一个问题,就是怎么用Java程序来调用已经写好的python脚本呢,一共有三种方法可以实现,具体方法分别为大家介绍: 1. 在java类中直接执行python语句 此方法需要引用org.python包,需要下载Jpython.在这里先介绍一下Jpython.下面引入百科的解释: Jython是一种完整的语言,而不是一个J

  • Java魔法堂之调用外部程序的方法

    前言 Java虽然五脏俱全但总有软肋,譬如获取CPU等硬件信息,当然我们可以通过JNI调用C/C++来获取,但对于对C/C++和Windows API不熟的码农是一系列复杂的学习和踩坑过程.那能不能通过简单一些.学习成本低一些的方式呢?答案是肯定的,在功能实现放在首位的情况下,借他山之石是最简洁有力的做法. 认识java.lang.Runtime#exec方法 作用:用于调用外部程序,并重定向外部程序的标准输入.标准输出和标准错误到缓冲池.功能就是和windows的"运行"一样. 重载

  • Kettle中使用JavaScrip调用jar包对文件内容进行MD5加密的操作方法

    Kettle中使用JavaScript调用jar包对文件内容进行MD5加密.本文主要知识点: JavaScript调用jar包对文件内容进行MD5加密,返回加密md5值 Kettle实现对文件内容的加密,返回加密md5值 2.使用方法 1)下载jar包 http://xiazai.jb51.net/202009/yuanma/filemd5hash_jb51.rar 2)将jar包导入Kettle的lib目录下 3)重启Kettle Spoon.bat,使得kettle加载到新加入的jar包,下

  • Java为何需要平衡方法调用与内联

    在 Java 中,方法调用一般通过 Virtual Call 还有 Classic Call. Classic Call 就是直接指向方法的地址,需要一次寻址到方法的地址,比直接执行代码慢. Virtual Call 需要通过 VMT(Virtual Method Table).这个VMT存储的是该class对象中所有的Virtual Method,程序运行的时候首先加载实例对象,然后通过实例对象找到VMT,通过VMT再找到对应的方法地址,再执行代码.所以比 Classic Call 更慢. J

  • Java 使用多线程调用类的静态方法的示例

    1.情景展示 静态方法内部实现:将指定内容生成图片格式的二维码: 如何通过多线程实现? 2.分析 之所以采用多线程,是为了节省时间 3.解决方案 准备工作 logo文件 将生成的文件保存在F盘test目录下 代码实现 /**  * 多线程测试类  * @explain 生成图片格式的二维码采用多线程实现  * @author Marydon  * @creationTime 2018年11月23日下午3:15:39  * @version 1.0  * @since  * @email mary

  • java中JSONObject转换为HashMap(方法+main方法调用实例)

    1.首先要导入json相关的jar包 引入的jar包: (版本自行定义,可以选用使用人数偏多的版本,这样比较稳定) commons-beanutils-1.9.2.jar commons-collections-3.2.1.jar commons-lang-2.6.jar commons-logging-1.2.jar ezmorph-1.0.6.jar json-lib-2.4-jdk15.jar jar包的下载可以去下面这个网址搜索: https://mvnrepository.com/ 2

  • java 父类子类有同名方法时如何调用的实现

    父类引用默认调用子类重写的方法 如果父类的引用p指向了子类的实例,则通过这个引用p访问同名方法时,调用的是子类重写的方法. 父类引用p可以指向不同的子类,调用不同子类重写的不同的同名方法,进而达到类间多态的效果. 但是,父类的引用只能访问子类重写的方法,父类中没有的方法,父类的引用不能访问.也就是说,通过父类的引用来调用子类的方法时,不能访问到子类后来自己定义的方法. 子类引用默认调用自定义的方法 通过子类的引用访问同名方法时,默认是重写之后的同名方法. 子类通过super关键字来调用被隐藏的父

  • Java利用httpclient通过get、post方式调用https接口的方法

    通过httpclient的get post方式调用http很常见.一般都是 HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(http://127.0.0.1/login); 但是如果要调用https这个方式就不行了.就要修改DefaultHttpClient <dependency> <groupId>org.apache.httpcomponents</groupId>

  • Java调用elasticsearch本地代码的操作方法

    虽然Java虚拟机为开发人员屏蔽了底层的实现细节,使得开发人员不用考虑底层操作系统的差异性.不过在某些应用程序中,还是免不了要直接与底层操作系统上的原生代码进行交互.今天我们就来看一下Java对本地调用提供的支持. 一.为什么要进行本地调用 1.基于性能的考虑 Java语言从其运行速度上来说,在大多数方面是慢于底层操作系统上原生的C和C++等语言的.这主要是由于Java虚拟机这个中间层次的存在.如果完全用Java语言实现的性能无法达到程序的预期要求,可以选择把部分重要且耗时的代码用C或C++来实

  • 在java中main函数如何调用外部非static方法

    使用外部方法时(不管是static还是非static),都要先new一个对象,才能使用该对象的方法. 举例如下: 测试函数(这是错误的): public class Test { public static void main(String[] args) { Employee employee = null; employee.setName("旺旺"); //有警告,况且这里也执行不下去 employee.setEmail("ww@qq.com"); emplo

随机推荐