论坛首页 Java企业应用论坛

Java之Lambda的初步学习

浏览 2151 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-02-13  


   前几天周大写了博客“解析JDK 7的动态类型语言支持”,见详http://www.iteye.com/topic/1120139。详细的介绍了JVM新指令invokedynamic。不过目前(JDK1.7)光靠javac没有办法生成带有invokedynamic 指令的字节码,它的实际意义在于JDK1.8的Lambda项目。今天1.8的预览版发布了,我也有空抽时间看了看Lambda的知识。

   说道Lambda项目,不得不先了解下“Lambda”的含义(数学知识丰富的可以略过)。Lambda表示数学符号“λ”,λ演算是计算机科学的一个基础,λ算法是被设计出来研究函数的定义、应用和递归的。表达了计算机中最基本的概念:“调用”和“置换”。基于λ演算发展出了不少新的演算方法,近年来很多经典的并发程序模型都是以它们为基础的。简而言之就是相当的牛X。

    回到Lambda项目里,Java引入Lambda语法的目的是为了加入Java对闭包的更好(完全?)支持。

    先来看看现有语言中的Lambda语法,比如Python。

    在Python中,定义一个方法:

 

def fun(x):
        return x*2

    运行 fun(5) 
    输出 10

 

 

   如果使用lambda语法则为:
(lambda x: x*2)(5)
 输出10.
    
    可以看到出,这样可以省略“定义”方法的步骤,或者说更灵活的定义一个方法。

    同样,Scheme、Groovy、JavaScript、Ruby等众多动态语言中都可以将函数作为对象,即些函数可以存储到变量中、作为参数传递给其他函数,这意味这函数可以被“动态”的创建和返回。C语言中也有函数指针,不过需要预先定义好参数列表。
    
    回到Java中,lambda语法的最直接的使用方法如下:

class A{
    private static int method(int x,int y){
       return x+y;
    }
}

  public static void main(String[] args){
      System.out.println(A.method(2,3));
  }

运行结果为5
 
换成Lambda语法变成:
public static void main(String[] args){
   Func f = #(int x int y)(x + n); 
   System.out.println(f.(2,3));//这个.用真不舒服……
}

运行结果为5.
   
这里#(int x int y)(x + n) 表示了 A.method()方法。
    Lambda语法 为 #()()
    第一个'#'表示为Lambda语法。
    第一个()中标记出的是参数的列表。
    第二个()是函数体,如果函数体只有单条语句,则可以用(),且不需要return关键字。如果有多条语句,必须用{},且有关键字,比如#(int x, int y){ x+=1 ; y+=2; return x+y;}//这里只是举例子说明,能简化当然就简化了。这里吐槽一下,java的Lambda语法很不“优雅”。


   如果仅从使用和实用角度来看Lambda语法似乎意义并不大,并且目前并不优雅的语法已经引起了不少非议,不过oracle大力推行Lambda语法的目的是从闭包角度来说的。
   说道闭包又是个讨论很多的话题,数学上闭包的定义很明确:对于映射F,集合S的任意成员x,都满足F(X)属于S,则称集合S在映射F下是满足闭包性质的。到了编程的领域,闭包(Closure)是词法闭包(Lexical Closure)的简称,对于它的定义有不同的观点,这里就不深究了。满足闭包的程序语言一般具有的性质是:
   1.函数是一阶值(First-class value),即上文提到的,函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
   2.函数可以嵌套定义,即在一个函数内部可以定义另一个函数。
   更多关于Java闭包的探讨可以看看这里:http://www.ibm.com/developerworks/cn/java/j-jtp04247.html

Lambda语法的实现(这里是参考别人之前的文章,并没实际实验过,仅供参考)。
    java语言支持闭包的传统手段是匿名内部类,在jdk1.6中引入了注解处理器,他可以提供新的语言特性,即单一抽象方法(Single Abstract Method),简称SAM类型。而Lambda通过invokedynamic指令转换SAM类型。
    我主要参考的是Rémi Forax的博客,他写的最清楚,地址为http://weblogs.java.net/blog/forax/archive/2011/01/04/jsr-292-goodness-lambda-sam-type-conversion-using-invokedynamic。
    目前Java没有生成invokedynamic的语法(Rémi Forax在写博客时),所以在博客中使用的是伪Java代码,和将来的实际代码会有所区别。
    比如有Lambda语法:

Comparator<Object, Object> c = #{  o1, o2 -> 1 };
 
     编译器的步骤为:
    1.为这个lambda计划生成一个静态方法 lambda$1。
    2.生成这个静态方法对应的方法句柄(MethodHandle)。
    3.将这个方法句柄翻译进一个class实例子,这个class实现 java.lang.reflect.Proxy接口。到这发现又是“代理”了。
    此时,即创建实现java.lang.reflect.Proxy接口的class实例时,lumbda还未绑定任何局部变量,

    这时被编译为:
Comparator c = (Comparator) invokedynamic [Lambdas#asSamBSM, #lambda$1, Comparator.class] ();

 

    在未来计划版本中, ASM将支持invokedynamic的boostrap参数是:

 

     mv.visitIndyMethodInsn("_", "()Ljava/util/Comparator;",
        new MHandle(MHandle.REF_invokeStatic, "Lambdas", "asSamBSM",
            "(Ljava/dyn/MethodHandles$Lookup;Ljava/lang/String;Ljava/dyn/MethodType;[Ljava/lang/Object;)Ljava/dyn/CallSite;"),
        new Object[] {
            new MHandle(MHandle.REF_invokeStatic, "LambdaTest", "lambda$1",
                 "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I"),
            Type.getObjectType("java/util/Comparator")
            });
    mv.visitVarInsn(ASTORE, 1);

 

 

    invokedynamic的bootstrap方法代码是

 

public class Lambdas {
  public static CallSite asSamBSM(Lookup lookup, String name, MethodType methodType, Object[] bsmArgs) {
    MethodHandle lambda = (MethodHandle)bsmArgs[0];
    Class<?> samInterface = (Class<?>)bsmArgs[1];
    
    if (methodType.returnType() != samInterface) {
      throw new InvokeDynamicBootstrapError("asSam incompatible return type "+methodType);
    }
    
    int parameterCount = methodType.parameterCount();
    if (parameterCount == 0) {  // constant case
      Object samInstance = MethodHandles.asInstance(lambda, samInterface);
      return new ConstantCallSite(
          MethodHandles.constant(samInterface, samInstance));
    }
    
    if (parameterCount == 1) {  // bind case
      MethodHandle combiner = BIND_TO.bindTo(lambda);
      MethodHandle target = MethodHandles.insertArguments(AS_INSTANCE, 1, samInterface);
      target = MethodHandles.dropArguments(target, 1, methodType.parameterType(0));
      
      return new ConstantCallSite(
          MethodHandles.foldArguments(target, combiner).asType(methodType));
    }
    
    throw new InvokeDynamicBootstrapError("asSam incompatible method type "+methodType);
  }
  
  private static final MethodHandle AS_INSTANCE;
  private static final MethodHandle BIND_TO;
  static {
    try {
      Lookup lookup = MethodHandles.publicLookup();
      AS_INSTANCE = lookup.findStatic(MethodHandles.class, "asInstance",
          MethodType.methodType(Object.class, MethodHandle.class, Class.class));
      BIND_TO = lookup.findVirtual(MethodHandle.class, "bindTo",
          MethodType.methodType(MethodHandle.class, Object.class));
    } catch (NoAccessException e) {
      throw new LinkageError("linkage error", e);
    }
  }
}
 

 

    无论lumbda的语法怎样它都已经来,基于lumbda特性,JVM或许能更好的支持脚本语言。这两天会抽时间多多研究。希望大家拍砖!

论坛首页 Java企业应用版

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