方法的调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局的入口地址(入口地址相当于直接引用),需要在类加载期间甚至到运行期间才能确定目标方法的直接引用。
通过javap -verbose+类名 可以看到,Java虚拟机中提供了四条方法调用字节码指令,
1.invokestatic 调用静态方法
2.invokespecial 调用实例构造器(init)方法、私有方法和父类方法
3.invokevirtual 调用所有的虚方法
4.invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
先来看下面两种方法调用方式:
解析调用:方法在程序真正运行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。(一定是静态的)
分派调用:方法在程序运行期间才确定调用版本,分派调用分为静态分派和动态分派
再来理解一个概念:
非虚方法:在类加载的时候就会把符号引用解析为该方法的直接引用,就是说能在解析阶段确定唯一的方法调用版本的方法称为非虚方法。只要能被invokestatic和invokespecial指令调用的方法,都符合非虚条件,包括了静态方法、私有方法、实例构造器和父类方法四类,还有一个特殊的final方法,它虽然通过invokevirtual调用,但Java语言规范中明确说明了final方法是一种非虚方法。
虚方法:与之相反的,在运行期才能确定方法调用版本。
所以非虚方法都是通过解析调用(静态),而虚方法都是通过分派调用(静态或者动态)。
那么在分派和解析调用中的静态和动态的概念又怎么理解呢,请看下面这个例子:
Human person = new Man();
Man是Human的一个子类,那么Human就是实例person的静态类型,Man是person的实际类型。
1.下面这段代码演示了方法重载(overload)时是通过静态分派的:
public class StaticDispatch {
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHello(Human m)
{
System.out.println("hello, guy!");
}
public void sayHello(Man m)
{
System.out.println("hello, man!");
}
public void sayHello(Woman m)
{
System.out.println("hello, woman!");
}
public static void main(String[] args) throws Exception {
Human man = new Man();
Human woman = new Woman();
StaticDispatch t = new StaticDispatch();
t.sayHello(man);
t.sayHello(woman);
}
}
运行结果是:
hello, guy!
hello, guy!
2.下面这段代码演示了方法重写(overwrite)时是动态分派的:
public class DynamicDispatch {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello() {
System.out.println("hello, man!");
}
}
static class Woman extends Human{
@Override
protected void sayHello() {
System.out.println("hello, woman!");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
}
}
运行结果是:
hello, man!
hello, woman!
接下来看以下invokevirtual指令调用方法的过程就可以理解为什么如此了:
(1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
(2)如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找结束;不通过,返回IllegalAccessError
(3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程
(4)始终没有找到合适的方法,则抛出AbstractMethodError
其实方法的重载和重写区别就是重载依赖传入方法的形参来区别,而重写依赖方法调用者的类型来区别。
在第一步中C就是方法的调用者,这里查找的是实际类型,这就是overwrite的原理,
在第二步中查找方法是通过描述符和简单名称来进行的,而传入参数的描述符是通过静态类型定义的,所以代码一会出现这样的结果
总结一句话,重载看静态类型,重写看实际类型。
分享到:
相关推荐
4. **DataWindow对象**:如果你想要在DataWindow中使用存储过程,可以设置DataWindow的SQL源为存储过程,然后在适当的方法(如RowChange, Fetch等)中调用执行。 其次,关于**存储过程的创建**,在提供的描述中,...
在C#中,窗体之间的方法调用是一个常见的需求,特别是在复杂的桌面应用程序设计中。本文将根据提供的标题、描述、标签以及部分内容,详细介绍如何在C#的一个窗体中调用另一个窗体的方法,并探讨其中涉及的技术细节。...
第一种方法适用于简单的存储过程调用场景,而第二种方法则更适合于需要处理复杂数据结构或返回值的情况。无论哪种方式,都应确保正确处理异常,并且在完成后关闭所有打开的资源,以避免内存泄漏或其他问题。
在Java编程中,调用存储过程是连接数据库并执行预定义SQL代码的一种常见方式。存储过程是由数据库管理系统(如MySQL, Oracle, SQL Server等)编译并存储的一组操作,可以包含复杂的逻辑、条件判断、循环等,提高数据...
### C#调用Oracle方法(包括调用存储过程) 在.NET框架中,使用C#语言进行数据库操作是一项常见的任务。本文将详细介绍如何使用C#语言连接Oracle数据库,并演示如何调用Oracle存储过程,特别是带有输出参数的情况。...
能不能写个动态的业务,只输入存储过程名称,自动...只写一个通用方法,就可以调用所有的存储过程。只根据输入不同的存储过程名称、参数内容,自动调用不同的存储过程。 已经使用在多个项目中 全开源项目 请放心下载
### VB6.0调用存储过程的例子(方法一) 在Visual Basic 6.0中,调用数据库中的存储过程是一项非常实用的功能。本篇将详细解释一个具体的示例,通过VB6.0来调用一个名为`ADOTestRPE`的存储过程,并测试其返回值、...
`ExecuteScalar()` 方法返回的是存储过程的第一行第一列的值,非常适合获取存储过程的返回值。 三、返回结果集合的存储过程 对于返回结果集的存储过程,我们可以使用 Dapper 的查询功能将其转换为强类型列表。以下...
了解并熟练掌握JDBC调用存储过程的方法,能够帮助你更高效地实现Java程序与数据库之间的交互。 综上所述,Java程序员可以通过JDBC API方便地在Java应用程序中调用存储过程,提高代码的可读性和可维护性。通过理解并...
`createStoredProcedureQuery()`方法允许我们直接创建一个用于调用存储过程的对象,然后设置输入/输出参数。例如: ```java public QueryResponse queryBill1(String pay_ID) throws Exception { QueryResponse...
在实际开发中,C#与ORACLE数据库的集成是一个非常重要的 topic,本文将详细介绍如何在C#中调用ORACLE的PACKAGE里的方法和存储过程。 首先,我们需要在ORACLE数据库中建立一个PACKAGE,包括SPEC和BODY两部分。SPEC是...
`OUT`参数是存储过程中的一种特殊参数类型,用于将结果传出存储过程,供调用者使用。 Java中调用存储过程主要通过JDBC(Java Database Connectivity)接口实现。以下是一步步的操作步骤: 1. **加载数据库驱动**:...
RMI远程方法调用是Java平台上的一个关键特性,它允许Java对象在不同的JVM之间进行通信,从而实现分布式计算。RMI的核心理念是让开发者能够像调用本地方法一样调用远程对象的方法,简化了分布式系统的设计和实现。 *...
在Hibernate中,我们可以通过Session对象的createSQLQuery方法来调用存储过程。 1. **配置Hibernate**:在Hibernate的配置文件(如`hibernate.cfg.xml`)中,需要设置与数据库的连接信息,包括URL、用户名、密码等...
在调用过程中,可能会遇到SQL异常或其他运行时异常,需要使用try-catch-finally结构进行异常处理。捕获`SQLException`,根据错误码或异常信息进行相应的处理。 五、批处理调用 如果需要调用多个存储过程或函数,...
在定义方法时,一个方法内不能再定义另一个方法,即不能嵌套定义,但是在调用一个方法的过程中,还可以调用另一个方法,这是方法的嵌套调用。 方法的嵌套调用 假设main方法中调用a方法,a 方法中调用b方法,具体流程...
总结,SqlHelper调用存储过程是.NET开发中常见的操作,通过正确配置连接字符串,创建并设置SqlParameter,然后选择合适的Execute方法,可以方便地调用存储过程执行数据库操作。理解并熟练运用这一技巧,能够提高开发...
本篇文章将深入探讨MySQL中的分页存储过程及其代码调用方法。 首先,理解分页的基本概念。在网页或应用程序中,我们通常会看到“上一页”、“下一页”这样的导航,这就是分页的表现形式。分页查询通过设置每页显示...
使用`@PostConstruct`注解的方法只会被调用一次,确保了初始化过程只执行一次。下面是一个简单的例子: ```java @Component public class InitializationService { @PostConstruct public void init() { // ...