论坛首页 Java企业应用论坛

通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比

浏览 20445 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-09-26   最后修改:2009-12-21
JDK 7将会实现JSR 292,为在JVM上实现动态语言提供更多支持。其中,MethodHandle是JSR 292的重要组成部分之一。有了它,意味着Java终于有了引用方法的方式,或者用C的术语说,“函数指针”。(我差点要说“引用‘方法’的‘方法’”了,好pun)。
下面的讨论都是基于当前(2009-09)的设计而进行的,今后相关具体设计可能变化,但大的方向应该比较明确了。JDK 7的代码例子都是在JDK 7 Binary Snapshot build 70下测试的。执行程序时要添加-XX:+EnableMethodHandles参数。

与其说JDK 7的MethodHandle像C的函数指针,还不如说像.NET的委托。
C#与.NET从1.0版开始就有“委托”的概念,通过委托可以在代码中引用任意方法,无论方法的可访问性、所属类型如何,无论是静态还是实例方法。之前一帖也提到了,.NET的委托提供了为方法创建“别名”的能力,使我们可以用统一的方式去调用签名相同但名字和所属类型都不一定相同的方法。与C的函数指针所不同的是,.NET的委托不但引用了方法,还会引用执行该方法所需要的环境,对引用实例方法的委托来说“环境”就是方法所在的实例;而C的函数指针则仅指向函数的代码而已,没有引用环境的功能。而且,.NET的委托包含了足够的元数据,可用于运行时做类型检查;而C的函数指针则仅仅是个裸指针。调用委托的速度接近调用虚方法的速度。
JDK 7将引入的MethodHandle从许多方面说都与.NET的委托非常相似。MethodHandle也可以指向任意方法,提供为方法创建“别名”的能力;可以用统一的方式去调用MethodHandle。此外,MethodHandle还支持组合,可以以适配器的方式将多个MethodHandle串在一起,实现参数过滤、参数转换、返回值转换等许多功能。调用MethodHandle的速度接近调用接口方法的速度。
MethodHandle对许多JVM的内部实现来说并不是一个全新的概念。要实现JVM,在内部总会保留一些指向方法的指针。JDK 7只是把它(和其它许多JVM里原本就支持的概念)具体化为Java类型暴露给Java代码用而已;这就是所谓的“reification”。

好吧,简单介绍了些背景,下面就通过代码来认识和感受一下JDK 7的MethodHandle,并与.NET的委托对比。

第一组例子,照例上hello world:

JDK 7:
import java.dyn.*;
import static java.dyn.MethodHandles.*;

public class TestMethodHandle1 {
    private static void hello() {
        System.out.println("Hello world!");
    }
    
    public static void main(String[] args) {
        MethodType type = MethodType.make(void.class);
        MethodHandle method = lookup()
            .findStatic(TestMethodHandle1.class, "hello", type);
        method.<void>invoke();
    }
}

执行这个测试需要使用如下命令:
java -XX:+EnableMethodHandles TestMethodHandle1

(注意“java”要使用JDK 7的,不要用了JDK 6或更早的)

首先,要使用MethodHandle,需要引入的类型都在java.dyn包里。这个例子用到的是MethodHandles、MethodHandles.Lookup、MethodType、MethodHandle几个。
流程是:
0、调用MethodHandles.lookup()方法,遍历调用栈检查访问权限,然后得到一个MethodHandles.Lookup实例;该对象用于确认创建MethodHandle的实例的类对目标方法的访问权限是否满足要求,并提供搜索目标方法的逻辑;
1、指定目标方法的“方法类型”,得到一个MethodType实例;
2、通过MethodHandles.lookup()静态方法得到一个类型为MethodHandles.Lookup的工厂,然后靠它搜索指定的类型、指定的名字、指定的方法类型的方法,得到一个MethodHandle实例;
3、调用MethodHandle上的invoke方法。

其中,第1步中调用的MethodType.make()方法接收的参数是一组类型,第一个参数是返回类型,后面依次是各个参数的类型。上例中MethodType.make(void.class)得到的就是一个返回类型为void,参数列表为空的方法类型。如果熟悉Java字节码的话,这个方法类型的描述符就是()V。关于方法描述符的格式,可以参考JVM规范第二版4.3.3小节。MethodType的实例只代表所有返回值与参数类型匹配的一类方法的方法类型,自身没有名字;在检查某个方法是否与某个MethodType匹配时只考虑结构,可以算是一种特殊的structural-typing。

第2步看起来跟普通的反射很像,但通过反射得到的代表方法的对象是java.lang.reflect.Method的实例,它含有许多跟“执行”没有直接关系的信息,比较笨重;通过Method对象调用方法只是正常方法调用的模拟,所有参数会被包装为一个数组,开销较大。而MethodHandle则是个非常轻量的对象,主要目的就是用来引用方法并调用;通过它去调用方法不会导致参数被包装,原始类型的参数也不会被自动装箱。
MethodHandles.Lookup上有三个find方法,包括findStatic、findVirtual、findSpecial,分别对应invokestatic、invokevirtual/invokeinterface、invokespecial会对应的调用逻辑。注意到findVirtual方法所返回的MethodHandle的方法类型会包含一个显式的“this”参数作为第一个参数;调用这样的MethodHandle要显式传入“receiver”。这个看起来就跟.NET的开放委托相似,可以参考我之前的一帖。由于JDK 7的MethodHandle支持currying,可以把receiver保存在MethodHandle里,所以也可以创建出类似.NET的闭合委托的MethodHandle实例。
MethodHandles.Lookup上还有一组方法可以从通过反射API得到的Constructor、Field或Method对象创建出对应的MethodHandle。

第3步调用的MethodHandle.invoke()看似是一个虚方法,实际上并不是MethodHandle上真的存在的方法,而只是标记用的虚构出来的方法。上例中第13行对应的Java字节码是:
invokevirtual java/dyn/MethodHandle.invoke:()V

也就是假装MethodHandle上有一个描述符为()V且名为invoke的虚方法,通过invokevirtual指令去调用它。
Java编译器为它做特殊处理:返回值类型如同泛型参数在<>内指定,不写的话默认为返回Object类型;参数列表的类型则由Java编译器根据实际参数的表达式推断出来。与正常的泛型方法不同,MethodHandle.invoke指定返回值类型可以使用void和所有原始类型,不必像使用泛型方法时需要把原始类型写为对应的包装类型。
MethodHandle的方法类型不是Java语言的静态类型系统的一部分。虽然它的实例在运行时带有方法类型信息(MethodType),但在编译时Java编译器却不知道这一点。所以在编译时,调用invoke时传入任意个数、任意类型的参数都可以通过编译;但在运行时要成功调用,由Java编译器推断出来的返回值类型与参数列表必须与运行时MethodHandle实际的方法类型一致,否则会抛出WrongMethodTypeExceptionJohn Rose把MethodHandle.invoke的多态性称为“签名多态性(signature polymorphism)”。

用户可以自行继承java.dyn.JavaMethodHandle来创建自定义的MethodHandle子类,可以添加域或方法等,并可以指定该类型看作MethodHandle时的“入口点”——实际指向的方法。

许多JVM实现在JIT编译的时候会做激进的优化,包括常量传播、内联、逃逸分析、无用代码削除等许多。JDK 7的MethodHandle的一个好处是它就像它所指向的目标方法的替身一样,JVM原本可以做的优化对MethodHandle也一样支持,特别是有需要的时候可以把目标方法内联到调用处。相比之下,通过反射去调用方法则无法被JVM有效的优化。

对比C#的例子:
C# 2.0:
using System;

static class TestDelegate1 {
    static void Hello() {
        Console.WriteLine("Hello world!");
    }
    
    static void Main(string[] args) {       
        Action method = Hello; // Action method = new Action(Hello);
        method();              // method.Invoke();
    }
}

这段代码与前面Java版的TestMethodHandle1功能基本相同。来观察一下两者的异同点。

要在C#里使用委托的流程是:
0、事先声明好合适的委托类型;
1、适用合适的委托类型,指定目标方法创建出委托的实例;
2、调用委托上的Invoke()方法。

.NET允许用户自定义委托类型,在C#里的语法是:
modifiers delegate return_type DelegateTypeName(argument_list);

该语法与方法的声明语法非常像,只是在返回类型之前多了个delegate关键字而已,比C中typedef函数指针类型的语法容易多了。上例用到的System.Action类型就是标准库里声明好的一个委托类型,其声明形如:
namespace System {
    public delegate void Action();
}

这样声明出来的委托类型相当于声明了一个Action类,继承System.MulticastDelegate,并且拥有一个返回值类型为void,参数列表为空的Invoke()方法。在C#里,用户无法像声明普通类型一样通过声明一个继承System.Delegate或System.MulticastDelegate的类来得到一个新的委托类型,而只能用上述语法来声明。不过从CLR的角度看,并没有限制用户不能自行继承上述两种类型来声明新的委托类型。
委托是.NET类型系统的一部分。两个委托类型即便表示的签名一致也会被认为是不同的类型,不能相互赋值/转换。这体现出了C#与.NET类型的nominal-typing性质。创建委托实例时则只考虑目标方法与委托类型在签名上是否吻合,而不考虑名字问题,这点又与JDK 7的MethodHandle相似。
委托上的Invoke方法的签名与委托声明的相吻合。在编译时,调用委托的Invoke()方法与调用一般的虚方法一样会被类型检查。
目前在C#里可以显式调用委托上的Invoke()方法,也可以直接把委托当成方法用括号调用。是显式调用Invoke()还是直接用括号调用委托,现在来说只是程序员的偏好问题而已。事实上在C# 2.0以前编译器会阻止程序员显式调用Invoke()方法。

用C#代码例子再稍微解释一下:
// 下面声明两个委托类型,它们的签名是一样的
// (int, int) -> int
delegate int BinaryIntOp1(int x, int y);
delegate int BinaryIntOp2(int i, int j);
// 形式参数的类型是重要的,名字不重要
// 上面两个委托上的Invoke()方法都形如:
// [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
// public virtual int Invoke(int i, int j);
// 注意到Invoke()是一个虚方法,其返回值与参数类型都是确定的,
// 而其实现是由运行时直接提供的。

class Demo {
    // 定义一个签名为(int, int) -> int的方法
    public static int Add(int a, int b) {
        return a + b;
    }
    
    static void Main(string[] args) {
        // 可以,Add方法与BinaryIntOp1要求的签名匹配
        BinaryIntOp1 op1 = new BinaryIntOp1(Add);

        // 可以,Add方法与BinaryIntOp1要求的签名匹配
        // C# 2.0的隐式创建委托的新特性:等同于写成new BinaryIntOp2(Add);
        BinaryIntOp2 op2 = Add;

        // 不行,两者属于不同的委托类型,无法相互赋值/转换
        //BinaryIntOp2 op3 = op1;
        
        // 调用Invoke()方法会被编译器检查类型是否匹配
        int sum = op1.Invoke(4, 2);
        // 下面这样就会在编译时出错:
        //int sum2 = op2.Invoke(new object(), 0);
    }
}


Java版例子中,创建MethodHandle对象需要在代码里通过MethodHandles.Lookup工厂来查找目标;在C#里编译器已经帮忙找出了目标方法的token,写起来方便许多;C#编译器会根据方法名与委托的签名去寻找合适重载版本的方法,找出它的token,并用于创建委托实例。如果目标方法无法在编译时确定,使用System.Delegate.CreateDelegate(Type, MethodInfo)方法也可以依靠反射信息创建出委托。

CLI的几个主流实现,微软的CLR、Novell的Mono等的JIT编译器都比较静态,遇到虚方法和委托调用都不会内联。从这点说,.NET比JVM的技术复杂度要低一些。不过至少在.NET里使用委托不会带来多少额外开销,所以还是可以放心使用的。

第二组例子,给hello world添加参数:

JDK 7:
import java.dyn.*;
import static java.dyn.MethodHandles.*;

public class TestMethodHandle2 {
    private static void hello(String name) {
        System.out.printf("Hello, %s!\n", name);
    }
    
    public static void main(String[] args) {
        if (0 == args.length) args = new String[] { "Anonymous" };
        
        MethodType type = MethodType.make(void.class, String.class);
        MethodHandle method = lookup()
            .findStatic(TestMethodHandle2.class, "hello", type);
        method.<void>invoke(args[0]);
    }
}

编译,以下述命令运行
java -XX:+EnableMethodHandles TestMethodHandle2 test

输出结果为:
引用
Hello, test!

基本上跟第一组例子一样,只是让hello()多了个参数而已。留意一下创建MethodType实例的代码如何对应的改变。
第15行对应的Java字节码是:
invokevirtual java/dyn/MethodHandle.invoke:(Ljava/lang/String;)V

留意Java编译器是如何根据调用invoke时传入的参数的静态类型(编译时类型)来决定invoke的方法描述符。(Ljava/lang/String;)V的意思是返回值类型为void,参数列表有一个参数,类型为java.lang.String。
如果把代码稍微修改,使MethodHandle的方法类型与Java编译器推断的调用类型不相符的话:
import java.dyn.*;
import static java.dyn.MethodHandles.*;

public class TestMethodHandle2 {
    private static void hello(Object name) {
        System.out.printf("Hello, %s!\n", name);
    }
    
    public static void main(String[] args) {
        if (0 == args.length) args = new String[] { "Anonymous" };
        
        MethodType type = MethodType.make(void.class, Object.class);
        MethodHandle method = lookup()
            .findStatic(TestMethodHandle2.class, "hello", type);
        method.<void>invoke(args[0]);
    }
}

编译运行会看到:
引用
Exception in thread "main" java.dyn.WrongMethodTypeException: (Ljava/lang/Object;)V cannot be called as (Ljava/lang/String;)V
        at TestMethodHandle2.main(TestMethodHandle2.java:15)

这演示了Java编译器将invoke的方法类型推断为(Ljava/lang/String;)V,而被调用的MethodHandle实例实际的方法类型却是(Ljava/lang/Object;)V,JVM便认为这个调用不匹配并拒绝执行。关键点是:调用invoke时,参数表达式的静态类型(编译时类型)必须与MethodHandle的方法类型中对于位置的参数类型“准确一致”;虽然String类型的引用可以隐式转换为Object类型的,但不满足“准确一致”的要求。
要想让修改过的TestMethodHandle2再次正确运行,可以把第15行改为:method.<void>invoke((Object)args[0]);,也就是加个类型转换,使Java编译器推断出来的方法描述符为(Ljava/lang/Object;)V。或者也可以加一个适配器:
import java.dyn.*;
import static java.dyn.MethodHandles.*;

public class TestMethodHandle2 {
    private static void hello(Object name) {
        System.out.printf("Hello, %s!\n", name);
    }
    
    public static void main(String[] args) {
        if (0 == args.length) args = new String[] { "Anonymous" };
        
        MethodType type = MethodType.make(void.class, Object.class);
        MethodType adaptedType = MethodType.make(void.class, String.class);
        MethodHandle method = lookup()
            .findStatic(TestMethodHandle2.class, "hello", type);
        MethodHandle adaptedMethod = MethodHandles.convertArguments(
            method, adaptedType);
        adaptedMethod.<void>invoke(args[0]);
    }
}

这里演示了MethodHandle的可组装性:通过给实际调用目标装一个转换参数类型的适配器,方法调用就又可以成功了。

对比C#的例子:
C# 3.0:
using System;

static class TestDelegate2 {
    static void Hello(string name) {
        Console.WriteLine("Hello, {0}!", name);
    }
    
    static void Main(string[] args) {
        if (0 == args.Length) args = new [] { "Anonymous" };
        Action<string> method = Hello;
        method(args[0]);
    }
}


基本上跟第一组例子也是一样的。Action<T>是标准库里预先声明好的一个泛型委托类型,其声明形如:
namespace System {
    public delegate void Action<T>(T t);
}

为了对比,下面也把Hello()的参数类型改为object,
using System;

static class TestDelegate2 {
    static void Hello(object name) {
        Console.WriteLine("Hello, {0}!", name);
    }
    
    static void Main(string[] args) {
        if (0 == args.Length) args = new [] { "Anonymous" };
        Action<object> method = Hello;
        method(args[0]);
    }
}

编译和运行都没有任何问题。这里要演示的是Invoke()方法是有确定的签名的,与委托类型声明的相吻合。编译器不会擅自推断Invoke()的签名。

第三组例子,可指定排序条件的快速排序:
前面一直在用hello world作例子或许是无聊了点,下面弄点稍微长一些的。

JDK 7:
import java.dyn.*;
import java.util.*;
import static java.dyn.MethodHandles.*;
import static java.lang.Integer.parseInt;

public class TestMethodHandle3 {
    private static int compareStringsByIntegerValue(String num1, String num2) {
        return parseInt(num1) - parseInt(num2);
    }
    
    private static int compareStringsByLength(String str1, String str2) {
        return str1.length() - str2.length();
    }
    
    public static void sort(String[] array, MethodHandle comparer) {
        if (0 == array.length) return;
        sort(array, 0, array.length - 1, comparer);
    }
    
    private static void sort(
        String[] array,
        int left,
        int right,
        MethodHandle comparer) {
        
        if (left >= right) return;
        
        String pivot = array[right];
        int lo = left - 1;
        int hi = right;
        while (true) {
            while (comparer.<int>invoke(array[++lo], pivot) < 0) {
            }
            while (hi > left
                && comparer.<int>invoke(array[--hi], pivot) > 0) {
            }
            if (lo >= hi) break;
            swap(array, lo, hi);
        }
        swap(array, lo, right);
        sort(array, left, lo - 1, comparer);
        sort(array, lo + 1, right, comparer);
    }
    
    private static <E> void swap(E[] array, int i, int j) {
        E temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    public static void main(String[] args) {
        String[] array = new String[] {
            "25", "02", "250", "48", "0024", "42", "2"
        };
        
        MethodType type = MethodType.make(
            int.class,
            String.class, String.class);
        MethodHandle comparer;
        
        comparer = lookup().findStatic(
            TestMethodHandle3.class,
            "compareStringsByIntegerValue",
            type);
        sort(array, comparer);
        for (String s : array) System.out.println(s);
        
        System.out.println();
        
        comparer = lookup().findStatic(
            TestMethodHandle3.class,
            "compareStringsByLength",
            type);
        sort(array, comparer);
        for (String s : array) System.out.println(s);
    }
}

编译,运行,输出结果为:
引用
02
2
0024
25
42
48
250

2
25
42
48
02
250
0024


核心的sort()方法是个简单的快排实现。为了让用户能够指定排序条件,我让它接收MethodHandle为参数来提供判断逻辑。在没有MethodHandle之前,我可能会选择使用策略模式来达到剥离出部分算法的目的。JDK里的许多API也是这么做的,例如Arrays.sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c),其中的Comparator参数就是“策略对象”。现在就可以直接用一个MethodHandle来代替它了。
例子的整体结构跟前两组没有显著的不同,应该还是比较好理解的。
值得注意的是,我把swap()写为了泛型方法,方便以后再更多地方能复用。排序也应该是个通用算法,为何不把sort()也写为泛型呢?
如果把例子中sort()方法里出现的String全部直接替换为泛型的,变成:
    public static <E> void sort(E[] array, MethodHandle comparer) {
        if (0 == array.length) return;
        sort(array, 0, array.length - 1, comparer);
    }
    
    private static <E> void sort(
        E[] array,
        int left,
        int right,
        MethodHandle comparer) {
        
        if (left >= right) return;
        
        E pivot = array[right];
        int lo = left - 1;
        int hi = right;
        while (true) {
            while (comparer.<int>invoke(array[++lo], pivot) < 0) {
            }
            while (hi > left
                && comparer.<int>invoke(array[--hi], pivot) > 0) {
            }
            if (lo >= hi) break;
            swap(array, lo, hi);
        }
        swap(array, lo, right);
        sort(array, left, lo - 1, comparer);
        sort(array, lo + 1, right, comparer);
    }

编译没问题,但运行的时候会看到comparer.<int>invoke(...)的调用抛WrongMethodTypeException异常。你可能会纳闷:array数组的元素类型不是E么,是泛型的啊;以String为泛型参数调用sort()的时候,invoke的实际参数类型与comparer的方法类型应该匹配才对,怎么会出错呢?
问题就在于Java的泛型是通过类型擦除法(type-erasure)来实现的,编译过后所有泛型参数都变为了Object,这里也不例外。所以Java编译器推断出来的invoke()的描述符是(Ljava/lang/Object;Ljava/lang/Object;)I,这就出现我们在第二组例子里遇到的问题了——方法类型不准确匹配。
于是解决的办法也很简单,只要改成这样:
    public static <E> void sort(E[] array, MethodHandle comparer) {
        if (0 == array.length) return;
        Class<Object> objClz = Object.class;
        MethodType adaptedType = MethodType.make(int.class, objClz, objClz);
        MethodHandle adaptedComparer = MethodHandles.convertArguments(
            comparer, adaptedType);
        sort(array, 0, array.length - 1, adaptedComparer);
    }

在调用核心的sort()方法前,先加一个适配器来解决类型差异的问题,就万事大吉了 ^ ^

对比C#的例子:
C# 2.0
using System;
using System.Collections.Generic;

static class TestDelegate3 {
    static int CompareStringsByIntegerValue(string num1, string num2) {
        return Convert.ToInt32(num1) - Convert.ToInt32(num2);
    }
    
    static int CompareStringsByLength(string str1, string str2) {
        return str1.Length - str2.Length;
    }
    
    public static void Sort<T>(T[] array, Comparison<T> comparer) {
        Sort(array, 0, array.Length - 1, comparer);
    }
    
    static void Sort<T>(
        T[] array,
        int left,
        int right,
        Comparison<T> comparer) {
        
        if (left >= right) return;
        
        T pivot = array[right];
        int lo = left - 1;
        int hi = right;
        while (true) {
            while (comparer(array[++lo], pivot) < 0) {
            }
            while (hi > left
                && comparer(array[--hi], pivot) > 0) {
            }
            if (lo >= hi) break;
            Swap(ref array[lo], ref array[hi]);
        }
        Swap(ref array[lo], ref array[right]);
        Sort(array, left, lo - 1, comparer);
        Sort(array, lo + 1, right, comparer);
    }
    
    static void Swap<T>(ref T t1, ref T t2) {
        T temp = t1;
        t1 = t2;
        t2 = temp;
    }
    
    static void Main(string[] args) {
        String[] array = new String[] { "25", "02", "250", "48", "0024", "42", "2" };
        
        Sort(array, CompareStringsByIntegerValue);
        foreach (var s in array) Console.WriteLine(s);
        
        Console.WriteLine();
        
        Sort(array, CompareStringsByLength);
        foreach (var s in array) Console.WriteLine(s);
    }
}


基本上也没什么需要特别解释的了。System.Comparison<T>是标准库里预先声明的一个泛型委托类型,其声明形如:
namespace System {
    public delegate int Comparison<T>(T x, T y);
}

注意.NET里委托与泛型可以很好的结合在一起使用,而不必考虑转换参数类型的问题,因为.NET的泛型信息会带到运行时。或者说,“.NET generics are reified”。

如果用上C# 3.0和.NET Framework 3.5的新特性,使用lambda表达式来提供排序依据,
using System;
using System.Collections.Generic;

static class TestDelegate3 {
    public static void Sort<T>(T[] array, Func<T, T, int> comparer) {
        Sort(array, 0, array.Length - 1, comparer);
    }
    
    static void Sort<T>(
        T[] array,
        int left,
        int right,
        Func<T, T, int> comparer) {
        
        if (left >= right) return;
        
        T pivot = array[right];
        int lo = left - 1;
        int hi = right;
        while (true) {
            while (comparer(array[++lo], pivot) < 0) {
            }
            while (hi > left
                && comparer(array[--hi], pivot) > 0) {
            }
            if (lo >= hi) break;
            Swap(ref array[lo], ref array[hi]);
        }
        Swap(ref array[lo], ref array[right]);
        Sort(array, left, lo - 1, comparer);
        Sort(array, lo + 1, right, comparer);
    }
    
    static void Swap<T>(ref T t1, ref T t2) {
        T temp = t1;
        t1 = t2;
        t2 = temp;
    }
    
    static void Main(string[] args) {
        var array = new [] { "25", "02", "250", "48", "0024", "42", "2" };
        
        Sort(array,
            (s1, s2) => Convert.ToInt32(s1) - Convert.ToInt32(s2));
        foreach (var s in array) Console.WriteLine(s);
        
        Console.WriteLine();
        
        Sort(array,
            (s1, s2) => s1.Length - s2.Length);
        foreach (var s in array) Console.WriteLine(s);
    }
}

我们就不必再为例中用到的排序条件写具名方法了。实际上C#编译器仍然为这两个lambda表达式生成了私有静态方法,我们得到的东西是一样的(硬要说的话,少得到了俩名字),但需要写在代码里的东西减少了。
如果进一步改为使用标准库中现成的方法来排序,
using System;
using System.Collections.Generic;

static class TestDelegate3 {    
    static void Main(string[] args) {
        var array = new [] { "25", "02", "250", "48", "0024", "42", "2" };
        
        Array.Sort(array,
            (s1, s2) => Convert.ToInt32(s1) - Convert.ToInt32(s2));
        foreach (var s in array) Console.WriteLine(s);
        
        Console.WriteLine();
        
        Array.Sort(array,
            (s1, s2) => s1.Length - s2.Length);
        foreach (var s in array) Console.WriteLine(s);
    }
}

整个代码就简洁了许多。


Alright,这帖就介绍MethodHandle与.NET的委托到这里。希望上面的例子达到了简单介绍JDK 7的MethodHandle的目的。
你可能会心存疑惑,“我为什么需要引用别的方法呢?” 由于Java以前一直没有提供这样的功能,一直只使用Java的人可能较少思考这个问题。但你可能会碰到过这样的问题,有时候要调用的目标只有到了运行的时候才知道,无法在代码里直接写方法调用,那怎么办?以前的解决办法就只有通过反射了。一个最简单的使用场景就是,原本通过反射做的方法调用,现在都可以通过MethodHandle完成。如果一个方法频繁被反射调用,开销会很明显,而且难以优化;通过MethodHandle调用则跟正常的接口方法调用速度接近,没有各种包装/装箱的开销,而且可以被JVM优化,何乐而不为?
在MethodHandle出现之前,许多JVM上的脚本语言实现,如JRuby,为了提高调用方法的速度,选择生成大量很小的“invoker类”来对方法签名做特化,通过它们去调用目标方法,避免反射调用的开销。但这么做有许多问题,一是实现麻烦,而是对PermGen堆带来巨大的压力。MethodHandle的出现极大的改善了状况,既便于使用,效率又高,而且还不会对PermGen带来多少压力。Charles Nutter在一帖中描述了这个问题
编辑:经指正,Groovy当前还没有用invoke stub,只是做了callsite caching优化。我只读过Groovy的编译器的代码,没读过运行时部分的代码,忽悠了同学们了,抱歉 <(_ _)>

进一步介绍等以后写invokedynamic的时候再写了~ until then

Have fun ^ ^
   发表时间:2009-09-26   最后修改:2009-09-26

java看起来无可避免地走向复杂化的深渊了,方法引用完全可以不要,只要引入闭包即可,为了曲线救国搞出的这些东西实在是让人犯恶心。看看这样的调用方式,

 comparer = lookup().findStatic(  
             TestMethodHandle3.class,  
             "compareStringsByIntegerValue",  
             type);  
 sort(array, comparer);  
 


实在是悲剧

 

0 请登录后投票
   发表时间:2009-09-26  
C#中的委托非常实用,但Java的这个做法有多少可用性啊
0 请登录后投票
   发表时间:2009-09-26  
JeffreyZhao 写道
C#中的委托非常实用,但Java的这个做法有多少可用性啊


文章中说了,作为重量级的method reflection的替代品还是不错的。比之策略模式来说,这个method handler的调用方式还是比较恶心。
0 请登录后投票
   发表时间:2009-09-26  
JeffreyZhao 写道
C#中的委托非常实用,但Java的这个做法有多少可用性啊

还是蛮实用的吧. 虽然不能代替reflection .但是提高了调用效率 ..

    话说reflection 本来在一般系统中用的就不多... method handle会不会在编程范式上有一些新的东西出现呢?

    老赵或许可以从c#的委托中预见到将来Java可能会出现的范式. . 跟fx在这里展开谈谈吧..  洗耳恭听.
0 请登录后投票
   发表时间:2009-09-26  
再次投了关键精华一票~

long~long~long~text

比起可用性,能让invoke的效率提高已经很满足了,指望java把所有事情都做了有点不现实,如果期望更多的灵活性,可以用python和ruby
0 请登录后投票
   发表时间:2009-09-26  
dennis_zane 写道
JeffreyZhao 写道
C#中的委托非常实用,但Java的这个做法有多少可用性啊


文章中说了,作为重量级的method reflection的替代品还是不错的。比之策略模式来说,这个method handler的调用方式还是比较恶心。

嗯,没错。说真的,由于缺乏在代码中直接表示对方法的引用的语法,要用MethodHandle去替代策略模式挺丑的。在需要“传递一小块代码”的场景中我可能还是会偏向使用匿名内部类,其实也不麻烦。
MethodHandle的主要要解决的问题就是“要高效的调用编译时已知签名但具体目标未知的方法”。可以想像有时候要根据配置去找bean上的getter/setter,或者是要不通过继承而通过聚合去拦截方法,之类的;这种地方以前只能靠反射,如今MethodHandle提供了更轻量且高效的解决方案。
我在这帖里故意没强调MethodHandle跟invokedynamic的关系,因为还有后续的帖在草稿箱里窝着 =v= 我原本是先在记invokedynamic的资料,写着写着发现不先写MethodHandle不行……||||
0 请登录后投票
   发表时间:2009-09-26  
如此的Java还是我们心目中的那个纯纯的咖啡嘛!!!
0 请登录后投票
   发表时间:2009-09-26   最后修改:2009-10-05
编辑掉。。。。。。。。
0 请登录后投票
   发表时间:2009-09-26  
linliangyi2007 写道
如此的Java还是我们心目中的那个纯纯的咖啡嘛!!!

有点同感
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics