我们知道java作为静态类型语言(编译期确定类型)直到java 8 之后才使用lambda表达式 对动态特性进行了代码层面的使用。虚拟机关于方法调用的指令有 invokestatic invokespecial invokevirtual invokeinterface,这些指令的第一个参数是在编译期间产生的符号引用,而动态语言是在运行期才能确定类型。那么java在虚拟机层面支持动态类型就成了java平台的发展趋势。java7 的java.lang.invoke 包来支持动态特性。之前我们的方法调用主要通过符号引用来确定调用的目标方法,而这个包为我们提供了一种新的动态确定目标方法的机制称为“method Handle”。类似于c++中的函数指针。在c++中我们可以把一个函数当做参数进行传递,而在java中则不行。
现在有了MethodHandle 就可以用类似的方式操作,
比如sort(int[] list,MethodHandle mh) 参数mh就可以是比较函数,代替了java中常用的java Arrays.sort(arr,Comparator comparator);那么如何操作呢?如下代码演示了如何在代码层面利用invoke包进行根据实际类型进行其方法调用。
public class Hello {
static class ClassA{
public void println(String s){
System.out.println(s);
}
}
public static void main(String[] args) throws Throwable {
while(true){
long time = System.currentTimeMillis();
if (time % 2 == 0) {
getMethodHandle(new ClassA()).invokeExact("gaojl hello!");
} else {
getMethodHandle(System.out).invokeExact("niub gaojl!");
}
}
}
public static MethodHandle getMethodHandle(Object rec) throws NoSuchMethodException, IllegalAccessException {
MethodType mt = MethodType.methodType(void.class,String.class);
MethodHandles.Lookup l = MethodHandles.lookup();
return l.findVirtual(rec.getClass(),"println",mt).bindTo(rec);
}
}
其实invokedynamic 指令与MethodHandle 机制的作用是一样的,都是为了解决原有的4条指令invoke*把分派逻辑固化在虚拟机层面,把如何查找目标方法的决定权从虚拟机转化到用户代码层面,让用户有更高的自由度。
二者思路目的是一致的:为了找到目的方法 invoke*4条指令利用符号引用 直接引用类来完成,而invokedynamic 是利用上层api来完成。
之前我们的invoke*指令的第一个参数 为常量池的方法符号引用,然后找到该线程对应的虚拟机栈中的操作数栈的栈顶元素,其实际类型记为C,之后按自己->父亲->…进行查找
invokedynamic 第一个参数为常量池中的CONSTANT_invokedynamic_info常量,从这个新常量中可以得到三种类型的信息:Bootstrap Method 、methodtype、方法名称。 值得注意的是bootstrap method方法返回的是CallSite 对象,这个代表真正要执行的方法调用。