1. JDK1.7(JSR-292)对动态类型的直接支持
invokevirtual invokespecial invokestatic invokeinterface的第一个参数都是被调用方法的符号引用(CONSTANT_Methodref_info或者CONSTANT_InterfaceMethodref_info),符号引用在编译期确定了接收者的类型,而动态语言只有在运行期才能确定接收者类型。
对此JDK1.7(JSR-292)提供了invokedynamic指令以及java.lang.invoke包。
2.java.lang.invoke包
主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式之外,提供了一种新的动态确定目标方法的机制,称为MethodHandle。
MethodHandler类似于C/C++的函数指针。
之前的Java没有办法单独地把一个函数作为参数进行传递。普遍的做法是设计一个带有方法的接口,以实现了这个接口的对象作为参数。
在拥有了MethodHandle之后,Java拥有了类似于函数指针的工具。
public class MethodHandleTest {
static class ClassA {
public void println(String s) {
System.out.println(s);
}
}
public static void main(String[] args) throws Throwable {
Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
getPrintlnMH(obj).invokeExact("hello world");
}
private static MethodHandle getPrintlnMH(Object receiver) throws Throwable {
MethodType mt = MethodType.methodType(void.class, String.class);
return MethodHandles.lookup().findVirtual(receiver.getClass(), "println", mt).bindTo(receiver);
}
}
MethodType代表方法类型:方法返回值和参数
MethodHandles.lookup():在指定类中查找符合给定方法名称、方法类型并且符合调用权限的方法句柄。
虚方法的第一个参数是隐式的,代表该方法的接收者,也即this指向的对象,该参数之前是放在参数列表中进行传播,现在提供了bindTo()方法。
getPrintlnMH()模拟了invokevirtual指令执行过程,只不过其分派逻辑并非固化在Class文件的字节码上,而是通过一个具体方法来实现。
该方法的返回值MethodHandle对象可以视为对最终调用的一个“引用”。
3.反射机制与MethodHandle机制的区别
- 都是在模拟方法调用,反射是在模拟Java代码层次的方法调用,而MethodHandle模拟字节码层次的方法调用。MethodHandles.lookup中的三个方法——findStatic findVirtual findSpecial正是为了对应于invokestatic invokevirtual&invokeinterface invokespecial这几条字节码指令的执行权限校验行为,而这些底层细节在调用反射API时不需要关心(MethodHandle权限检查在句柄创建阶段完成,在实际调用过程中,JVM并不会检查MethodHandle权限。如果多次被调用,相较于反射调用,会省下重复权限检查的开销)
- java.lang.reflect.Method(重量级)远比java.lang.invoke.MethodHandle(轻量级)所包含的信息多。前者是方法在Java侧的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性,还包含执行权限等运行期信息。 而后者仅仅包含与执行该方法相关的信息。
- MethodHandle可以采用类似于字节码优化技术的思路,反射不行。
- 反射只是为了Java语言服务,MethodHandle是服务于所有JVM上的语言。
4.invokedynamic指令
在某种程度上,invokedynamic与MethodHandle机制的作用是一样的,都是为了解决4条invoke指令方法分派规则固化在虚拟机中的问题,把如何查找目标方法的决定权从虚拟机转嫁到具体用户代码中,让用户有更高的自由度。
MethodHandle采用上层Java代码和API实现,invokedynamic用字节码和Class中其他属性和常量来完成。
invokedynamic指令位置称为“动态调用点”(Dynamic Call Site),指令的第一个参数不再是方法的符号引用,而是CONSTANT_InvokeDynamic_info,包含三项信息:引导方法(放在BootstrapMethods属性中)、方法类型和名称。
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index; //指向引导方法
u2 name_and_type_index; //方法名和方法描述符
}
引导方法返回值是java.lang.invoke.CallSite对象,代表真正要执行的目标方法调用。
interface Strategy {
String approach(String msg);
}
interface StrategyDev {
String approach(String msg);
}
class Soft implements Strategy {
@Override
public String approach(String msg) {
return msg.toLowerCase() + "?";
}
}
class Unrelated {
static String twice(String msg) {
return msg + " " + msg;
}
String third(String msg) {
return msg + " " + msg + " " + msg;
}
}
public class Strategize {
Strategy strategy;
String msg;
Strategize(String msg) {
strategy = new Soft(); // [1]
this.msg = msg;
}
void communicate() {
System.out.println(strategy.approach(msg));
}
void changeStrategy(Strategy strategy) {
this.strategy = strategy;
}
public static void main(String[] args) {
Strategy strategy1 = Unrelated::twice;//static
System.out.println(strategy1.approach("Hello there"));
}
}
字节码:
Constant pool:
#9 = InvokeDynamic #0:#49 // #0:approach:()Lcom/enjoy/learn/core/lambdastream/test/test4/Strategy;
#49 = NameAndType #57:#63 // approach:()Lcom/enjoy/learn/core/lambdastream/test/test4/Strategy;
#57 = Utf8 approach
#63 = Utf8 ()Lcom/enjoy/learn/core/lambdastream/test/test4/Strategy;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: invokedynamic #9, 0 // InvokeDynamic #0:approach:()Lcom/enjoy/learn/core/lambdastream/test/test4/Strategy;
5: astore_1
6: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: ldc #10 // String Hello there
12: invokeinterface #7, 2 // InterfaceMethod com/enjoy/learn/core/lambdastream/test/test4/Strategy.approach:(Ljava/lang/String;)Ljava/lang/String;
17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
LineNumberTable:
line 41: 0
line 45: 6
line 47: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 args [Ljava/lang/String;
6 15 1 strategy1 Lcom/enjoy/learn/core/lambdastream/test/test4/Strategy;
}
SourceFile: "Strategize.java"
InnerClasses:
public static final #71= #70 of #76; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #46 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#47 (Ljava/lang/String;)Ljava/lang/String;
#48 REF_invokeStatic com/enjoy/learn/core/lambdastream/test/test4/Unrelated.twice:(Ljava/lang/String;)Ljava/lang/String;
#47 (Ljava/lang/String;)Ljava/lang/String;
5.掌控方法分配规则
在Java程序中,可以通过“super”关键字很方便地调用到父类中的方法,但如果要访问祖类的方法呢?
1)使用MethodHandle实现
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
class GrandFather {
void thinking() {
System.out.println("I am grandfather");
}
}
class Father extends GrandFather {
@Override
void thinking() {
System.out.println("I am father");
}
}
public class Son extends Father{
@Override
void thinking() {
try {
MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(GrandFather.class,
"thinking", mt).bindTo(new GrandFather());
mh.invoke();
} catch (Throwable e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Son().thinking();
}
}
2)方法引用(invokedynamic)实现
import java.util.function.Function;
class GrandFather {
void thinking() {
System.out.println("I am grandfather");
}
}
class Father extends GrandFather {
@Override
void thinking() {
System.out.println("I am father");
}
}
public class Son extends Father{
@Override
void thinking() {
Runnable function = new GrandFather()::thinking;
function.run();
}
public static void main(String[] args) {
new Son().thinking();
}
}
字节码:
void thinking();
descriptor: ()V
flags: (0x0000)
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/enjoy/learn/core/oop/method/GrandFather
3: dup
4: invokespecial #3 // Method com/enjoy/learn/core/oop/method/GrandFather."<init>":()V
7: dup
8: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class;
11: pop
12: invokedynamic #5, 0 // InvokeDynamic #0:run:(Lcom/enjoy/learn/core/oop/method/GrandFather;)Ljava/lang/Runnable;
17: astore_1
18: aload_1
19: invokeinterface #6, 1 // InterfaceMethod java/lang/Runnable.run:()V
24: return
LineNumberTable:
line 32: 0
line 33: 18
line 44: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lcom/enjoy/learn/core/oop/method/Son;
18 7 1 function Ljava/lang/Runnable;