一二中的概念如图展示:
package com.study.method;
public class TestClass {
private int x;
public int addResult() {
return x+1;
}
}
一、简单名称
没有类型和参数修饰的方法或字段名称
比如整形变量x和方法addResult。
二、全限定名
一个类的全限定名是将类全名的.全部替换为/
例如com/study/method/TestClass
三、描述符
如下图,这里贴上官方文档:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
以上来自简书https://www.jianshu.com/p/8b33361a5a3f
四、方法句柄和方法类型
Java7中为间接调用方法引入了新的api,即 方法句柄。
方法句柄中包含两个重要的类,MethodHandle和MethodType.
MethodType用来描述方法的返回值类型以及入参类型。
MehodHandle包含一个指向Method对象(方法在jvm内部的对等体)的指针。
而MehodHandle对象有2个重要方法invoke和invokeExact.
invokeExtract要求更加精确
MethodHandle获得的方法引用,并不能 突破访问权限本身的限制,比如private方法,就不能在类外被使用,这一点不如反射。
通过句柄调用方法示例(来源:https://blog.youkuaiyun.com/wulianzhazha/article/details/78815647):
public class TestUtil {
public static void main(String[] args) throws Throwable{
//声明定义方法的签名,参数为返回值类型、参数类型
MethodType methodType = MethodType.methodType(void.class, String.class);
//声明定义方法句柄,通过lookup对象得到方法句柄,参数为方法所在的类、方法的名称、所匹配的方法签名
MethodHandle methodHandle = methodHandle = MethodHandles.lookup().findVirtual(Hello.class, "sayHello", methodType);
//调用底层方法
methodHandle.invoke(Hello.class.newInstance(), "Test");
}
}
class Hello {
public void sayHello(String str) {
System.out.println("SayHello Method.."+str);
}
}
执行结果:
如果MehodHandle取得的是父类方法句柄的指针,然后调用时,第一个参数(也就是调用方)是子类的话,invokestactic会失败(也就是说它其实还要求mehodhandle所属的Class对象与 调用方实例是一个类型)。
而invoke不会做这个检查,也就是说即使句柄是父类的方法句柄,如果调用方是子类实例的话,它实际运行时会调用子类相应的覆盖的方法
五、动态调用方法
动态调用的方法:a.getClass().getMethod(str, new Class[]{}).invoke(a, new Object[]{})
a是类的对象
通过getClass()获取该类对象
getMethod(str, new Class[]{})得到的是a对象中名为str,并且不带参数的方法。如果要带参数,比如下面的String 和 int,那就是getMethod(str, new Class[]{String.class,int.class})
invoke(a,new Object[]{})调用方法,第一个参数是要调用这个方法的对象,如果方法是static的,这个参数可以为null
如果调用有参数的方法str(String s, int i),应该这样写invoke(a,new Object[]{“content”, 1})
来源(https://blog.youkuaiyun.com/jiangeeq/article/details/78899797)
public class TestUtil {
private static TestUtil movingInvokeTest = new TestUtil();
public void do_test(String str,int i) throws Exception {
if(i == 0)
movingInvokeTest.getClass().getMethod(str, new Class[]{}).invoke(movingInvokeTest, new Object[]{});
else if(i == 1)
movingInvokeTest.getClass().getMethod(str, new Class[]{String.class}).invoke(movingInvokeTest, new Object[]{"s"});
else if(i == 2)
movingInvokeTest.getClass().getMethod(str, new Class[]{String.class,int.class}).invoke(movingInvokeTest, new Object[]{"qw",1});
}
/*
* 下面是3个方法
*/
public void speak()
{
System.out.println("调用的没有参数的方法");
}
public void speak(String s)
{
System.out.println("调用有一个参数的方法,参数为:"+s);
}
public void speak(String s,int i)
{
System.out.println("调用有两个参数的方法,参数为,参数为:"+s+"和" +i);
}
public static void main(String[] args) throws Exception
{
movingInvokeTest.do_test("speak",1);
}
}
不同输入的测试结果,分别是i为0,1,2时的结果
六、符号引用
比如这一句
ps.println(“hello world”); //ps为PrintStream对象
Java语言在编译期间就已将println(String)方法完整的符号引用(本例中为一项CONSTANT_InterfaceMethodref_info常量)生成出来
如上,当作为方法调用的参数存储到class文件中时,就是这个格式。
在JDK7之前的字节码指令集中,方法调用指令的第一个参数都是被调用方法的符号引用
七、动态调用点
涉及 invokedynamic指令,篇幅较长,这里不多作解释。
每一处含有invokedynamic指令的位置都被称作“动态调用点(Dynamically-Computed Call Site)”,这条指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK 7时新加入的CONSTANT_InvokeDynamic_info常量,从这个新常量中可以得到3项信息:引导方法(Bootstrap Method,该方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)和名称。引导方法是有固定的参数,并且返回值规定是java.lang.invoke.CallSite对象,这个对象代表了真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并且执行引导方法,从而获得一个CallSite对象,最终调用到要执行的目标方法上。