原文来自:fair-jm.iteye.com 转截请注明出处
最近看了一些MethodHandle的使用 东西很杂 七拼八凑 有一些自己的理解可能有错误
因为是七拼八凑的 一些来源我已经记不清楚了...以下买标注哪里的主要来自于《深入理解java7》的迷你书 还有小部分是API或者网上其他的文章中的
MethodHandle 允许对一个方法产生引用,如同对象引用一样,这样避免了繁重的反射。
性能更好 创建MethodHandle时就实现方法检测 而不是调用invoke()时
一般Java类都在编译时检查类型,而invokedynamic的调用不是在编译阶段检查,而是在运行时检查。要做到这点,我们需要定义一个bootstrap,这样在不存在的方法和我们实际方法搭起桥梁。
当我们通过InvokeDynamic调用不存在的方法getDate()时, JVM会使用bootstrap方法来获得真正方法的MethodHandle,然后调用它。JVM缓存了MethodHandle 能提高性能,第一次动态调用将在bootstrap方法中搜索。当然,首先得注册告诉JVM我们的bootstrap方法, 这时得用Linkage.registerBootstrapMethod(String methodName) 方法. //注:在现在的JDK7中并未有InvokeDynamic这个类....Linkage似乎也被移除了...
invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(§2.9)、私有方法和父类方法。
invokestatic指令用于调用类方法(static方法)。
介绍这个主要是MethodHandlers.LookUp类中的一些方法和这些有点联系。
关于以上这些指令在代码中具体的出现请见下面的例子:
- interface SampleInterface {
- void sampleMethodInInterface();
- }
- class S implements SampleInterface {
- public void sampleMethodInInterface() {}
- public void normalMethod() {}
- public void fatherMethod() {}
- public static void staticSampleMethod() {}
- }
- class Sample extends S {
- public void sampleMethodInInterface() {}
- public void normalMethod() {}
- public static void staticSampleMethod() {}
- }
- class MethodInvokeTypes {
- public void invoke() {
- S sample = new Sample(); ---> 4: invokespecial #3 // Method Sample."<init>":()V
- sample.sampleMethodInInterface(); ---> 9: invokevirtual #4 // Method S.sampleMethodInInterface:()V
- Sample newSample = new Sample(); --->16: invokespecial #3 // Method Sample."<init>":()V
- newSample.normalMethod(); --->21: invokevirtual #5 // Method Sample.normalMethod:()V
- Sample.staticSampleMethod(); --->24: invokestatic #6 // Method Sample.staticSampleMethod:()V
- newSample.fatherMethod(); --->28: invokevirtual #7 // Method Sample.fatherMethod:()V
- }
- }
方法调用的流程:
1)名称:要调用的方法的名称一般是由开发人员在源代码中指定的符号名称。这个名称同样会出现在编译之后的字节代码中。
2)链接:链接包含了要调用方法的类。这一步有可能会涉及类的加载。
3)选择:选择要调用的方法。在类中根据方法名称和参数选择要调用的方法。
4)适配:调用者和接收者对调用的方式达成一致,即对方法的类型声明达成共识。
MethodHandle实例:
- String a="abcd";
- MethodType mt=MethodType.methodType(String.class,int.class,int.class);
- MethodHandle handle=MethodHandles.lookup().findVirtual(String.class,"substring",mt);
- System.out.println(handle.invoke(a,1,2)); //输出b
除了invoke方法可以调用外,要注意invokeExact()这个要求严格匹配的方法:
以上改成:handle.invokeExact(a,1,2) 是错误的 一定要写成(String)handle.invokeExact(a,1,2)
MethodType的一些使用:
- MethodType mt=MethodType.methodType(void.class,int.class,double.class);
- System.out.println(mt);
- System.out.println(mt.wrap());
- System.out.println(mt.unwrap());
- System.out.println(mt.generic());
- System.out.println(mt.toMethodDescriptorString());
- System.out.println(mt.erase());
- 输出:
- (int,double)void
- (Integer,Double)Void
- (int,double)void
- (Object,Object)Object
- (ID)V
句柄获取:
获得方法句柄通过java.lang.invoke.MethodHandles.Lookup类来完成
findConstructor就是查找构造器的
findVirtual就是查找一般函数的(同invokeVirtual)
findStatic 就是查找静态方法的(同invokeStatic)
以及findSpecial查找私有方法的
获取属性的话通过findGetter或者fingStaticGetter就可以了
findConstructor的演示(内部类):
- (这是一个错误的例子):
- public class TestMH {
- class Person{
- private String name;
- private int age;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
- public static void main(String[] args) throws Throwable {
- MethodHandles.Lookup lookup=MethodHandles.lookup();
- MethodHandle mh=lookup.findConstructor(Person.class, MethodType.methodType(void.class));
- Person p=(Person)mh.invokeExact();
- System.out.println(p);
- }
- }
这样的代码会提示:
Exception in thread "main" java.lang.NoSuchMethodException: no such constructor: com.cc.dynamic.TestMH$Person.<init>()void/newInvokeSpecial
原因很简单..内部类默认会把外部类的实例传入构造方法 那么自然就不会有参数为空的构造方法了 如下:
// Method descriptor #12 (Lcom/cc/dynamic/TestMH;)V
// Stack: 2, Locals: 2
TestMH$Person(com.cc.dynamic.TestMH arg0);
在内部类里的方法中都会加入这个参数 如果加个String的参数的构造函数进去:
public TestMH$Person(com.cc.dynamic.TestMH arg0, java.lang.String name);
改正也很简单:
- public static void main(String[] args) throws Throwable {
- MethodHandles.Lookup lookup=MethodHandles.lookup();
- MethodHandle mh=lookup.findConstructor(Person.class, MethodType.methodType(void.class,TestMH.class));
- Person p=(Person)mh.invokeExact(new TestMH());
- System.out.println(p);
- }
findSpecial演示:
- public class TestMH {
- private String privateInfo(){
- return "10";
- }
- public static void main(String[] args) throws Throwable {
- MethodHandle mh=MethodHandles.lookup().findSpecial(TestMH.class, "privateInfo", MethodType.methodType(String.class),TestMH.class);
- System.out.println(mh.invoke(new TestMH()));
- }
- }
如果调用其它类里的私有方法 则会出现错误
把参数写成:
- //Person中的私有方法
- MethodHandle mh=MethodHandles.lookup().findSpecial(Person.class, "privateInfo", MethodType.methodType(String.class),Person.class);
或者:
- //Person中的私有方法
- MethodHandle mh=MethodHandles.lookup().findSpecial(Person.class, "privateInfo", MethodType.methodType(String.class),TestMH.class);
都会出错
有哪位可以告知下为什么吗?
折衷的方式可以通过反射:
- Method m=Person.class.getDeclaredMethod("privateInfo");
- m.setAccessible(true); //破坏掉
- MethodHandle mh=MethodHandles.lookup().unreflect(m);
- System.out.println(mh.invoke(new Person()));
findGetter演示:
- MethodHandles.Lookup lookUp=MethodHandles.lookup();
- MethodHandle mh=lookUp.findGetter(Person.class, "name",String.class);
- System.out.println(mh.invoke(new Person()));
Person的name是public的 private会报错(没有访问权限) 这种方式获取的域是可以写成 t.x的形式域
通过其他方式生成MethodHandle:
- public static void main(String[] args) throws Throwable {
- MethodHandle mh=MethodHandles.constant(String.class, "hello");
- System.out.println((String)mh.invokeExact());
- } //这种方式生成一个返回指定类型的内容的方法句柄
- MethodHandle mh=MethodHandles.identity(String.class);
- System.out.println((String)mh.invokeExact("hello")); //这种方式生成一种输入什么返回什么的方法句柄
函数柯里化的实现:
- public static MethodHandle curry(int number,MethodHandle mh){
- return MethodHandles.insertArguments(mh, 0, number);
- }
- public static int add(int a,int b){
- return a+b;
- }
- public static void main(String[] args) throws Throwable {
- MethodType mt=MethodType.methodType(int.class,int.class,int.class);
- MethodHandle mh=MethodHandles.lookup().findStatic(TestMH.class,"add",mt);
- mh=curry(10,mh); //让int add(int,int) 变为int add(5,int)
- System.out.println(mh.invoke(10));
- }
MethodHandles中一些其他的方法:
dropArguments:用来忽略传递的参数的 第一个参数是MethodHandle对象 第二个参数是忽略参数的起始位置,接下去的参数是忽略参数的类型(和传入的要匹配)
(代码来自API中)
- public void testDrop() throws Throwable{
- MethodHandle cat = MethodHandles.lookup().findVirtual(String.class,
- "concat", MethodType.methodType(String.class, String.class));
- assertEquals("xy", (String) cat.invokeExact("x", "y"));
- MethodHandle d0 = dropArguments(cat, 0, String.class);
- assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
- MethodHandle d1 = dropArguments(cat, 1, String.class);
- assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
- MethodHandle d2 = dropArguments(cat, 2, String.class);
- assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
- MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
- assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
- }
filterArguments:过滤参数的 与其说是过滤其实是对参数进行一下预处理
- @Test
- public void testFilter() throws Throwable{
- MethodHandle cat = lookup().findVirtual(String.class,
- "concat", MethodType.methodType(String.class, String.class));
- MethodHandle upcase = lookup().findVirtual(String.class,
- "toUpperCase", MethodType.methodType(String.class));
- assertEquals("xy", (String) cat.invokeExact("x", "y"));
- MethodHandle f0 = filterArguments(cat, 0, upcase);
- assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
- MethodHandle f1 = filterArguments(cat, 1, upcase);
- assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
- MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
- assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY