Java7中为间接调用方法引入了新的api,即 方法句柄。
方法句柄中包含两个重要的类,MethodHandle和MethodType.
MethodType用来描述方法的返回值类型以及入参类型。
MehodHandle包含一个指向Method对象(方法在jvm内部的对等体)的指针。
而MehodHandle对象有2个重要方法invoke和invokeExact.
MethodHandle获得的方法引用,并不能 突破访问权限本身的限制,比如private方法,就不能在类外被使用,这一点不如反射。
但是也可以暴露方法句柄给外部使用,比如下面这个例子:
就通过方法句柄,在类Study1中调用了类Test1的私有方法。
public class Test1 extends p{
public int a=0;
static Integer si=6;
String s="Hello world";
public Test1(int d){
}
@Override
public void say1() {
System.out.println("t");
}
private void do1(){
a++;
System.out.println(a);
}
}
public class Study1 {
public static void main(String[] args) throws Throwable {
MethodType methodType=MethodType.methodType(Void.TYPE);
Test1 test1=new Test1(4);
// MethodHandle methodHandle2= MethodHandles.lookup().findVirtual(
// Test1.class,"do1",methodType
// );
MethodHandle methodHandle2=Test1.getMethodHandle();
methodHandle2.invokeExact(test1);
}
public void do1(){
do2();
}
public void do2(){
System.out.println("1");
}
}
关于invoke和invokeExtract方法的区别:
invokeExtract要求更加精确,
如下 methodHandle2.invokeExact(test1,5.1,new Integer(1));可以执行,
methodHandle2.invokeExact(test1,5.1,1);会报错,因为要将1转换为integer,所以不合要求。这个方法要求不能有任何类型转换,也就是参数严格一致。
invoke相对要轻松很多。
还有一点,如果MehodHandle取得的是父类方法句柄的指针,然后调用时,第一个参数(也就是调用方)是子类的话,invokestactic会失败(也就是说它其实还要求mehodhandle所属的Class对象与 调用方实例是一个类型)。
而invoke不会做这个检查,也就是说即使句柄是父类的方法句柄,如果调用方是子类实例的话,它实际运行时会调用子类相应的覆盖的方法(推测可能是使用methodHandle在vtabl虚方法表中的索引来快速调用的)。
这个例子可以证明上面这个结论,虽然通过方法句柄拿到了方法的引用,但是如果
子类Test1中覆盖了say1方法,那么最终被调用的是子类的say1。但是很明显,方
法引用其实是指向的父类的方法。所以invoke是通过调用方的虚方法表来调用实际方法的。
而invokestaic则要求“methodHanle的调用方的Class”与“mehodHandle所引用
的method所属的Class”必须一致。
public static void main(String[] args) throws Throwable {
MethodType methodType=MethodType.methodType(Void.TYPE);
MethodHandle methodHandle2= MethodHandles.lookup().findVirtual(
p.class,"say1",methodType
);
Test1 test1=new Test1(4);
methodHandle2.invoke(test1);
}
private void do1(double b,Integer integer){
a++;
System.out.println("do1 id");
}
public static void main(String[] args) throws Throwable {
MethodType methodType=MethodType.methodType(Void.TYPE,double.class,Integer.class);
MethodHandle methodHandle2= MethodHandles.lookup().findVirtual(
Test1.class,"do1",methodType
);
Test1 test1=new Test1(4);
methodHandle2.invokeExact(test1,5.1,new Integer(1));
methodHandle2.invokeExact(test1,5.1,1);
}